自成一套
Java基础
jvm jre jdk 分别是什么?
a. JDK(Java Development Kit) 是 Java 语言的软件开发工具包(SDK)
b. JRE(Java Runtime Environment,Java 运行环境),包含 JVM 标准实现及 Java 核心类库。JRE 是 Java 运行环境,并不是一个开发环境,所以没有包含任何开发工具(如编译器和调试器)
c. JVM 是 Java Virtual Machine(Java 虚拟机)的缩写,JVM 是一种用于计算设备的规范,它是一个虚构出来的计算机
Java 三大特性
面向对象是利于语言对现实事物进行抽象。面向对象具有以下四大特征:
(1)继承:继承是从已有类得到继承信息创建新类的过程
(2)封装:通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。
(3)多态性:多态性是指允许不同子类型的对象对同一消息作出不同的响应。
(4)抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。
int 和 Integer 有什么区别,以及以下程序结果
(1)Integer 是 int 的包装类,int 则是 java 的一种基本数据类型
(2)Integer 变量必须实例化后才能使用,而 int 变量不需要
(3)Integer 实际是对象的引用,当 new 一个 Integer 时,实际上是生成一个指针指向此对象;而 int 则是直接存储数据值
(4)Integer 的默认值是 null,int 的默认值是 0
Java 八大基本数据类型
a. 浮点型:float,double
b. 字符型:char
的是你吗12w hujonwrt6 w强. 整型:[email protected],short,int,C 入1塞k,ol./分区,
d. Boolean 型:boolean
&、| 和&&、||的区别
&、|有两种用法:(1)按位运算;(2)逻辑运算。逻辑运算时不会短路
&&、||逻辑运算符,会短路。
==和 Equals 区别
(1) ==
如果比较的是基本数据类型,那么比较的是变量的值
如果比较的是引用数据类型,那么比较的是地址值(两个对象是否指向同一块内存)
(2) equals
如果没重写 equals 方法比较的是两个对象的地址值
如果重写了 equals 方法后我们往往比较的是对象中的属性的内容
equals 方法是从 Object 类中继承的,默认的实现就是使用==
hashcode 与 equal
1.equal()相等的两个对象他们的 hashCode()肯定相等,
2.hashCode()相等的两个对象他们的 equal()不一定相等,
所有对于需要大量并且快速的对比的话如果都用 equal()去做显然效率太低,所以解决方式是,每当需要对比 的时候,首先用 hashCode()去对比,如果 hashCode()不一样,则表示这两个对象肯定不相等(也就是不必再用equal()去再对比了),如果 hashCode()相同,此时再对比他们的 equal(),如果 equal()也相同,则表示这两个对象是真的相同了,这样既能大大提高了效率也保证了对比的绝对正确性!
String StringBuilder StringBuffer 的区别
1.String 为字符串常量,而 StringBuilder 和 StringBuffer 均为字符串变量,即 String 对象一旦创建之后该对象是不可更改的,但后两者的对象是变量,是可以更改的。
2.String buffer 和 String build 区别
(1)StringBuffer 与 StringBuilder 中的方法和功能完全是等价的,
(2)只是 StringBuffer 中的方法大都采用了 synchronized 关键字进行修饰,因此是线程安全的,而 StringBuilder 没有这个修饰,可以被认为是线程不安全的。
(3)在单线程程序下,StringBuilder 效率更快,因为它不需要加锁,不具备多线程安全而 StringBuffer 则每次都需要判断锁,效率相对更低
方法重载与重写的区别
重写:子类的方法覆盖父类的方法,要求返回值、方法名和参数都相同。 子类抛出的异常不能超过父类相应方法抛出的异常。(子类异常不能超出父类异常) 子类方法的的访问级别不能低于父类相应方法的访问级别(子类访问级别不能低于父类访问级别)
重载:是在同一个类中的两个或两个以上的方法,拥有相同的方法名,但是参数却不相同,方法体也不相同,
java 集合
Java 集合类存放于 java.util 包中,是一个用来存放对象的容器。Collection 是 List 接口和 Set 接 口的父接口
List 实现类:
Vector 底层数据结构是数组,查询快增删慢;线程安全,效率低,
ArrayList 底层数据结构是数组查询快增删慢;线程不安全,效率高,
ArrayList 初始容量为 10 扩容算法(原来的容量*3)/2+1 1.7 1.8 初始容量+(初始容量/2)
LinkedList 底层数据结构是链表,查询慢,增删快;线程不安全,效率高
ArrarList 和 LinkedList 区别
(1)ArrayList 是实现了基于动态数组的数据结构,LinkedList 基于链表的数据结构。
(2)对于随机访问 get 和 set,ArrayList 觉得优于 LinkedList,因为 LinkedList 要移动指针。
(3)对于新增和删除操作 add 和 remove,LinkedList 比较占优势,因为 ArrayList 要移动数据。 这一点要看实际情况的。若只对单条数据插入或删除,ArrayList 的速度反而优于 LinkedList。但若是批量随机的插入删除数据,LinkedList 的速度大大优于 ArrayList. 因为 ArrayList 每插入一条数据,要移动插入点及之后的所有数据
Set 实现类:
HashSet:不能保证元素的顺序不可重复;不是线程安全的;集合元素可以为空不可以重复,有序因为底层采用链表 和 哈希表的算法。链表保证元素的添加顺序,哈希表保证元素的唯一性
TreeSet:有序;不可重复,底层使用 红黑树算法,擅长于范围查询。
TreeSet 和 HashSet 区别
HashSet 是采用 hash 表来实现的。其中的元素没有按顺序排列,add()、remove()以及 contains()等方法都是复杂度为 O(1)的方法。
TreeSet 是采用树结构实现(红黑树算法)。元素是按顺序进行排列,但是 add()、remove()以及 contains()等方法都是复杂度为 O(log (n))的方法。它还提供了一些方法来处理排序的 set,如 first(), last(), headSet(), tailSet()等等。
Map 的实现类
HashMap
为什么用的是Hash表
如果不用hash表,那么他存储的元素将是无序的,如果往里面添加元素并且不重复的话需要用equals()
进行判断,如果是单个元素还好,如果是多个那么比较起来特别麻烦
HashMap1.7和1.8的区别
1.7:数组+链表
HashMap默认哈希表是16,在1.7之前如果需要对其进行添加元素的话,首先会去调用对象的hashCode()方法,然后根据哈希算法对这个hashCode()进行运算,运算之后生成一个数组的索引值,根据这个索引值找到对应的位置,如果没有对象栈那么直接添加进去如果存在对象栈,那么通过equals()比较两者的内容,如果相同,那么后进来的会把存在的Value值覆盖,反之形成链表,jdk1.7的时候后进来的放在前面,形成链表,造成碰撞,如果元素过多的话会降低效率,hashMap提供了加载因子(0.75)减少类似这个问题的发生,但是相应的效率低,如果进行查询的话极端情况下可能查找到最后一个才是自己想要的元素,因此效率可能会更低
加载因子(当元素到达现有Hash表的75%进行扩容)一旦扩容那么它将链表中的元素进行重排序
1.8:数组+链表+红黑树
jdk1.8对他进行了优化,添加了红黑树算法,那么他触发红黑树的条件是,当碰撞的元素的个数大于8,并且总元素(hash表)大于64.在这种情况下他会将链表转换为红黑树,在哈希表扩容时,如果发现链表长度小于 6,则会由树重新退化为链表,除了添加之外其他操作都比链表效率高,因为在jdk1.8的时候添加是添加到了红黑树的末尾,在此期间他要对红黑树存储的元素进行比较找位置.其他方面红黑树减少了对比的次数那么相应的效率就上来啦
HashTable
hashTable 是线程安全的一个 map 实现类,它实现线程安全的方法是在各个方法上添加了 synchronize 关键字。 但是现在已经不再推荐使用 HashTable 了,因为现在有了 ConcurrentHashMap 这个专门用于多线程场景下的 map实现类,其大大优化了多线程下的性能。
ConcurrentHashMap: 并发数/并发级别:16
这个 map 实现类是在 jdk1.5 中加入的,其在 jdk1.6/1.7 中的主要实现原理是 segment 段锁,它不再使用和 HashTable 一样的 synchronize 一样的关键字对整个方法进行加锁,转而利用 segment 段落锁来对其进行加锁,以 保证 Map 的多线程安全。
其实可以理解为,一个 ConcurrentHashMap 是由多个 HashTable 组成,所以它允许获取不用段锁的线程同时持有该资源,segment 有多少个,理论上就可以同时有多少个线程来持有它这个资源。
其默认的 segment 是一个数组,默认长度为 16。也就是说理论商可以提高 16 倍的性能
在 JAVA 的 jdk1.8 中 则 对 ConcurrentHashMap 又 再 次 进 行 了 大 的 修 改 , 取 消 了 segment 段 锁 字 段 , 采 用 了CAS+Synchronize 技术来保障线程安全。底层采用数组+链表+红黑树的存储结构
为什么要这样做?
锁分段X(段的长度不好评定,太大资源浪费,太小元素过多) jdk1.7
CAS 算法(操作系统底层的算法)
HashMap 和 HashTable 区别
(1)线程安全性不同
HashMap 是线程不安全的,HashTable 是线程安全的,其中的方法是 Synchronize 的,在多线程并发的情况下,可以直接使用 HashTable,但是使用 HashMap时必须自己增加同步处理。
(2)是否提供 contains 方法
HashMap 只有 containsValue 和 containsKey 方法;HashTable 有 contains、containsKey 和 containsValue 三个方法,其中 contains 和 containsValue 方法功能相同。
(3)key 和 value 是否允许 null 值
Hashtable 中,key 和 value 都不允许出现 null 值。HashMap 中,null 可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为 null。
(4)数组初始化和扩容机制
HashTable 在不指定容量的情况下的默认容量为 11,而 HashMap 为 16,Hashtable 不要求底层数组的容量一定要为 2 的整数次幂,而 HashMap 则要求一定为 2 的整数次幂。
Hashtable 扩容时,将容量变为原来的 2 倍加 1,而 HashMap 扩容时,将容量变为原来的 2 倍。
线程和进程的区别
1.一个进程至少有一个线程
2.进程是资源的分配和调度的一个独立单元,而线程是 CPU 调度的基本单元.
3.一个线程只能属于一个进程,但是一个进程可以拥有多个线程。多线程处理就是允许一个进程中在同一时刻执行多个任务。
进程间的几种通信方式说一下?
1、锁机制
互斥锁:提供了排它方式阻止数据结构被并发修改的方法。
读写锁:允许多个线程同时读共享数据,而对写操作互斥。
条件变量:可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。
2、信号量机制:包括无名线程信号量与有名线程信号量
3、信号机制:类似于进程间的信号处理。
线程间通信的主要目的是用于线程同步,所以线程没有像进程通信中用于数据交换的通信机制
线程的生命周期
新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
就绪状态(Runnable):当调用线程对象的 start()方法,线程即进入就绪状态。处于就绪状态的线程,只是说明 此线程已经做好了准备,随时等待 CPU 调度执行,并不是说执行了 start()此线程立即就会执行;
运行状态(Running):当 CPU 开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。 注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对 CPU 的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被 CPU 调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状 态又可以分为三种:
1.等待阻塞:运行状态中的线程执行 wait()方法,使本线程进入到等待阻塞状态;
2.同步阻塞 – 线程在获取 synchronized 同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
3.其他阻塞 – 通过调用线程的 sleep()或 join()或发出了 I/O 请求时,线程会进入到阻塞状态。当 sleep()状态超时、 join()等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入就绪状态。
死亡状态(Dead):线程执行完了或者因异常退出了 run()方法,该线程结束生命周期。
sleep yield wait join 方法的区别
sleep()方法
运行中的线程,执行 sleep()方法,放弃 CPU,转到阻塞状态。但不会放弃占有的资源,例如锁。sleep()时间结束,进入可运行状态。
yield()方法
执行 yield()方法,如果此时有相同或更高优先级的其他线程处于就绪状态,那么 yield()方法把当前线程放到可运行池中。如果只有相同优先级的线程,那么,该线程可能接着马上执行。如果没有相同或更高优先级线程,则什么也不做。yield()方法并不会释放锁。
wait()方法
wait 方法用于线程通信,与 notify,nofityall 配合使用。它必须在 synchronized 语句块内使用。wait()方法使当前线程暂停执行并释放对象锁标示,让其他线程可以进入 synchronized 数据块,当前线程被放入对象等待池,该方法是 java.lang.Object 的方法。
join()方法
当前运行的线程执行另一个线程的 join()方法,当前线程进入阻塞状态,直到另一个线程运行结束,它才会恢复到可就绪状态。join()方法,调用了 wait(),因此它会释放锁。
创建线程的三种方式
一、继承 Thread 类创建线程类
(1)定义 Thread 类的子类,并重写该类的 run 方法,该 run 方法的方法体就代表了线程要完成的任务。因此把 run() 方法称为执行体。
(2)创建 Thread 子类的实例,即创建了线程对象。
(3)调用线程对象的 start()方法来启动该线程。
二、通过 Runnable 接口创建线程类
(1)定义 runnable 接口的实现类,并重写该接口的 run()方法,该 run()方法的方法体同样是该线程的线程执行体。
(2)创建 Runnable 实现类的实例,并依此实例作为 Thread 的 target 来创建Thread 对象,该 Thread 对象才是真正的线程对象。
(3)调用线程对象的 start()方法来启动该线程。
三、通过 Callable 和 Future 创建线程
(1)创建 Callable 接口的实现类,并实现 call()方法,该 call()方法将作为线程执行体,并且有返回值。
(2)创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable对象的 call()方法的返回值。
(3)使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。
(4)调用 FutureTask 对象的 get()方法来获得子线程执行结束后的返回值
Io 流
1、IO 流的分类
根据处理数据类型的不同分为:字符流和字节流
根据数据流向不同分为:输入流和输出流
A)输入流和输出流区别
输入流只能进行读操作,输出流只能进行写操作,程序中需要根据待传输数据的不同特性而使用不同的流。
B) 字符流和字节流区别
读写单位不同:字节流以字节(8bit)为单位,字符流以字符为单位,根据码表映射字符,一次可能读多个字节。
处理对象不同:字节流能处理所有类型的数据(如图片、视频等),而字符流只能处理字符类型的数据
字节流:一次读入或读出是 8 位二进制。
字符流:一次读入或读出是 16 位二进制。
设备上的数据无论是图片或者视频,文字,它们都以二进制存储的。二进制的最终都是以一个 8 位为数据单元进行体现,所以计算机中的最小数据单元就是字节。意味着,字节流可以处理设备上的所有数据,所以字节流一样可以处理 字符数据。
结论:只要是处理纯文本数据,就优先考虑使用字符流。 除此之外都使用字节流。
C)缓冲流:
BufferedInputStrean 、BufferedOutputStream、 BufferedReader、 BufferedWriter 增加缓冲功能,避免频繁读写硬盘
Java NIO 简介
Java NIO 与 IO 的主要区别
IO NIO
面向流(Stream Oriented) 面向缓冲区(Buffer Oriented)
阻塞IO(Blocking IO) 非阻塞IO(Non Blocking IO)
(无) 选择器(Selectors)
通道和缓冲区
Java NIO系统的核心在于:通道(Channel)和缓冲区(Buffer)。通道表示打开到 IO 设备(例如:文件、套接字)的连接。若需要使用 NIO 系统,需要获取用于连接 IO 设备的通道以及用于容纳数据的缓冲区。然后操作缓冲区,对数据进行处理。
简而言之,Channel 负责传输, Buffer 负责存储
缓冲区的基本属性
标记、位置、限制、容量遵守以下不变式: 0 <= mark <= position <= limit <= capacity
谈谈你对反射的理解
(1)什么是反射机制?有什么作用?
所谓的反射机制就是 java 语言在运行时拥有一项自观的能力。通过这种能力可以彻底的了解自身的情况为下一步的动作做准备。
Java 的反射机制的实现要借助于 4 个类:class,Constructor,Field,Method;
其中 class 代表的是类对象,Constructor-类的构造器对象,Field-类的属性对象,Method-类的方法对象。通过这四个对象我们可以粗略的看到一个类的各个组成部分。
在 Java 运行时环境中,对于任意一个类,可以知道这个类有哪些属性和方法。对于任意一个对象,可以调用它的任意一个方法。这种动态获取类的信息以及动态调用对象的方法的功能来自于 Java 语言的反射(Reflection)机制。
Java 8新特性简介
Lambda 表达式
创建对象的 5 种方式
使用new关键字 调用了构造函数
使用Class类的newInstance方法 调用了构造函数
使用Constructor类的newInstance方法 调用了构造函数
使用clone方法 没有调用构造函数
使用反序列化 没有调用构造函数
Synchronized lock volatile 的区别
一、synchronized 与 Lock 的区别
1.首先 synchronized 是 java 内置关键字,在 jvm 层面,Lock 是个 java 类;
2.synchronized 无法判断是否获取锁的状态,Lock 可以判断是否获取到锁;
3.synchronized 会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁), Lock 需在 finally 中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
4.用 synchronized 关键字的两个线程 1 和线程 2,如果当前线程 1 获得锁,线程 2 线程等待。如果线程 1 阻 塞,线程 2 则会一直等待下去,而 Lock 锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
5.synchronized 的锁可重入、不可中断、非公平,而 Lock 锁可重入、可判断、可公平(两者皆可)
6.Lock 锁适合大量同步的代码的同步问题,synchronized 锁适合代码少量的同步问题。
二、synchronized 和 volatile 区别
volatile 关键字解决的是变量在多个线程之间的可见性;而 sychronized 关键字解决的是多个线程之间访问共享资源的同步性。
volatile 只能用于修饰变量,而 synchronized 可以修饰方法,以及代码块。(volatile 是线程同步的轻量级实现,所以 volatile 性能比 synchronized 要好,
多线程访问 volatile 不会发生阻塞,而 sychronized 会出现阻塞。
volatile 能保证变量在多个线程之间的可见性,但不能保证原子性;而 sychronized 可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公有内存中的数据做同步。
线程安全包含原子性和可见性两个方面。
对于用 volatile 修饰的变量,JVM 虚拟机只是保证从主内存加载到线程工作内存的值是最新的。
三、公平锁和非公平锁的区别
公平锁,在并发环境中,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程是等待队列的第一个,就占有锁, 否则就会加入到等待队列中,以后会按照FIFO 的规则从队列中取
非公平锁比较粗鲁,上来就直接尝试占有锁,如果尝试失败,就再采用类似公平锁那种方式
volatile 关键字
Java 提供了一种稍弱的同步机制,即 volatile 变量,用来确保将变量的更新操作通知到其他线程。可以将 volatile 看做一个轻量级的锁,但是又与锁有些不同:
说说自己是怎么使用 synchronized 关键字,在项目中用到了吗
synchronized关键字最主要的三种使用方式:
修饰实例方法,作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁
修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁 。因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管new了多少个对象,只有一份,所以对该类的所有对象都加了锁)。所以如果一个线程A调用一个实例对象的非静态 synchronized 方法,而线程B需要调用这个实例对象所属类的静态synchronized 方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态synchronized 方法占用的锁是当前实例对象锁。
修饰代码块,对给定对象加锁,进入同步代码块前要获得给定对象的锁。 synchronized(this)代码块锁定的是当前对象。synchronized 关键字加到 static 静态方法和synchronized(class)代码块上都是是给 Class 类上锁。这里再提一下:synchronized关键字加到非 static 静态方法上是给对象实例上锁。另外需要注意的是:尽量不要使用 synchronized(String a) 因为JVM中,字符串常量池具有缓冲功能!
Synchronized用过吗,其原理是什么?
Synchronized是由JVM实现的一种互斥同步方式,如果你查看被Synchronized修饰程序块编译后的字节码,会发现,被Synchronized修饰过的程序块,在编译前后被编译器生成了monitorenter和monitorexit两个字节码指令。这两个指令是什么意思呢?在虚拟机执行到monitorenter指令时,首先会尝试获取对象的锁:如果这个对象没有锁定,或者当前线程已经拥有了这个对象的锁,把锁的计数器+ 1;当执行monitorexit指令时将锁计数器-1; 当计数器为0时,锁就被释放了。如果获取对象失败了,那当前线程就要阻塞等待,直到对象锁被另一个线程释放为止。Java中Synchronize通过在对象头设置标记,达到了获取锁和释放锁的目的。
你刚才提到获取对象的锁,这个“锁”到底是什么?如何确定对象的锁?
“锁”的本质其实是monitorenter和monitorexit字节码指令的一个Reference类型的参数,即要锁定和解锁的对象。使用Synchronized可以修饰不同的对象,对应的对象锁可以这么确定。
1.如果Synchronized明确指定了锁对象,比如Synchronized(变量名)、Synchronized(this)等,说明加解锁对象为该对象。
2.如果没有明确指定:若Synchronized修饰的方法为非静态方法,表示此方法对应的对象为锁对象;若Synchronized修饰的方法为静态方法,则表示此方法对应的类对象为锁对象。
注意,当一个对象被锁住时,对象里面所有用Synchronized修饰的方法都将产生堵塞,而对象里非Synchronized修饰的方法可正常被调用,不受锁影响。
什么是可重入性,为什么说Synchronized是可重入锁?
可重入性是锁的一个基本要求,是为了解决自己锁死自己的情况。比如下面的伪代码,一个类中的同步方法调用另一个同步方法,假如Synchronized不支持重入,进入method2方法时当前线程获得锁,method2方法里面执行method1时当前线程又要去尝试获取锁,这时如果不支持重入,它就要等释放,把自己阻塞,导致自己锁死自己。对Synchronized来说,可重入性是显而易见的,刚才提到,在执行monitorenter指令时,如果这个对象没有锁定,或者当前线程已经拥有了这个对象的锁(而不是已拥有了锁则不能继续获取),就把锁的计数器+1,其实本质上就通过这种方式实现了可重入性。
为什么说Synchronized是非公平锁?
非公平主要表现在获取锁的行为上,并非是按照申请锁的时间前后给等待线程分配锁的,每当锁被释放后,任何一个线程都有机会竞争到锁,这样做的目的是为了提高执行性能,缺点是可能会产生线程饥饿现象。
单例模式了解吗?来给我手写一下!给我解释一下双重检验锁方式实现单例模式的原理呗!
//双重校验锁实现对象单例(线程安全)
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
//先判断对象是否已经实例过,没有实例化过才进入加锁代码
if (uniqueInstance == null) {
//类对象加锁
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
uniqueInstance 采用 volatile 关键字修饰也是很有必要的, uniqueInstance = new Singleton(); 这段代码其实是分为三步执行:
但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用getUniqueInstance() 后发现uniqueInstance 不为空,因此返回 uniqueInstance,但此时 uniqueInstance 还未被初始化。
使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。
说说 JDK1.6 之后的synchronized 关键字底层做了哪些优化,可以详细介绍一下这些优化吗
JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。
锁主要存在四种状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。
谈谈 synchronized和ReenTrantLock 的区别
① 两者都是可重入锁
“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。
② synchronized 依赖于 JVM 而 ReenTrantLock 依赖于 API
synchronized 是依赖于 JVM 实现的,虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。ReenTrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock 方法配合 try/fifinally 语句块来完成)
③ ReenTrantLock 比 synchronized 增加了一些高级功能
①等待可中断
ReenTrantLock提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。
②可实现公平锁
ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。 ReenTrantLock默认情况是非公平的,可以通过 ReenTrantLock类的ReentrantLock(boolean fair) 构造方法来制定是否是公平的。
③可实现选择性通知(锁可以绑定多个条件)
synchronized关键字与wait()和notify/notifyAll()方法相结合可以实现等待/通知机制,ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition() 方法。Condition是JDK1.5之后才有的,它具有很好的灵活性,比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例(即对象监视器),线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。 在使用notify/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的,用ReentrantLock类结合Condition实例可以实现“选择性通知” ,这个功能非常重要,而且是Condition接口默认提供的。而synchronized关键字就相当于整个Lock对象中只有一个Condition实例,所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题,而Condition实例的signalAll()方法 只会唤醒注册在Condition实例中的所有等待线程。
如果你想使用上述功能,那么选择ReenTrantLock是一个不错的选择。
讲一下Java内存模型
在 JDK1.2 之前,Java的内存模型实现总是从主存(即共享内存)读取变量,是不需要进行特别的注意的。而在当前的 Java 内存模型下,线程可以把变量保存本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成数据的不一致要解决这个问题,就需要把变量声明为 volatile,这就指示 JVM,这个变量是不稳定的,每次使用它都到主存中进行读取。
说白了, volatile 关键字的主要作用就是保证变量的可见性然后还有一个作用是防止指令重排序。
为什么要用线程池?
线程池提供了一种限制和管理资源(包括执行一个任务),每个线程池还维护一些基本统计信息.
使用线程池的好处:
降低资源消耗。 通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
提高响应速度。 当任务到达时,任务可以不需要的等到线程创建就能立即执行。
提高线程的可管理性。 线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
实现Runnable接口和Callable接口的区别
如果想让线程池执行任务的话需要实现的Runnable接口或Callable接口。 Runnable接口或Callable接口实现类都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行。两者的区别在于 Runnable 接口不会返回结果但是 Callable 接口可以返回结果。
Java JUC 是什么
在 Java 5.0 提供了 java.util.concurrent (简称JUC )包,在此包中增加了在并发编程中很常用的实用工具类,用于定义类似于线程的自定义子系统,包括线程池、异步 IO 和轻量级任务框架。提供可调的、灵活的线程池。还提供了设计用于多线程上下文中的 Collection 实现等
Condition 是什么?
介绍一下Atomic 原子类
Atomic 翻译成中文是原子的意思。在化学上,我们知道原子是构成一般物质的最小单位,在化学反应中是不可分割的。在我们这里 Atomic 是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。
所以,所谓原子类说简单点就是具有原子/原子操作特征的类
JUC 包中的原子类是哪4类?
基本类型
使用原子的方式更新基本类型
AtomicInteger:整形原子类
AtomicLong:长整型原子类
AtomicBoolean :布尔型原子类
数组类型
使用原子的方式更新数组里的某个元素
AtomicIntegerArray:整形数组原子类
AtomicLongArray:长整形数组原子类
AtomicReferenceArray :引用类型数组原子类
引用类型
AtomicReference:引用类型原子类
AtomicStampedRerence:原子更新引用类型里的字段原子类
AtomicMarkableReference :原子更新带有标记位的引用类型
对象的属性修改类型
AtomicIntegerFieldUpdater:原子更新整形字段的更新器
AtomicLongFieldUpdater:原子更新长整形字段的更新器
AtomicStampedReference :原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。
volatile 关键字 内存可见性
内存可见性(Memory Visibility)是指当某个线程正在使用对象状态而另一个线程在同时修改该状态,需要确保当一个线程修改了对象状态后,其他线程能够看到发生的状态变化。
可见性错误是指当读操作与写操作在不同的线程中执行时,我们无法确保执行读操作的线程能实时地看到其他线程写入的值,有时甚至是根本不可能的事情。
为了解决这个问题我们可以通过同步来保证对象被安全地发布。除此之外我们也可以使用一种更加轻量级的 volatile 变量。
TCP、UDP 协议的区别
UDP 在传送数据之前不需要先建立连接,远程主机在收到 UDP 报文后,不需要给出任何确认。虽然 UDP 不提供可靠交付,但在某些情况下 UDP 确是一种最有效的工作方式(一般用于即时通信),比如: QQ 语音、 QQ 视频 、直播等等
TCP 提供面向连接的服务。在传送数据之前必须先建立连接,数据传送结束后要释放连接。 TCP 不提供广播或多播服务。由于 TCP 要提供可靠的,面向连接的运输服务(TCP的可靠体现在TCP在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制,在数据传完后,还会断开连接用来节约系统资源),一系列操作难以避免增加了许多开销,如确认,流量控制,计时器以及连接管理等。这不仅使协议数据单元的首部增大很多,还要占用许多处理机资源。TCP 一般用于文件传输、发送和接收邮件、远程登录等场景。
在浏览器中输入url地址 ->> 显示主页的过程
总体来说分为以下几个过程:
TCP 三次握手和四次挥手(面试常客)
客户端 –发送带有 SYN 标志的数据包– 一次握手–服务端
服务端 –发送带有 SYN/ACK 标志的数据包– 二次握手–客户端
客户端 –发送带有带有 ACK 标志的数据包– 三次握手–服务端
为什么要三次握手?
三次握手的目的是建立可靠的通信信道,而三次握手最主要的目的就是双方确认自己与对方的发送与接收是正常的。
第一次握手:Client 什么都不能确认;
Server 确认了对方发送正常
第二次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;
Server 确认了:自己接收正常,对方发送正常
第三次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;
Server 确认了:自己发送、接收正常,对方发送接收正常所以三次握手就能确认双发收发功能都正
常,缺一不可。
为什么要传回 SYN
接收端传回发送端所发送的 SYN 是为了告诉发送端,我接收到的信息确实就是你所发送的信号了。
SYN 是 TCP/IP 建立连接时使用的握手信号。在客户机和服务器之间建立正常的 TCP 网络连接时,客户机首先发出一个 SYN 消息,服务器使用 SYN-ACK 应答表示接收到了这个消息,最后客户机再以ACK(Acknowledgement[汉译:确认字符 ,在数据通信传输中,接收站发给发送站的一种传输控制字符。它表示确认发来的数据已经接受无误。 ])消息响应。这样在客户机和服务器之间才能建立起可靠的TCP连接,数据才可以在客户机和服务器之间传递。
传了 SYN,为啥还要传 ACK
双方通信无误必须是两者互相发送信息都无误。传了 SYN,证明发送方到接收方的通道没有问题,但是接收方到发送方的通道还需要 ACK 信号来进行验证。
什么时候需要使用到4次握手?
断开一个 TCP 连接需要“四次挥手”:
客户端-发送一个 FIN,用来关闭客户端到服务器的数据传送
服务器-收到这个 FIN,它发回一 个 ACK,确认序号为收到的序号加1 。和 SYN 一样,一个 FIN 将占用一个序号
服务器-关闭与客户端的连接,发送一个FIN给客户端
客户端-发回 ACK 报文确认,并将确认序号设置为收到序号加1
为什么要四次挥手
任何一方都可以在数据传送结束后发出连接释放的通知,待对方确认后进入半关闭状态。当另一方也没有数据再发送的时候,则发出连接释放通知,对方确认后就完全关闭了TCP连接。
写出 java 5 种运行时异常
NullPointerException - 空指针引用异常
ClassCastException - 类型强制转换异常。
IllegalArgumentException - 传递非法参数异常。
ArithmeticException - 算术运算异常
IndexOutOfBoundsException - 下标越界异常
NumberFormatException - 数字格式异常
final、finally、finalize 区别
final 用于修饰类、成员变量和成员方法。final 修饰的类,不能被继承(String、StrngBuilder、 StringBuffer、Math,不可变类),其中所有的方法都不能被重写,所有不能同时用 abstract 和 final 修饰(abstract 修饰的是抽象类,抽象类是用于被子类继承的,和 final 起相反的作用);final 修饰的方法不能被重写,但是子类可以用父类中 final 修饰的方法;final 修饰的成员变量是不可变的,如果成员变量是基本数据类型,初始化之后成员变量的值不能被改变,如果成员变量是引用类型,那么它只能指向初始化时指向的那个
finally 是在异常处理时提供 finally 块来执行任何清除操作。不管有没有异常被抛出、捕获都会被执 行。try 块中的内容是在无异常时执行到结束。catch 块中的内容,是在 try 块内容发生 catch 所声明的异常时,跳转到 catch 块中执行。finally 块则是无论异常是否发生都会执行 finally 块的内容,所以在代码逻辑中有需要无论发生什么都必须执行的代码,可以放在 finally 块中。
finalize:Object 类中定义的方法,Java 中允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在销毁对象时调用的,通过重写 finalize() 方法可以整理系统资源或者执行其他清理工作。
Finally 一定会被执行吗?
至少有两种情况下 finally 语句是不会被执行的:
(1)try 语句没有被执行到,如在 try 语句之前就返回了,这样 finally 语句就不会执行,这也说明了 finally 语句被执行的必要而非充分条件是:相应的 try 语句一定被执行到。
(2)在 try 块中有 System.exit(0);这样的语句,System.exit(0);是终止 Java 虚拟机 JVM 的,连 JVM 都停止了,所有都结束了,当然 finally 语句也不会被执行
什么是 java 序列化,如何实现 java 序列化?
序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题。
序列化的实现 :将需要被序列化的类实现Serializable接口 ,该接口没有需要实现的方法只是为了标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个 ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream 对象的 writeObject(Object obj)方法就可以将参数为 obj 的对象写出(即保存其状态),要恢复的话则用输入流.
Object 中有哪些方法
(1)protected Object clone()—>创建并返回此对象的一个副本。
(2)boolean equals(Object obj)—>指示某个其他对象是否与此对象“相等”。
(3)protected void finalize()—>当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。
(4)Class extendsObject> getClass()—>返回一个对象的运行时类。
(5)int hashCode()—>返回该对象的哈希码值。
(6)void notify()—>唤醒在此对象监视器上等待的单个线程。
(7)void notifyAll()—>唤醒在此对象监视器上等待的所有线程。
(8)String toString()—>返回该对象的字符串表示。
(9)void wait()—>导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。
void wait(long timeout)—>导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll()方法,或者超过指定的时间量。
void wait(long timeout, int nanos)—>导致当前的线程等待,直到其他线程调用此对象的 notify()
产生死锁的基本条件
产生死锁的原因:
(1) 系统资源不足。
(2) 进程运行推进的顺序不合适。
(3) 资源分配不当等。
产生死锁的四个必要条件:
(1) 互斥条件:一个资源每次只能被一个进程使用。
(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。
死锁的解除与预防:
理解了死锁的原因,尤其是产生死锁的四个必要条件,就可以最大可能地避免、预防和解除死锁。所以,在系统设计、进程调度等方面要考虑如何不让这四个必要条件成立,确定资源的合理分配算法,避免进程永久占据系统资源。此外,也要防止进程在处于等待状态的情况下占用资源。因此,对资源的分配要给予合理的规划。
什么是线程池,如何使用?
线程池就是事先将多个线程对象放到一个容器中,当使用的时候就不用 new 线程而是直接去池中拿线程即可,节省了开辟子线程的时间,提高的代码执行效率。
在 JDK 的 java.util.concurrent.Executors 中提供了生成多种线程池的静态方法。
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(4);
ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(4);
ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
然后调用他们的 execute 方法即可。
优点:
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
Java 自带有哪几种线程池?
(1)newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。这种类型的线程池特点是:工作线程的创建数量几乎没有限制(其实也有限制的,数目为 Interger. MAX_VALUE), 这样可灵活的往线程池中添加线程。
如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为 1 分钟),则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。
在使用 CachedThreadPool 时,一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统瘫痪。
(2)newFixedThreadPool
创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。FixedThreadPool 是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是,在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源。
(3)newSingleThreadExecutor
创建一个单线程化的 Executor,即只创建唯一的工作者线程来执行任务,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。如果这个线程异常结束,会有另一个取代它,保证顺序执行。单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。
(4)newScheduleThreadPool
创建一个定长的线程池,而且支持定时的以及周期性的任务执行。例如延迟 3 秒执行。
JVM
JVM 内存分哪几个区,每个区的作用是什么?
(1)方法区:
a. 有时候也成为永久代,在该区内很少发生垃圾回收,但是并不代表不发生 GC,在这里进行的 GC 主要是对方法区里的常量池和对类型的卸载
b. 方法区主要用来存储已被虚拟机加载的类的信息、常量、静态变量和即时编译器编译后的代码等数据。
c. 该区域是被线程共享的。
d. 方法区里有一个运行时常量池,用于存放静态编译产生的字面量和符号引用。该常量池具有动态性,也就是说常量并不一定是编译时确定,运行时生成的常量也会存在这个常量池中。
jdk1.7以前除了hotspot,其他JVM厂商早就没有永久区了
jdk1.8之后hotspot也没有永久区了,取而代之的是元空间(MetaSpace)
元空间(MetaSpace)相较于永久区最大的不同,使用的是物理内存,垃圾回收机制在元空间快满的时候才会使用垃圾回收器,类的加载全都是在元空间
随着动态类加载的情况越来越多,这块内存变得不太可控,如果设置小了,系统运行过程中就容易出现内存溢出,设置大了又浪费内存
(2)虚拟机栈:
a. 虚拟机栈也就是我们平常所称的栈内存,它为 java 方法服务,每个方法在执行的时候都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接和方法出口等信息。
b. 虚拟机栈是线程私有的,它的生命周期与线程相同。
c. 局部变量表里存储的是基本数据类型、returnAddress 类型(指向一条字节码指令的地址)和对象引用,这个对象引用有可能是指向对象起始地址的一个指针,也有可能是代表对象的句柄或者与对象相关联的位置。局部变量所需的内存空间在编译期间确定
d. 操作数栈的作用主要用来存储运算结果以及运算的操作数,它不同于局部变量表通过索引来访问,而是压栈和出栈的方式
e. 每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态链接,动态链接就是将常量池中的符号引用在运行期转化为直接引用。
(3)本地方法栈:
本地方法栈和虚拟机栈类似,只不过本地方法栈为 Native 方法服务。
(4)堆:
java 堆是所有线程共享的一块内存,在虚拟机启动时创建,几乎所有的对象实例都在这里创建,因此该区域经常发生垃圾回收操作。
(5)程序计数器:
内存空间小,字节码解释器工作时通过改变这个计数值可以选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理和线程恢复等功能都需要依赖这个计数器完成。该内存区域是唯一一个 java 虚拟机规范没有规定任何 OOM 情况的区域。
heap(堆) 和 stack(栈) 有什么区别
(1)申请方式
stack:由系统自动分配。例如,声明在函数中一个局部变量 int b; 系统自动在栈中为 b 开辟空间
heap:需要程序员自己申请,并指明大小,在 c 中 malloc 函数,对于 Java 需要手动 new Object()的形式开辟
(2)申请后系统的响应
stack:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。
heap:操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
(3)申请大小的限制
stack:栈是向低地址扩展的数据结构,是一块连续的内存的区域。也就是说栈顶的地址和栈的最大容量是系统预先规定好的,在 WINDOWS 下,栈的大小是 2M(也有的说是 1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示 overflow。因此,能从栈获得的空间较小。
heap:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的, 自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见, 堆获得的空间比较灵活,也比较大。
(4)申请效率的比较
stack:由系统自动分配,速度较快。但程序员是无法控制的。
heap:由 new 分配的内存,一般速度比较慢,容易产生内存碎片,不过用起来最方便。
(5)heap 和 stack 中的存储内容
stack:在函数调用时,第一个进栈的是主函数后的下一条指令(函数调用语句的下一条可执行语句)的地址, 然后是函数的各个参数,在大多数的 C 编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。
当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。
heap:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。
java 类加载过程?
(1)加载
a. 通过一个类的全限定名获取该类的二进制流。
b. 将该二进制流中的静态存储结构转化为方法去运行时数据结构。
c. 在内存中生成该类的 Class 对象,作为类的数据访问入口。
(2)验证
验证的目的是为了确保 Class 文件的字节流中的信息不会危害到虚拟机.在该阶段主要完成以下四种
验证:
a. 文件格式验证:验证字节流是否符合 Class 文件的规范,如主次版本号是否在当前虚拟机范围内,常量池中的常量是否有不被支持的类型.
b. 元数据验证:对字节码描述的信息进行语义分析,如这个类是否有父类,是否集成了不被继承的类等。
c. 字节码验证:通过验证数据流和控制流的分析,确定程序语义是否正确,主要针对方法体的验证。如:方法中的类型转换是否正确,跳转指令是否正确等。
d. 符号引用验证:确保解析动作能正确执行。最后为类的静态变量分配内存并将其初始化为默认值
(3)解析
主要完成符号引用到直接引用的转换动作。解析动作并不一定在初始化动作完成之前,也有可能在初始化之后。
(4)初始化
初始化时类加载的最后一步,前面的类加载过程,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的 Java 程序代码。
什么是类加载器,类加载器有哪些?
通过类的权限定名获取该类的二进制字节流的代码块叫做类加载器。
主要有一下四种类加载器:
(1)启动类加载器(Bootstrap ClassLoader)用来加载 java 核心类库,无法被 java 程序直接引用。
(2)扩展类加载器(extensions Classloader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
(3)系统类加载器(system Classloader)也叫应用类加载器:它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。
(4)用户自定义类加载器,通过继承 java.lang.ClassLoader 类的方式实现。
java 中垃圾收集的方法有哪些?
1)引用计数法应用于:微软的 COM/ActionScrip3/Python 等
a) 如果对象没有被引用,就会被回收,缺点:需要维护一个引用计算器
2)复制算法应用于:新生代
a) 效率高,缺点:需要内存容量大,比较耗内存
3)标记清除
a) 效率比较低,会产生碎片。
4)标记压缩
a) 效率低速度慢,需要移动对象,但不会产生碎片。
5)标记清除压缩的集合,多次 GC 后才 Compact
a) 适用于占空间大刷新次数少的老年代0,是 标记清除和标记压缩 的集合体
如何判断一个对象是否存活?(或者 GC 对象的判定方法)
判断一个对象是否存活有两种方法:
(1)引用计数法
所谓引用计数法就是给每一个对象设置一个引用计数器,每当有一个地方引用这个对象时,就将计数器加一,引用失效时,计数器就减一。当一个对象的引用计数器为零时,说明此对象没有被引用,也就是“死对象”,将会被垃圾回收.引用计数法有一个缺陷就是无法解决循环引用问题,也就是说当对象 A 引用对象 B,对象 B 又引用者对象 A,那么此时 A,B 对象的引用计数器都不为零,也就造成无法完成垃圾回收,所以主流的虚拟机都没有采用这种算法。
(2)可达性算法(引用链法)
该算法的基本思路就是通过一些被称为引用链(GC Roots)的对象作为起点,从这些节点开始向下搜索,搜索走过的路径被称为(Reference Chain),当一个对象到 GC Roots 没有任何引用链相连时(即从 GC Roots 节点到该节点不可达),则证明该对象是不可用的。
在 java 中可以作为 GC Roots 的对象有以下几种:虚拟机栈中引用的对象、方法区类静态属性引用的对象、方法区常量池引用的对象、本地方法栈 JNI引用的对象。
简述 java 内存分配与回收策略以及 Minor GC 和 Major GC(full GC)
内存分配:
(1)栈区:栈分为 java 虚拟机栈和本地方法栈
(2)堆区:堆被所有线程共享区域,在虚拟机启动时创建,唯一目的存放对象实例。堆区是 gc 的主要区域,通常情况下分为两个区块新生代和老年代。
更细一点年轻代又分为 Eden 区,主要放新创建对象,From survivor 和 To survivor 保存 gc 后幸存下的对象,默认情况下各自占比 8:1:1。
(3)方法区:被所有线程共享区域,用于存放已被虚拟机加载的类信息,常量,静态变量等数据。被 Java 虚拟机描述为堆的一个逻辑部分。习惯是也叫它永久代(permanment generation)
(4)程序计数器:当前线程所执行的行号指示器。通过改变计数器的值来确定下一条指令,比如循环,分支,跳转,异常处理,线程恢复等都是依赖计数器来完成。线程私有的。
回收策略以及 Minor GC 和 Major GC:
(1)对象优先在堆的 Eden 区分配。
(2)大对象直接进入老年代。
(3)长期存活的对象将直接进入老年代。
当 Eden 区没有足够的空间进行分配时,虚拟机会执行一次 Minor GC.Minor GC 通常发生在新生代的 Eden 区,在这个区的对象生存期短,往往发生 GC的频率较高,回收速度比较快;Full Gc/Major GC 发生在老年代,一般情况下,触发老年代 GC 的时候不会触发 Minor GC,但是通过配置在 Full GC之前进行一次 Minor GC 这样可以加快老年代的回收速度。
什么是分布式垃圾回收(DGC)?它是如何工作的?
DGC叫做分布式垃圾回收。
RMI 使用DGC来做自动垃圾回收。因为RMI包含了跨虚拟机的远程对象的引用,垃圾回收是很困难的。DGC使用引用计数算法来给远程对象提供自动内存管理。
类加载器双亲委派模型机制?
当一个类收到了类加载请求时,不会自己先去加载这个类,而是将其委派给父类,由父类去加载,如果此时父类不能加载,反馈给子类,由子类去完成类的加载。
设计模式
你所知道的设计模式有哪些
Java 中一般认为有 23 种设计模式
总体来说设计模式分为三大类:
创建型模式,共 5 种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共 7 种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共 11 种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式
工厂设计模式(Factory)
什么是工厂设计模式?
工厂设计模式,就是用来生产对象的,在 java 中,万物皆对象,这些对象都需要创建,如果创建的时候直接 new 该对象,就会对该对象耦合严重,假如我们要更换对象,所有 new 对象的地方都需要修改一遍,这显然违背了软件设计的开闭原则,如果我们使用工厂来生产对象,我们就只和工厂打交道就可以了,彻底和对象解耦,如果要更换对象,直接在工厂里更换该对象即可,达到了与对象解耦的目的;所以说,工厂模式最大的优点就是:解耦
1、对于简单工厂和工厂方法来说,两者的使用方式实际上是一样的,如果对于产品的分类和名称是确定的,数量是相对固定的,推荐使用简单工厂模式;
2、抽象工厂用来解决相对复杂的问题,适用于一系列、大批量的对象生产。
代理模式(Proxy)
什么是代理模式?
代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。
举个例子来说明:假如说我现在想买一辆二手车,虽然我可以自己去找车源,做质量检测等一系列的车辆过户流程,但是这确实太浪费我得时间和精力了。我只是想买一辆车而已为什么我还要额外做这么多事呢?于是我就通过中介公司来买车,他们来给我找车源,帮我办理车辆过户流程,我只是负责选择自己喜欢的车,然后付钱就可以了
为什么要用代理模式?
中介隔离作用:
在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。
开闭原则,增加功能:
代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是同过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要修改已经封装好的委托类。
有哪几种代理模式?
如果按照代理创建的时期来进行分类的话,可以分为两种:静态代理、动态代理。
⚫ 静态代理是由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行之前,代理类.class 文件就已经被创建了。
⚫ 动态代理是在程序运行时通过反射机制动态创建的。
静态代理总结:
优点:可以做到在符合开闭原则的情况下对目标对象进行功能扩展。
缺点:我们得为每一个服务创建代理类,工作量太大,不易管理。同时接口一旦发生改变,代理类也得相应修改。
JDK 动态代理总结:
优点:相对于静态代理,动态代理大大减少了开发任务,同时减少了对业务接口的依赖,降低了耦合度。
缺点:Proxy 是所有动态生成的代理的共同的父类,因此服务类必须是接口的形式,不能是普通类的形式,因为 Java 无法实现多继承。
CGLib 动态代理(CGLib Proxy)
JDK 实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要 CGLib 了。CGLib 采用了底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。但因为采用的是继承,所以不能对 final 修饰的类进行代理。JDK 动态代理与 CGLib 动态代理均是实现 Spring AOP 的基础。
CGLib 动态代理和JDK动态代理如何选择?
CGLib 创建的动态代理对象比 JDK 创建的动态代理对象的性能更高,但是 CGLIB 创建代理对象时所花费的时间却比 JDK 多得多。所以对于单例的对象,因为无需频繁创建对象,用 CGLIB 合适,反之使用 JDK 方式要更为合适一些。同时由于 CGLib 由于是采用动态创建子类的方法,对于 final 修饰的方法无法进行代理。
简述动态代理的原理?常用的动态代理的实现方式?
动态代理的原理: 使用一个代理将对象包装起来,然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理过滤。代理对象决定是否以及何时将方法调用转到原始对象上动态代理的方式(换句话说就是过滤之后交由原始对象确定)
基于接口实现动态代理: JDK 动态代理
基于继承实现动态代理: Cglib、Javassist 动态代理
数据库
当MySQL单表记录数过大时,数据库的CRUD性能会明显下降,一些常见的优化措施如下:
当MySQL单表记录数过大时,数据库的CRUD性能会明显下降,一些常见的优化措施如下:
限定数据的范围: 禁止不带任何限制数据范围条件的查询语句。
读/写分离: 经典的数据库拆分方案,主库负责写,从库负责读;
垂直分区: 根据数据库里面数据表的相关性进行拆分。 例如,用户表中既有用户的登录信息又有用户的基本信息,可以将用户表拆分成两个单独的表,甚至放到单独的库做分库。
垂直拆分的优点: 查询时减少数据量和减少I/O次数。此外,垂直分区可以简化表的结构,易于维护。
垂直拆分的缺点: 主键会出现冗余,需要管理冗余列,并会引起Join操作,可以在应用层进行Join来解决。此外,垂直分区会让事务变得更加复杂;
水平分区: 保持数据表结构不变,通过某种策略存储数据分片。这样每一片数据分散到不同的表或者库中,达到了分布式的目的。 水平拆分是指数据表行的拆分,表的行数超过200万行时,就会变慢,这时可以把一张的表的数据拆成多张表来存放。
水平拆分可以支持非常大的数据量。分表仅仅是解决了单一表数据过大的问题,但由于表的数据还是在同一台机器上,其实对于提升MySQL并发能力没有什么意义,所以水平拆分最好分库 ,应用端改造2少,但分片事务难以解决 ,跨界点Join性能较差,逻辑复杂。
尽量不要对数据进行分片,因为拆分会带来逻辑、部署、运维的各种复杂度 ,一般的数据表在优化得当的情况下支撑千万以下的数据量是没有太大问题的。如果实在要分片,尽量选择客户端分片架构,这样可以减少一次和中间件的网络I/O。
数据库分片的两种常见方案:
客户端代理: 分片逻辑在应用端,封装在jar包中,通过修改或者封装JDBC层来实现。 当当网的 Sharding
JDBC 、阿里的TDDL是两种比较常用的实现。
中间件代理: 在应用和数据中间加了一个代理层。分片逻辑统一维护在中间件服务中。 我们现在谈的 Mycat
、360的Atlas、网易的DDB等等都是这种架构的实现。
关系数据库中连接池的机制是什么?
前提:为数据库连接建立一个缓冲池。
(1)从连接池获取或创建可用连接
(2)使用完毕之后,把连接返回给连接池
(3)在系统关闭前,断开所有连接并释放连接占用的系统资源
(4)能够处理无效连接,限制连接池中的连接总数不低于或者不超过某个限定值。
概念:
最小连接数是连接池一直保持的数据连接。如果应用程序对数据库连接的使用量不大,将会有大量的数据库连接资源被浪费掉。
最大连接数是连接池能申请的最大连接数。如果数据连接请求超过此数,后面的数据连接请求将被加入到等待队列中,这会影响之后的数据库操作。
数据库池连接数量一直保持一个不少于最小连接数的数量,当数量不够时,数据库会创建一些连接,直到一个最大连接数,之后连接数据库就会等待。
SQL 的 select 语句完整的执行顺序
SQL Select 语句完整的执行顺序:
(1)from 子句组装来自不同数据源的数据;
(2)where 子句基于指定的条件对记录行进行筛选;
(3)group by 子句将数据划分为多个分组;
(4)使用聚集函数进行计算;
(5)使用 having 子句筛选分组;
(6)计算所有的表达式;
(7)select 的字段;
(8)使用 order by 对结果集进行排序。
数据库三大范式
第一范式(1NF)是指数据表中的每个字段必须是不可拆分的最小单元,也就是确保每一列的原子性。
第二范式(2NF)是指满足 1NF 后,要求表中的所有列,都必须依赖于主键,而不能有任何一列与主键没有关系,也就是说一个表只描述一件事情。
第三范式(3NF)是指必须先满足第二范式(2NF),另外要求表中的每一列只与主键直接相关而不是间接相关。
数据库五大约束
1.主键约束(Primay Key Coustraint) 唯一性,非空性;
2.唯一约束 (Unique Counstraint)唯一性,可以空,但只能有一个;
3.默认约束 (Default Counstraint) 该数据的默认值;
4.外键约束 (Foreign Key Counstraint) 需要建立两表间的关系;
5.非空约束(Not Null Counstraint):设置非空约束,该字段不能为空。
分页
Mysql: select * from emp limit 0 ,5;
Oracle: select e.* from (select emp.* ,rownum rn from emp) e where rn between 1 and 5;
Mysql与 Oracle 相比,Mysql 有什么优势?
Mysql 是开源软件,随时可用,无需付费。
Mysql 是便携式的带有命令提示符的 GUI。
使用 Mysql 查询浏览器支持管理
存储引擎是什么?Mysql 中使用什么存储引擎?
存储引擎称为表类型,数据使用各种技术存储在文件中。
技术涉及:
Storage mechanism(存储机制) , Locking levels(锁级别) , Indexing(索引) , Capabilities and functions(能力和功能)
MySQL的主从复制
主从复制(也称 AB 复制)允许将来自一个MySQL数据库服务器(主服务器)的数据复制到一个或多个MySQL数据库服务器(从服务器)
主从复制的有好处
MySQL中主从复制的优点包括:
一主一从复制原理
一主多从
如果一主多从的话,这时主库既要负责写又要负责为几个从库提供二进制日志。此时可以稍做调整,将二进制日志只给某一从,这一从再开启二进制日志并将自己的二进制日志再发给其它从。或者是干脆这个从不记录只负责将二进制日志转发给其它从,这样架构起来性能可能要好得多,而且数据之间的延时应该也稍微要好一些。
MySQL主从复制一致性
数据同步一致性解决方案:半同步复制
MySQL的Replication默认是一个异步复制的过程,从MySQL5.5开始,MySQL以插件的形式支持半同步复制,我先谈下异步复制,这样可以更好的理解半同步复制。
1)异步复制
MySQL默认的复制是异步的,主库在执行完客户端提交的事务后会立即将结果返给给客户端,并不关心从库是否已经接收并处理,这样就会有一个问题,主如果挂掉了,此时主上已经提交的事务可能并没有传到从库上。
2)半同步复制
介于异步复制和全同步复制之间,主库在执行完客户端提交的事务后不是立刻返回给客户端,而是等待至少一个从库接收到并写到relay log中才返回给客户端。相对于异步复制,半同步复制提高了数据的安全性,同时它也造成了一定程度的延迟,这个延迟最少是一个TCP/IP往返的时间。所以,半同步复制最好在低延时的网络中使用。
MySQL主从复制搭建步骤(Docker)
主服务器搭建
镜像拉取,主机MySQL容器创建,修改外部映射文件my.cnf,赋予从机权限
从服务器搭建
启动 从机,每一台从机创建用户赋权,建立从机之间的关系
如果读写同时操作如何保证读写的数据一致性?
一般情况下,读写不会同时使用,如果需要做这样操作的话,那么在数据库中间件(Mycat)他有一种策略去解决这个问题,也就是说他去读从库的时候会先判断一下我们当前环境是否存在主从复制发生,如果有那么他会把这次的请求数据直接交给主库完成,不去读从库,把他重定向交给主库处理,那么这个时候他是能保证数据的一致性的!
oracel 与 mysql 的区别
A).自动增长的数据类型处理
MYSQL 有自动增长的数据类型,插入记录时不用操作此字段,会自动获得数据值。ORACLE 没有自动增长的数据类型,需要建立一个自动增长的序列号,插入记录时要把序列号的下一个值赋于此字段。
B).单引号的处理
MYSQL 里可以用双引号包起字符串,ORACLE 里只可以用单引号包起字符串。在插入和修改字符串前必须做单引号的替换:把所有出现的一个单引号替换成两个单引号。
C).空字符的处理
MYSQL 的非空字段也有空的内容,ORACLE 里定义了非空字段就不容许有空的内容。按 MYSQL 的 NOT NULL 来
定义 ORACLE 表结构, 导数据的时候会产生错误。因此导数据时要对空字符进行判断,如果为 NULL 或空字符,需
要把它改成一个空格的字符串。
D).日期字段的处理
MYSQL 日期字段分 DATE 和 TIME 两种,ORACLE 日期字段只有 DATE,包含年月日时分秒信息用当前数据库的
系统时间为 SYSDATE, 精确到秒
E).并发性
mysql 以表级锁为主,对资源锁定的粒度很大,如果一个 session 对一个表加锁时间过长,会让其他 session 无法
更新此表中的数据。虽然 InnoDB 引擎的表可以用行级锁,但这个行级锁的机制依赖于表的索引,如果表没有索引或者 sql 语句没有使用索引,那么仍然使用表级锁。
oracle 使用行级锁,对资源锁定的粒度要小很多,只是锁定 sql 需要的资源,并且加锁是在数据库中的数据行上不依赖与索引。所以 oracle 对并发性的支持要好很多
F).提交方式
oracle 默认不自动提交,需要用户手动提交。mysql 默认是自动提交。
数据库优化
(1)Sql 语句优化
1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
2.应尽量避免在 where 子句中对字段进行 null 值判断。
3.应尽量避免在 where 子句中使用!=或<>操作符, MySQL 只有对以下操作符才使用索引:<,<=,=,>,>=
5.in 和 not in 也要慎用,否则会导致全表扫描,对于连续的数值,能用 between 就不要用 in
6.应尽量避免在 where 子句中对字段进行表达式操作,应尽量避免在 where 子句中对字段进行函数操作
7.尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型
(2)索引优化
1.数据量超过百万级别的表应该有索引;
2.经常与其他表进行连接的表,在连接字段上应该建立索引;
3.经常出现在 Where 子句 order by,group by 中的字段,特别是大表的字段,应该建立索引;
4.索引应该建在小字段上,对于大的文本字段甚至超长字段,不要建索引;
索引
(1)什么是索引?
索引是对数据库表中一列或多列的值进行排序的一种结构
(2)索引的优点
①建立索引的列可以保证行的唯一性,生成唯一的 rowId
②建立索引可以有效缩短数据的检索时间
③建立索引可以加快表与表之间的连接
④为用来排序或者是分组的字段添加索引可以加快分组和排序顺序
(3)索引的缺点
①创建索引和维护索引需要时间成本,这个成本随着数据量的增加而加大
②创建索引和维护索引需要空间成本,每一条索引都要占据数据库的物理存储空间,数据量越大,占用空间也越大(数据表占据的是数据库的数据空间)
③会降低表的增删改的效率,因为每次增删改索引需要进行动态维护,导致时间变长
(4)索引什么时候会失效
1.like模糊查询以%开头索引失效
2.不在索引列上做任何操作(计算、函数、类型类型转换),否则会导致索引失效而转向全表扫描
2.字符串不加单引号索引失效
4.or查询每列都要有索引,不然索引失效
5,mysql在使用!= 或者 < > 的时候无法使用索引会导致全表扫描
6,联合索引要遵循最左匹配原则
(5)什么样的列需要建立索引
1.在经常使用在 WHERE 子句中的列上面创建索引,加快条件的判断速度。
2.在经常需要排序的列上创建索引,因为索引已经排序,这样查询可以利用索引的排序,加快排序查询 时间
3.在经常用在连接的列上,这些列主要是一些外键,可以加快连接的速度.
(6)什么样的列不需要建立索引
1、频繁更新的字段不适合创建索引,因为每次更新不单单是更新记录,还会更新索引,保存索引文件;
2、表记录太少,不需要创建索引;
3、经常增删改的表;
4、数据重复且分布平均的字段,因此为经常查询的和经常排序的字段建立索引。注意某些数据包含大量重复数据,因此他建立索引就没有太大的效果,例如性别字段,只有男女,不适合建立索引。
(7)常见索引
Mysql 常见索引有:主键索引、唯一索引、普通索引、全文索引、组合索引
普通索引:最基本的索引,没有任何限制
唯一索引:与"普通索引"类似,不同的就是:索引列的值必须唯一,但允许有空值。
主键索引:它 是一种特殊的唯一索引,不允许有空值。
全文索引:仅可用于 MyISAM 表,针对较大的数据,生成全文索引很耗时好空间。
组合索引:用多个列组成的索引。
b-tree 和 b+tree 的区别
(1)非叶子节点只存储键值信息
(2)所有叶子节点之间都有一个链指针
(3)数据记录都存放在叶子节点中
存储过程 存储函数
存储过程/函数是什么?
存储过程和存储函数是事先经过编译并存储在数据库中的一段 sql 语句的集合
存储过程与存储函数的区别?
1.存储函数必须有返回值,而存储过程没有返回值。
2.存储函数的参数类型只能是 in ,存储过程的参数可以是 in ,out 类型
(1)优点
• 运行速度:对于复杂的业务逻辑,因为在存储过程创建的时候,数据库已经对其进行了一次解析和优化。存储过程一旦执行,在内存中就会保留一份这个存储过程,这样下次再执行同样的存储过程时,可以从内存中直接调用,所以执行速度会比普通 sql 快。
•减少网络传输:存储过程直接就在数据库服务器上跑,所有的数据访问都在数据库服务器内部进行,不需要传输数据到其它服务器,所以会减少一定的网络传输。但是在存储过程中没有多次数据交互,那么实际上网络传输量和直接 sql 是一样的。而且我们的应用服务器通常与数据库是在同一内网,大数据的访问的瓶颈会是硬盘的速度,而不是网速。
(2)缺点
开发调试复杂,由于 IDE 的问题,存储过程的开发调试要比一般程序困难
没办法应用缓存。虽然有全局临时表之类的方法可以做缓存,但同样加重了数据库的负担。如果缓存并发严
重,经常要加锁,那效率实在堪忧
不支持群集,数据库服务器无法水平扩展,或者数据库的切割(水平或垂直切割)。数据库切割之后,存储
过程并不清楚数据存储在哪个数据库中
移植性差
事务的四大特性
原子性(Atomicity)
原子性是指事务是一个不可分割的工作单位,事务中的操作要么都提交,要么都回滚。 比如:转账转过去的加和转时候的减必须一次发生
一致性(Consistency)
事务必须使数据库从一个一致性状态变换到另外一个一致性状态。 比如:转账时双方的总数在转账的时候保持一致
隔离性(Isolation)
事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。 比如:多个用户操纵,防止数据干扰,就要为每个客户开启一个自己的事务;
持久性(Durability)
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响
事务的并发问题:
⚫ 脏读:事务 A 读取了事务 B 更新的数据,然后 B 回滚操作,那么 A 读取到的数据是脏数据
⚫ 不可重复读:事务 A 多次读取同一数据,事务 B 在事务 A 多次读取的过程中,对数据作了更新并提交,导致事务 A 多次读取同一数据时,结果不一致
⚫ 幻读:系统管理员 A 将数据库中所有员工等级从具体等级改为 ABCDE 等级,但是系统管理员 B 就在这个时候插入了一条具体等级的记录,当系统管理员 A 改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。
小结:
不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件行,解决幻读需要锁表
MySQL 事务隔离级别:
事务隔离级别 脏读 不可重复读 幻读
读未提交(read-uncommitted) 是 是 是
不可重复读(read-committed) 否 是 是
可重复读(repeatable-read) 否 否 是
串行化(serializable) 否 否 否
事务的传播机制
REQUIRED(默认):支持使用当前事务,如果当前事务不存在,创建一个新事务。
SUPPORTS:支持使用当前事务,如果当前事务不存在,则不使用事务。
MANDATORY:中文翻译为强制,支持使用当前事务,如果当前事务不存在,则抛出 Exception。
REQUIRES_NEW:创建一个新事务,如果当前事务存在,把当前事务挂起。
NOT_SUPPORTED:无事务执行,如果当前事务存在,把当前事务挂起。
NEVER:无事务执行,如果当前有事务则抛出 Exception。
NESTED:嵌套事务,如果当前事务存在,那么在嵌套的事务中执行。如果当前事务不存在,则表现跟 REQUIRED一样
MyISAM 和 InnoDB 两者之间区别
MyISAM:每个 MyISAM 在磁盘上存储成三个文件。第一个文件的名字以表的名字开始,扩展名指出文件类型。.frm 文件存储表定义。数据文件的扩展名为.MYD (MYData)。索引文件的扩展名是.MYI (MYIndex)。
InnoDB:所有的表都保存在同一个数据文件中(也可能是多个文件,或者是独立的表空间文件),InnoDB 表的大小只受限于操作系统文件的大小,一般为 2GB。
MyISAM:强调的是性能,每次查询具有原子性,其执行数度比 InnoDB 类型更快,但是不提供事务支持。
InnoDB:提供事务支持事务,外键。 具有事务(commit)、回滚(rollback)功能
MyISAM:只支持表级锁,用户在操作 myisam 表时,select,update,delete,insert 语句都会给表自动加锁,如果加锁以后的表满足 insert 并发的情况下,可以在表的尾部插入新的数据。
InnoDB:支持事务和行级锁,是 innodb 的最大特色。行锁大幅度提高了多用户并发操作的新能。但是 InnoDB 的行锁,只是在 WHERE 的主键是有效的,非主键的 WHERE 都会锁全表的。
MyISAM 锁的粒度是表级,而 InnoDB 支持行级锁定。简单来说就是, InnoDB 支持数据行锁定,而 MyISAM 不支持行锁定,只支持锁定整个表。即 MyISAM 同一个表上的读锁和写锁是互斥的,MyISAM 并发读写时如果等待队列中既有读请求又有写请求,默认写请求的优先级高,即使读请求先到,所以 MyISAM 不适合于有大量查询和修改并存的情况,那样查询进程会长时间阻塞。因为 MyISAM 是锁表,所以某项读操作比较耗时会使其他写进程饿死。
MyISAM:支持(FULLTEXT 类型的)全文索引
InnoDB:不支持(FULLTEXT 类型的)全文索引
5)表主键
MyISAM:允许没有任何索引和主键的表存在,索引都是保存行的地址。
InnoDB:如果没有设定主键或者非空唯一索引,就会自动生成一个 6 字节的主键(用户不可见),数据是主索引的一部分,附加索引保存的是主索引的值。InnoDB 的主键范围更大,最大是 MyISAM 的 2 倍
6)外键
MyISAM:不支持
InnoDB:支持
MyISAM 管理非事务表。它提供高速存储和检索,以及全文搜索能力。如果应用中需要执行大量的 SELECT 查询,那么 MyISAM 是更好的选择。
InnoDB 用于事务处理应用程序,具有众多特性,包括 ACID 事务支持。如果应用中需要执行大量的INSERT 或 UPDATE 操作,则应该使用 InnoDB,这样可以提高多用户并发操作的性能。
你们公司有哪些数据库设计规范
(一)基础规范
1、表存储引擎必须使用 InnoD,表字符集默认使用 utf8,必要时候使utf8mb4
解读:
(1)通用,无乱码风险,汉字 3 字节,英文 1 字节
(2)utf8mb4 是 utf8 的超集,有存储 4 字节例如表情符号时,使用它
2、禁止使用存储过程,视图,触发器,Event
解读:
(1)对数据库性能影响较大,互联网业务,能让站点层和服务层干的事情,不要交到数据库层
(2)调试,排错,迁移都比较困难,扩展性较差
3、禁止在数据库中存储大文件,例如照片,可以将大文件存储在对象存储系统,数据库中存储路径
4、禁止在线上环境做数据库压力测试
5、测试,开发,线上数据库环境必须隔离
(二)命名规范
1、库名,表名,列名必须用小写,采用下划线分隔
解读:abc,Abc,ABC 都是给自己埋坑
2、库名,表名,列名必须见名知义,长度不要超过 32 字符
解读:tmp,wushan 谁知道这些库是干嘛的
3、库备份必须以 bak 为前缀,以日期为后缀
4、从库必须以-s 为后缀
5、备库必须以-ss 为后缀
(三)表设计规范
1、单实例表个数必须控制在 2000 个以内
2、单表分表个数必须控制在 1024 个以内
3、表必须有主键,推荐使用 UNSIGNED 整数为主键
潜在坑:删除无主键的表,如果是 row 模式的主从架构,从库会挂住
4、禁止使用外键,如果要保证完整性,应由应用程式实现
解读:外键使得表之间相互耦合,影响 update/delete 等 SQL 性能,有可能造成死锁,高并发情况下容易成为数据库瓶颈
5、建议将大字段,访问频度低的字段拆分到单独的表中存储,分离冷热数据
(四)列设计规范
1、根据业务区分使用 tinyint/int/bigint,分别会占用 1/4/8 字节
2、根据业务区分使用 char/varchar
解读:
(1)字段长度固定,或者长度近似的业务场景,适合使用 char,能够减少碎片,查询性能高
(2)字段长度相差较大,或者更新较少的业务场景,适合使用 varchar,能够减少空间
3、根据业务区分使用 datetime/timestamp
解读:前者占用 5 个字节,后者占用 4 个字节,存储年使用 YEAR,存储日期使用 DATE,存储时间使用 datetime
4、必须把字段定义为 NOT NULL 并设默认值
解读:
(1)NULL 的列使用索引,索引统计,值都更加复杂,MySQL 更难优化
(2)NULL 需要更多的存储空间
(3)NULL 只能采用 IS NULL 或者 IS NOT NULL,而在=/!=/in/not in 时有大坑
5、使用 INT UNSIGNED 存储 IPv4,不要用 char(15)
6、使用 varchar(20)存储手机号,不要使用整数
解读:
(1)牵扯到国家代号,可能出现+/-/()等字符,例如+86
(2)手机号不会用来做数学运算
(3)varchar 可以模糊查询,例如 like ‘138%’
7、使用 TINYINT 来代替 ENUM
解读:ENUM 增加新值要进行 DDL 操作
(五)索引规范
1、唯一索引使用 uniq_[字段名]来命名
2、非唯一索引使用 idx_[字段名]来命名
3、单张表索引数量建议控制在 5 个以内
解读:
(1)互联网高并发业务,太多索引会影响写性能
(2)生成执行计划时,如果索引太多,会降低性能,并可能导致 MySQL 选择不到最优索引
(3)异常复杂的查询需求,可以选择 ES 等更为适合的方式存储
4、组合索引字段数不建议超过 5 个
解读:如果 5 个字段还不能极大缩小 row 范围,八成是设计有问题
5、不建议在频繁更新的字段上建立索引
6、非必要不要进行 JOIN 查询,如果要进行 JOIN 查询,被 JOIN 的字段必须类型相同,并建立索引
解读:踩过因为 JOIN 字段类型不一致,而导致全表扫描的坑么?
7、理解组合索引最左前缀原则,避免重复建设索引,如果建立了(a,b,c),相当于建立了(a), (a,b), (a,b,c)
(六)SQL 规范
1、禁止使用 select *,只获取必要字段
解读:
(1)select *会增加 cpu/io/内存/带宽的消耗
(2)指定字段能有效利用索引覆盖
(3)指定字段查询,在表结构变更时,能保证对应用程序无影响
2、insert 必须指定字段,禁止使用 insert into T values()
解读:指定字段插入,在表结构变更时,能保证对应用程序无影响
3、隐式类型转换会使索引失效,导致全表扫描
4、禁止在 where 条件列使用函数或者表达式
解读:导致不能命中索引,全表扫描
5、禁止负向查询以及%开头的模糊查询
解读:导致不能命中索引,全表扫描
6、禁止大表 JOIN 和子查询
7、同一个字段上的 OR 必须改写问 IN,IN 的值必须少于 50 个
8、应用程序必须捕获 SQL 异常
解读:方便定位线上问题
说明:本规范适用于并发量大,数据量大的典型互联网业务,可直接参考。
JAVA WEB
HTTP协议
超文本传输协议(英文:HyperText Transfer Protocol,缩写:HTTP)是一种用于分布式、协作式和超媒体信息系统的应用层协议。HTTP是万维网的数据通信的基础。
Http 的长连接和短连接
HTTP 协议有 HTTP/1.0 版本和 HTTP/1.1 版本。HTTP1.1 默认保持长连接(HTTP persistent connection,也翻译为持久连接),数据传输完成了保持 TCP 连接不断开(不发 RST 包、不四次握手),等待在同域名下继续用这个通道传输数据;相反的就是短连接。
在 HTTP/1.0 中,默认使用的是短连接。也就是说,浏览器和服务器每进行一次 HTTP 操作,就建立一次连接,任务结束就中断连接。从 HTTP/1.1 起,默认使用的是长连接,用以保持连接特性。
http 常见的状态码有哪些?
200 OK 客户端请求成功
301 Moved Permanently (永久移除),请求的 URL 已移走。Response 中应该包含一个 Location URL, 说明资源现在所处的位置
302found 重定向
400 Bad Request 客户端请求有语法错误,不能被服务器所理解
401 Unauthorized 请求未经授权,这个状态代码必须和 WWW-Authenticate 报头域一起使用
403 Forbidden 服务器收到请求,但是拒绝提供服务
404 Not Found 请求资源不存在,eg:输入了错误的 URL
500 Internal Server Error 服务器发生不可预期的错误
503 Server Unavailable 服务器当前不能处理客户端的请求,一段时间后可能恢复正常
GET 和 POST 的区别?
(1)GET 请求的数据会附在 URL 之后(就是把数据放置在 HTTP 协议头中),以?分割 URL 和传输数据,参数之间以&相连,如:
login.action?name=zhagnsan&password=123456。
POST 把提交的数据则放置在是 HTTP 方法体中.
(2)GET 方式提交的数据最多只能是 1024 字节,理论上 POST 没有限制,可传较大量的数据。其实这样说是错误的,不准确的:“GET 方式提交的数据最多只能是 1024 字节",因为 GET 是通过 URL 提交数据,那么 GET 可提交的数据量就跟 URL 的长度有直接关系了。而实际上,URL 不存在参数上限的问题,HTTP 协议规范没有对 URL 长度进行限制。这个限制是特定的浏览器及服务器对它的限制。IE 对 URL 长度的限制是 2083 字节(2K+35)。对于其他浏览器,如 Netscape、FireFox 等,理论上没有长度限制,其限制取决于操作系统的支持。
(3)POST 的安全性要比 GET 的安全性高。这里安全的含义是真正的 Security 的含义,比如:通过 GET 提交数据,用户名和密码将明文出现在 URL 上,因为(1)登录页面有可能被浏览器缓存,(2)其他人查看浏览器的历史纪录,那么别人就可以拿到你的账号和密码了,除此之外,使用 GET 提交数据还可能会造成 Cross-site request forgery 攻击。
Get 是向服务器发索取数据的一种请求,而 Post 是向服务器提交数据的一种请求,在 FORM(表单)中,Method默认为"GET",实质上,GET 和 POST 只是发送机制不同,并不是一个取一个发!
在单点登录中,如果 cookie 被禁用了怎么办?
单点登录的原理是后端生成一个 session ID,然后设置到 cookie,后面的所有请求浏览器都会带上 cookie, 然后服务端从 cookie 里获取 session ID,再查询到用户信息。所以,保持登录的关键不是 cookie,而是通过 cookie 保存和传输的 session ID,其本质是能获取用户信息的数据。除了 cookie,还通常使用 HTTP 请求头来传输。但是这个请求头浏览器不会像 cookie 一样自动携带,需要手工处理。
servlet 是线程安全的吗?为什么?
Servlet 对象并不是一个线程安全的对象。
Servlet 第一次被调用的时候,init()方法会被调用,然后调用 service() 方法,从第二次被请求开始,就直接调用 service()方法。
因为 servlet 是单实例的,所以后面再次请求同一个 Servlet 的时候都不会创建 Servlet 实例,而且 web 容器会针对每个请求创建一个独立的线程,这样多个并发请求会导致多个线程同时调用 service() 方法,这样就会存在线程不安全的问题。
如何解决 Servlet 线程不安全的问题?
(1)不要在 servlet 中使用成员变量。
(2)可以给 servlet 中的方法添加同步锁,Synchronized,但是不提倡,数据并发访问会造成阻塞等待。
(3)可以实现 SingleThreadModel 接口,如下。这样可以避免使用成员变量的问题,但是也不提倡,原因同上。
Public class Servlet1 extends HttpServlet implements SingleThreadModel{
………
}
谈谈过滤器的作用
java过滤器能够对目标资源的请求和响应进行截取.
谈谈拦截器的作用
拦截器是JavaWeb开发中必须用的技术,可以对整个系统字符集编码、URL访问权限过滤、过滤敏感词信息、session用户是否存在日志记录等等,拦截器只对controller请求起作用
拦截器和过滤器有什么区别
拦截器是基于 java 的反射机制的,而过滤器是基于函数回调。
拦截器不依赖 servlet 容器,过滤器依赖与 servlet 容器。
拦截器只能对 action 请求起作用,而过滤器则可以对几乎所有的请求起作用。
拦截器可以访问 action 上下文、值栈里的对象,而过滤器不能访问。
在 action 的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次
拦截器和过滤器的执行顺序
过滤前 – 拦截前 – Action 处理 – 拦截后 – 过滤后。
过滤是一个横向的过程,首先把客户端提交的内容进行过滤(例如未登录用户不能访问内部页面的处理);过滤通过后,拦截器将对用户提交数据进行检查,做一些前期的数据处理,接着把处理后的数据发给对应的 Action;Action 处理完成返回后,在此次期间拦截器还可以做其他操作,再向上返回到过滤器的后续操作
Servlet 是什么
Servlet(Server Applet)是 Java Servlet 的简称,他是用 Java 编写的服务器端程序,主要功能在于交互式地浏览和修改数据,生成动态 Web 内容
Serlvet 工作原理
首先客户发送个请求,Servlet 容器会创建特定于这个请求的 ServletRequest 对象和 ServletResponse 对象然后调用 Servlet 的 service()方法。Service()方法从ServletRequest 对象获得客户请求信息,处理该请求,然后通过 ServletResponse 对象向客户返回响应信息。
Servlet 生命周期
1,初始化阶段
调用 init()方法
Servlet 容器创建一个 Servlet 实例并且调用 Servlet 的 init()方法进行初始化。在 Servlet 的整个生命周期内init() 方法只被调用一次。
2,响应客户请求阶段 调用 service()方法
对于用户到达 Servlet 的请求,Servlet 容器会创建特定于这个请求的 ServletRequest 对象和 ServletResponse 对象,然后调用 Servlet 的 service 方法。service 方法从 ServletRequest 对象获得客户请求信息,处理该请求,并通过 ServletResponse 对象向客户返回响应信息。
3,终止阶段 调用 destroy()方法
当 WEB 应用被终止,或 Servlet 容器终止运行,或 Servlet 容器重新装载 Servlet 新实例时,Servlet 容器会先调用 Servlet 的 destroy()方法,在 destroy()方法中可以释放掉 Servlet 所占用的资源
Servlet 什么时候创建
1,默认情况下,当 WEB 客户第一次请求访问某个 Servlet 的时候,WEB 容器将创建这个 Servlet 的实例。
2,当 web.xml 文件中如果元素中指定了子元素时,Servlet 容器在启动 web 服务器时,将按照顺序创建并初始化 Servlet 对象。
servlet 与 jsp 的区别和联系
1.jsp 经编译后就变成了 Servlet.
2.JSP 的本质就是 Servlet,JVM 只能识别 java 的类,不能识别 JSP 的代码,
3.Web 容器将 Jsp 的代码编译成 JVM 能够识别 java 类
4.jsp 更擅长表现于页面显示,servlet 更擅长于逻辑控制.
5.JSP 侧重于视图,Servlet 主要用于控制逻辑
Session 与 cookie 的区别
一、对于 cookie:
①cookie 是创建于服务器端,存在浏览器端
②cookie 的生命周期可以通过 cookie.setMaxAge(2000)来设置,如果没有设置 setMaxAge, 则 cookie 的生命周期当浏览器关闭的时候,就消亡了
cookie 存在于客户端,临时文件夹中
session:存在于服务器的内存中,一个 session 域对象为一个用户浏览器服务
cookie 是以明文的方式存放在客户端的,安全性低,可以通过一个加密算法进行加密后存放session 存放于服务器的内存中,所以安全性好
cookie 会传递消息给服务器,session 本身存放于服务器,不会有传送流量
(1)cookie 的生命周期是累计的,从创建时,就开始计时,30 分钟后,cookie 生命周期结束,
(2)session 的生命周期是间隔的,从创建时,开始计时如在 30 分钟,没有访问 session,那么 session 生命周期被销毁,但是,如果在 30 分钟内(如在第 29 分钟时)访问过 session,那么将重新计算 session 的生命周期
(3) 关机会造成 session 生命周期的结束,但是对 cookie 没有影响
转发于重定向的区别
1.转发只访问服务器一次。重定向访问服务器两次
2.转发地址栏没有变化;重定向地址栏有变化
3.转发在服务器端完成的;重定向是在客户端完成的
4.转发不会执行转发后的代码;重定向会执行重定向之后的代码
5 转发必须是在同一台服务器下完成;重定向可以在不同的服务器下完成
jQuery 选择器
1.基本选择器
基本选择器是 JQuery 最常用的选择器,也是最简单的选择器,它通过元素 id、class 和标签名来查找 DOM 元素
2.层级选择器
如果想通过 DOM 元素之间的层次关系来获取特定元素, 例如后代元素, 子元素, 相邻元素, 兄弟元素等, 则需要使用层次选择器。
3.过滤选择器
按照不同的过滤规则, 过滤选择器可以分为基本过滤, 内容过滤, 可见性过滤, 属性过滤, 子元素过滤和表单对象属性过滤选择器。
Ajax
AJAX 全称为“Asynchronous JavaScript and XML”(异步 JavaScript 和 XML),是一种创建交互式网页应用的网页开发技术
JDBC
1.Jdbc 是什么
(1)JDBC(Java DataBase Connectivity,java 数据库连接)是一种用于执行 SQL 语句的 Java API,可以为多种关系数据库提供统一访问,它由一组用 Java 语言编写的类和接口组成。JDBC 提供了一种基准,使数据库开发人员能够编写数据库应用程序
2.jdbc 连接数据库步骤
1,使用 jdbc 编程需要连接数据库,注册驱动和数据库信息
2,操作 Connection,打开 Statement 对象
3,通过 Statement 对象执行 SQL,返回结果到 ResultSet 对象
4,使用 ResultSet 读取数据,然后通过代码转化为具体的实体对象
5,关闭数据库相关的资源
3.Jdbc 缺点
工作量比较大,需要连接,然后处理 jdbc 底层事务,处理数据类型,还需要操作 Connection,Statement 对象和 ResultSet 对象去拿数据并关闭他们
4.Jdbc 优点
执行效率要比 hibernate mybatis 要快,hibernate ,mybatis 框架都是对 jdbc 的封装
框架
简述 Spring 中 IOC 容器常用的接口和具体的实现类
BeanFactory: SpringIOC 容器的基本设置,是最底层的实现,面向框架本身的.
ApplicationContext: BeanFactory 的子接口, 提供了更多高级的功能, 面向开发者的.
ConfigurableApplicationContext: ApplicationContext 的子接口,扩展出了 close 和 refresh 等关闭刷新容器的方法
ClassPathXmlApplicationContext:从 classpath 的 XML 配置文件中读取上下文,并生成上下文定义。应用程序上下文从程序环境变量中获取。
FileSystemXmlApplicationContext :由文件系统中的 XML 配置文件读取上下文。
XmlWebApplicationContext:由 Web 应用的 XML 文件读取上下文。
Spring MVC的优点:
1.基于组件技术, 全部的应用对象,无论控制器和视图,还是业务对象之类的都是java组件,并且和Spring其他基础结构紧密集成.
2.不依赖于Servlet API(目标虽是如此,但是在实现的时候确实是依赖于Servlet的)
3.可以任意使用各种视图技术,而不仅仅局限于JSP
4.支持各种请求资源的映射策略
5.易于扩展
简述 Spring 中如何基于注解配置 Bean 和装配 Bean
(1)首先要在 Spring 中配置开启注解扫描
(2)在具体的类上加上具体的注解
(3)Spring 中通常使用@Autowired 或者是@Resource 等注解进行 bean 的装配
Spring中 @Autowired注解与@Resource注解的区别
相同点:
@Resource的作用相当于@Autowired,均可标注在字段或属性的setter方法上。
不同点:
(1)提供方:
(2)注入方式:
(3)属性:
请解释 Spring Bean(IOC) 的生命周期?
(1)默认情况下,IOC容器中bean的生命周期分为五个阶段:
首先调用构造器或者是通过工厂的方式创建 Bean 对象,然后给 bean 对象的属性注入值,再调用初始化方法(init-method)进行初始化,那么此时就可以使用Bean,IOC 容器关闭时销毁 Bean 对象.
(2)当加入了 Bean 的后置处理器后,IOC 容器中 bean 的生命周期分为七个阶段:
首先他会通过构造器或工厂方法创建Bean实例,然后为Bean的属性设置值和对其他Bean的引用,再将Bean实例传递给Bean后置处理器的postProcessBeforelnitialization方法,去调用Bean的初始化方法(init-method),将Bean实例传递给Bean后置处理器的postProcessAfterlnitialization方法,那么这个时候就可以使用Bean了,当容器关闭时,调用Bean的销毁方法(destroy-method)
简述 SpringMvc 里面拦截器是如何定义,如何配置,拦截器中三个重要的方法
定义:有两种方式
实现 HandlerInterceptor 接口 继承 HandlerInterceptorAdapter
拦截器中三个重要的方法:
preHandle postHandle afterCompletion
Spring 框架中都用到了哪些设计模式?
Spring框架中使用到了大量的设计模式,下 面列举了比较有代表性的:
开发中主要使用Spring的什么技术?
Mybatis 工作原理
1.加载 mybatis 全局配置文件(数据源、mapper 映射文件等),解析配置文件,MyBatis 基于 XML 配置文件生成Configuration,和一个个 MappedStatement(包括了参数映射配置、动态 SQL 语句、结果映射配置),其对应着
2、SqlSessionFactoryBuilder 通过 Configuration 对象生成 SqlSessionFactory,用来开启 SqlSession。
3、SqlSession 对象完成和数据库的交互:
4、用户程序调用 mybatis 接口层 api(即 Mapper 接口中的方法)
5、SqlSession 通过调用 api 的 Statement ID 找到对应的 MappedStatement 对象
6、通过 Executor(负责动态 SQL 的生成和查询缓存的维护)将 MappedStatement 对象进行解析,sql 参数转化、动态 sql 拼接,生成 jdbc Statement 对象
7、JDBC 执行 sql。
8、借助 MappedStatement 中的结果映射关系,将返回结果转化成 HashMap、JavaBean 等存储结构并返回。
MyBatis 中 #{}和${}的区别是什么?
#{}是预编译处理,${}是字符串替换;
Mybatis 在处理#{}时,会将 sql 中的#{}替换为?号,调用 PreparedStatement 的 set 方法来赋值;使用#{}可以有效的防止 SQL 注入,提高系统安全性
Mybatis 结果集的映射方式有几种,并分别解释每种映射方式如何使用
自动映射 ,通过 resultType 来指定要映射的类型即可。
自定义映射 通过 resultMap 来完成具体的映射规则,指定将结果集中的哪个列映射到对象的哪个属性。
简述 MyBatis 的单个参数、多个参数如何传递及如何取值。
MyBatis 传递单个参数,如果是普通类型(String+8 个基本)的,取值时在#{}中可以任意指定,如果是对象类型的,则在#{}中使用对象的属性名来取值
MyBatis 传递多个参数,默认情况下,MyBatis 会对多个参数进行封装 Map,取值时在#{}可以使用 0 1 2 … 或者是 param1 param2…
MyBatis 传递多个参数,建议使用命名参数,在 Mapper 接口的方法的形参前面使用@Param() 来指定封装 Map 时用的 key. 取值时在#{}中使用@Param 指定的 key
MyBatis 如何获取自动生成的(主)键值?
在标签中使用 useGeneratedKeys 和 keyProperty 两个属性来获取自动生成的主键值
简述 Mybatis 的动态 SQL,列出常用的 6 个标签及作用
动态 SQL 是 MyBatis 的强大特性之一 基于功能强大的 OGNL 表达式。
动态 SQL 主要是来解决查询条件不确定的情况,在程序运行期间,根据提交的条件动态的完成查询
常用的标签:
: 进行条件的判断
:在判断后的 SQL 语句前面添加 WHERE 关键字,并处理 SQL 语句开始位置的 AND 或者 OR 的问题
:可以在 SQL 语句前后进行添加指定字符 或者去掉指定字符.
: 主要用于修改操作时出现的逗号问题
:类似于 java 中的 switch 语句.在所有的条件中选择其一
:迭代操作
Mybatis 的 Xml 映射文件中,不同的 Xml 映射文件,id 是否可以重复?
不同的 Xml 映射文件,如果配置了 namespace,那么 id 可以重复;如果没有配置 namespace,那么 id 不能重复
简述 Spring 中如何给 bean 对象注入集合类型的属性
Spring 使用 等标签给对应类型的集合注入值
简述 Spring 中 bean 的作用域
总共有四种作用域:
Singleton 单例的 Prototype 原型的 Request Session
简述 Spring 中自动装配常用装配模式
byName: 根据 bean 对象的属性名进行装配
byType: 根据 bean 对象的属性的类型进行装配,需要注意匹配到多个兼容类型的 bean 对象时,会抛出异常。
请解释@Autowired 注解的工作机制及 required 属性的作用
简述 Springmvc 中 ContextLoaderListener 的作用以及实现原理
作用:
ContextLoaderListener 的作用是通过监听的方式在 WEB 应用服务器启动时对 Spring 的容器对象进行初始化.
原理:
ContextLoaderListener 实现了 ServletContextListener 接口,用于监听ServletContext 的创建,当监听到 ServletContext 创建时,在对应 contextInitialized方法中,对 Spring 的容器对象进行创建,并将创建好的容器对象设置到 ServletContext 域对象中,目的是让各个组件可以通过 ServletContext 共享到 Spring 的容器对象
简述 Mybatis 提供的两级缓存,以及缓存的查找顺序
(1)MyBatis 的缓存分为一级缓存和 二级缓存。
一级缓存是 SqlSession 级别的缓存,默认开启。
二级缓存是 NameSpace 级别(Mapper)的缓存,多个 SqlSession 可以共享,使用时需要进行配置开启。
(2)缓存的查找顺序:二级缓存 => 一级缓存 => 数据库
简述 Spring 与 Springmvc 整合时,如何解决 bean 被创建两次的问题
简述 Spring 与 Mybatis 整合时,主要整合的地方?
(1)SqlSession: 通过 SqlSessionFactoryBean 来配置用于创建 SqlSession 的信息。例如: Mybatis 的核心配置文件、 Mapper 映射文件、数据源等
(2)Mapper 接口: 使用 MapperScannerConfigurer 批量为 MyBatis 的 Mapper 接口生成代理实现类并将具体的对象交给 Spring 容器管理
简述 Spring 声明式事务中@Transaction 中常用的事务传播行为?
通过 propagation 来执行事务的传播行为
REQUIRED:使用调用者的事务,如果调用者没有事务,则启动新的事务运行
REQUIRES_NEW:将调用者的事务挂起,开启新的事务运行
简述@RequestMapping 注解的作用,可标注的位置,常用的属性
(1)该注解的作用是用来完成请求 与 请求处理方法的映射
(2)该注解可以标注在类上或者是方法上
(3)常用的属性:
value: 默认属性, 用于指定映射的请求 URL
method: 指定映射的请求方式
params: 指定映射的请求参数
headers: 指定映射的请求头信息
简述 Springmvc 中处理模型数据的两种方式
使用 ModelAndView 作为方法的返回值,将模型数据和视图信息封装到 ModelAndView 中
使用 Map 或者是 Model 作为方法的形参,将模型数据添加到 Map 或者是 Model 中
简述 REST 中的四种请求方式及对应的操作
GET 查询操作 POST 添加操作 DELETE 删除操作 PUT 修改操作
简述Spring Mvc视图和视图解析的关系及作用
请求处理方法执行完成后,最终返回一个 ModelAndView 对象。对于那些返回 String、View 或 ModelMap 等类型的处理方法,Spring MVC 也会在内部将它们装配成一个 ModelAndView 对象,该对象包含了视图逻辑名和模型对象的信息。
Spring MVC 借助视图解析器(ViewResolver)得到最终的视图对象(View),这可能是我们常见的 JSP 视图,也可能是一个基于 FreeMarker、Velocity 模板技术的视图,还可能是 PDF、Excel、XML、JSON 等各种形式的视图。
对于最终究竟采取何种视图对象对模型数据进行渲染,处理器并不关心,处理器的工作重点聚焦在生产模型数据的工作上,从而实现 MVC 的充分解耦。
简述 REST 中 HiddenHttpMethodFilter 过滤器的作用
该过滤器主要负责转换客户端请求的方式,当浏览器的请求方式为 POST,并且在请求中能通过 _method 获取到请求参数值。该过滤器就会进行请求方式的转换。
一般在 REST 中,都是将 POST 请求转换为对应的 DELETE 或者是 PUT
简述 Springmvc 中如何返回 JSON 数据
Step1:在项目中加入 json 转换的依赖,例如 jackson,fastjson,gson 等
Step2:在请求处理方法中将返回值改为具体返回的数据的类型, 例如数据的集合类 List等
Step3:在请求处理方法上使用@ResponseBody 注解
简述如何在 myBatis 中的增删改操作获取到对数据库的影响条数
直接在 Mapper 接口的方法中声明返回值即可
Springmvc 中的控制器的注解用哪个,可以是否用别的注解代替
使用@Controller 注解来标注控制器,不能使用别的注解代替。
如何在 Springmvc 中获取客户端提交的请求参数
直接在请求处理方法中声明对应的形参,也可以是用@RequestParam 注解来具体指定将那些请求参数映射到方法中对应的形参
简述 Springmvc InternalResourceViewResolver 解析器的工作机制
使用 prefix + 方法的返回值 + suffix 生成一个物理视图路径
Springmvc 中如何完成重定向
在请求处理方法的返回值前面加 redirect: 前缀, 最终会解析得到 RedirectView,RedirectView 会完成重定向的操作。
简述 Spring 中切面中常用的几种通知,并简单解释
前置通知(前置增强) 在目标方法执行之前执行
后置通知(后置增强) 在目标方法执行之后执行,不管目标方法有没有抛出异常
返回通知(最终增强) 在目标方法成功返回之后执行, 可以获取到目标方法的返回值
异常通知(异常增强) 在目标方法抛出异常后执行
环绕通知(环绕增强) 环绕着目标方法执行
Spring中那些地方使用到了切面?
Authentication 权限 Caching 缓存 Transactions 事务 Error handling 错误处理
解释 MyBatis 中 @Param 注解的作用
通过该注解来指定 Mybatis 底层在处理参数时封装 Map 使用的 key,方便在 SQL 映射文件中取参数。
简述 Mybatis 中使用 Mapper 接口开发,如何完成 Mapper 接口与 SQL 映射文件、方法与 SQL 语句的绑定
Mapper 接口与 SQL 映射文件绑定:SQL 映射文件中的 namespace 的值指定成 Mapper 接口的全类名
接口中方法与 SQL 语句的绑定:SQL 语句的 id 指定成接口中的方法名。
谈谈你对 Spring 的理解
Spring 是一个开源框架,为简化企业级应用开发而生,最大的特点是分层开发,他是一个 IOC 和 AOP容器框架。
spring如何简化java开发
1)基于POJO的轻量级和最小侵入性编程;
2)通过依赖注入和面向接口实现松耦合;
3)基于切面和惯例进行声明式编程;
4)通过切面和模板减少样版式代码。
如果不用Spring,你打算怎么做开发?
WEB项目,写Servlet一样可以完成
非WEB项目,不用Spring,字节写单例,相互引用,因为在Spring底层他的配置文件中的类都是单例
SpringMVC是什么?
首先是一个MVC框架。在web模型中,MVC是一种很流行的框架,通过把Model,View,Controller分离,把较为复杂的web应用分成逻辑清晰的几部分,是为了简化开发,减少出错。并且,他是spring的一个子框架,当然拥有spring的特性,如依赖注入。
Spring框架的好处,为什么要用Spring?
1.方便解耦,便于开发(Spring就是一个大工厂,可以将所有对象的创建和依赖关系维护都交给spring管理)
2.spring支持aop编程(spring提供面向切面编程,可以很方便的实现对程序进行权限拦截和运行监控等功能)
3.声明式事务的支持(通过配置就完成对事务的支持,不需要手动编程)
4.方便程序的测试,spring 对junit4支持,可以通过注解方便的测试spring 程序
5.方便集成各种优秀的框架
6.降低javaEE API的使用难度(Spring 对javaEE开发中非常难用的一些API 例如JDBC,javaMail,远程调用等,都提供了封装,是这些API应用难度大大降低)
EJB是什么?
EJB是sun的JavaEE服务器端组件模型,设计目标与核心应用是部署分布式应用程序。简单来说就是把已经编写好的程序(即:类)打包放在服务器上执行。凭借java跨平台的优势,用EJB技术部署的分布式系统可以不限于特定的平台。
EJB (Enterprise JavaBean)是J2EE(javaEE)的一部分,定义了一个用于开发基于组件的企业多重应用程序的标准。其特点包括网络服务支持和核心开发工具(SDK)。 在J2EE里,Enterprise Java Beans(EJB)称为Java 企业Bean,是Java的核心代码,分别是会话Bean(Session Bean),实体Bean(Entity Bean)和消息驱动Bean(MessageDriven Bean)。在EJB3.0推出以后,实体Bean被单独分了出来,形成了新的规范JPA。
JPA是什么?
JPA (Java Persistence API)Java持久化API。是一套Sun公司Java官方制定的ORM 方案,是规范,是标准 ,sun公司自己并没有实现
ORM是什么?
ORM是对象关系映射(英语:(Object Relational Mapping,简称duORM,或O/RM,或O/R mapping),是一种程序技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换
Spring 容器的主要核心是:
控制反转(IOC),传统的 java 开发模式中,当需要一个对象时,我们会自己使用 new 或者 getInstance 等直接或者间接调用构造方法创建一个对象。而在 spring 开发模式中,spring 容器使用了工厂模式为我们创建了所需要的对象,不需要我们自己创建了,直接调用 spring 提供的对象就可以了,这是控制反转的思想。
依赖注入(DI),spring 使用 javaBean 对象的 set 方法或者带参数的构造方法为我们在创建所需对象时将其属性自动设置所需要的值的过程,就是依赖注入的思想。
面向切面编程(AOP),在面向对象编程(oop)思想中,我们将事物纵向抽成一个个的对象。而在面向切面编程中,我们将一个个的对象某些类似的方面横向抽成一个切面,对这个切面进行一些如权限控制、事物管理,记录日志等公用操作处理的过程就是面向切面编程的思想。AOP 底层是动态代理,如果是接口采用 JDK 动态代理,如果是类采用 CGLIB 方式实现动态代理。
Spring 的事务管理有几种?
(1)基于TransactionInterceptor声明式事务管理的定义:用在 Spring 配置文件中声明式的处理事务来代替代码式的处理事务。这样的好处是,事务管理不侵入开发的组件,具体来说,业务逻辑对象就不会意识到正在事务管理之中,事实上也应该如此,因为事务管理是属于系统层面的服务,而不是业务逻辑的一部分,如果想要改变事务管理策划的话,也只需要在定义文件中重新配置即可,这样维护起来极其方便。
transactionManager属性,用来指定一个事务治理器,并将具体事务相关的操作请托给它; transactionAttributes属性,该属性的每一个键值对中,键指定的是方法名,方法名可以行使通配符, 而值就是表现呼应方法的所运用的事务属性。
(2)基于 @Transactional 的声明式事务管理:@Transactional可以浸染于接口、接口方法、类和类方法上。作用于类上时,该类的一切public 方法将都具有该类型的事务属性。
(3)编程式事务管理的定义:在代码中显式挪用 beginTransaction()、commit()、rollback()等事务治理相关的方法, 这就是编程式事务管理。Spring 对事物的编程式管理有基于底层 API 的编程式管理和基于 TransactionTemplate 的编程式事务管理两种方式。
springmvc 工作流程
1、用户发送请求至前端控制器 DispatcherServlet。
2、DispatcherServlet 收到请求调用 HandlerMapping 处理器映射器。
3、处理器映射器找到具体的处理器(可以根据 xml 配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给 DispatcherServlet。
4、DispatcherServlet 调用 HandlerAdapter 处理器适配器。
5、HandlerAdapter 经过适配调用具体的处理器(Controller,也叫后端控制器)。
6、Controller 执行完成返回 ModelAndView。
7、HandlerAdapter 将 controller 执行结果 ModelAndView 返回给 DispatcherServlet。
8、DispatcherServlet 将 ModelAndView 传给 ViewReslover 视图解析器。
9、ViewReslover 解析后返回具体 View。
10、DispatcherServlet 根据 View 进行渲染视图(即将模型数据填充至视图中)。
11、 DispatcherServlet 响应用户。
DispatcherServlet:作为前端控制器,整个流程控制的中心,控制其它组件执行,统一调度,降低组件之间的耦合性,
提高每个组件的扩展性。
HandlerMapping:通过扩展处理器映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。
HandlAdapter:通过扩展处理器适配器,支持更多类型的处理器。
ViewResolver:通过扩展视图解析器,支持更多类型的视图解析,例如:jsp、freemarker、pdf、excel 等
Spring 的常用注解
@Component: 用于创建对象的,作用于类。
@Controller: 一般用于表现层的注解。
@Service: 一般用于业务层的注解。
@Repository: 一般用于持久层的注解。
@Bean 用于把当前方法的返回值作为 bean 对象存入 spring 的 ioc 容器中
@ComponentScan 用于指定 spring 在初始化容器时要扫描的包
@Autowired:自动按照类型注入
@Scope:作用:指定 bean 的作用范围。
分布式/微服务
集群如何做 session 共享
集群 session 共享问题:
把session 存到 memcache 或者 redis 中进行同步 session,不会加大数据库的负担,并且安全性比用cookie 大大的提高,把 session 放到内存里面,比从文件中读取要快很多!
Redis 是什么
Redis全称(Remote Dictionary Server) 本质上是一个 Key-Value 类型的内存数据库,整个数据库统统加载在内存当中进行操作,定期通过异步操作把数据库数据 flush到硬盘上进行保存。因为是纯内存操作,Redis 的性能非常出色,每秒可以处理超过 10 万次读写操作,是已知性能最快的 Key-Value DB。 Redis 的出色之处不仅仅是性能,最大的魅力是支持保存多种数据结构,此外单个 value 的最大限制是 1GB,因此 Redis可以用来实现很多有用的功能,比方说用他的 List 来做 FIFO 双向链表,实现一个轻量级的高性能消息队列服务,用他的 Set 可以做高性能的 tag 系统等等。另外 Redis 也可以对存入的 Key-Value 设置 expire 时间。 Redis 的主要缺点是数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此 Redis 适合的场景主要局限在较小数据量的高性能操作和运算上。
为什么要用 redis /为什么要用缓存
主要从“高性能”和“高并发”这两点来看待这个问题。
高性能:
假如用户第一次访问数据库中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。将该用户访问的数据存在数据缓存中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据即可!
高并发:
直接操作缓存能够承受的请求是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。
为什么 Redis 需要把所有数据放到内存中?
Redis 为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘。 所以 Redis 具有快速和数据持久化的特征。如果不将数据放在内存中,磁盘 I/O 速度会严重 影响 Redis 的性能。在内存越来越便宜的今天,Redis 将会越来越受欢迎。 如果设置了最大使用的内存,则数据已有记录数达到内存限值后不能继续插入新值。
Redis 集群方案什么情况下会导致整个集群不可用?
有 A,B,C 三个节点的集群,在没有复制模型的情况下,如果节点 B 失败了,那么整个集群就会以为缺少 5501-11000 这个范围的槽而不可用。
为什么要用 redis 而不用 map/guava 做缓存?
缓存分为本地缓存和分布式缓存。以 Java 为例,使用自带的 map 或者 guava 实现的是本地缓存,最主要的特点是轻量以及快速,生命周期随着 jvm 的销毁而结束,并且在多实例的情况下,每个实例都需要各自保存一份缓存,缓存不具有一致性。
使用 redis 或 memcached 之类的称为分布式缓存,在多实例的情况下,各实例共用一份缓存数据,缓存具有一致性。缺点是需要保持 redis 或 memcached服务的高可用,整个程序架构上较为复杂。
Redis 有哪些适合的场景?
(1)、会话缓存(Session Cache)
最常用的一种使用 Redis 的情景是会话缓存(session cache)。用 Redis 缓存会话比其他存储(如 Memcached)的优势在于:Redis 提供持久化。当维护一个不是严格要求一致性的缓存时,如果用户的购物车信息全部丢失,大部分人都会不高兴的!随着 Redis 这些年的改进,很容易找到怎么恰当的使用 Redis 来缓存会话的文档。甚至广为人知的商业平台 Magento 也提供 Redis 的插件。
(2)、全页缓存(FPC)
除基本的会话 token 之外,Redis 还提供很简便的 FPC 平台。回到一致性问题,即使重启 了 Redis 实例,因为有磁盘的持久化,用户也不会看到页面加载速度的下降,这是一个极大改进,类似 PHP 本地 FPC。 再次以 Magento 为例,Magento 提供一个插件来使用 Redis 作为全页缓存后端。
(3)、队列
Reids 在内存存储引擎领域的一大优点是提供 list 和 set 操作,这使得 Redis 能作为一个很好的消息队列平台来使用。他类似于本地程序语言(如 Python)对 list 的 push/pop 操作。Celery 有一个后台就是使用 Redis 作为 broker。
(4)、排行榜/计数器
Redis在内存中对数字进行递增或递减的操作实现的非常好。Redis 提供了集合(Set)和有序集合(Sorted Set)也使得我们在执行这些操作的时候变的非常简单。Agora Games 就是一个很好的例子,用 Ruby 实现的,它的排行榜就是使用 Redis 来存储数据的。
(5)、发布/订阅
发布/订阅的使用场景确实非常多。我已看见人们在社交网络连接中使用,还可作为基于发布/订阅的脚本触发器,甚至用 Redis 的发布/订阅功能来建立聊天系统!
Redis 支持的 Java 客户端都有哪些?官方推荐用哪个?
Redisson、Jedis、lettuce 等等,官方推荐使用 Redisson。
Redis 和 Redisson 有什么关系?
Redisson 是一个高级的分布式协调 Redis 客户端,能帮助用户在分布式环境中轻松实现一些 Java 的对象 (Bloom filter, BitSet, Set, SetMultimap, ScoredSortedSet, SortedSet, Map, ConcurrentMap, List, ListMultimap, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, ReadWriteLock, AtomicLong, CountDownLatch, Publish / Subscribe, HyperLogLog)
Jedis 与 Redisson 对比有什么优缺点?
Jedis 是 Redis 的 Java 客户端,其 API 提供了比较全面的 Redis 命令的支持;
Redisson 实现了分布式和可扩展的 Java 数据结构,和 Jedis 相比,功能较为简单,不支持字符串操作,不支持排序、事务、管道、分区等 Redis 特性。Redisson 的宗旨是促进使用者对 Redis 的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上。
什么是分布式锁?
为了防止分布式系统中的多个进程之间相互干扰,我们需要一种分布式协调技术来对这些进程进行调度。而这个分布式协调技术的核心就是来实现这个分布式锁。
为什么要使用分布式锁?
为了保证一个方法在高并发情况下的同一时间只能被同一个线程执行,在传统单体应用单机部署的情况下,可以使用Java并发处理相关的API(如ReentrantLock或synchronized)进行互斥控制。但是,随着业务发展的需要,原单体单机部署的系统被演化成分布式系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题。
使用redis如何设计分布式锁?说一下 实现思路?使用ZK可以吗?如何实现?这两种有什么区别?
redis:
1.线程A使用setnx(上锁的对象,超时的时间戳t1),如果返回true,获得锁。
2.线程B用get获取t1,与当前时间戳比较判断是是否超时,没超时false,若超时执行第3步;
3.计算新的超时时间t2,使用getset命令返回t3(该值可能其他线程已经修改过),如果t1==t3,获得锁,如果t1!=t3说明锁被其他线程获取了。
4.获取锁后,处理完业务逻辑,再去判断锁是否超时,如果没超时删除锁,如果已超时,不用处理(防止删除其他线程的锁)。
zk:
1.客户端对某个方法加锁时,在zk上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点node1;
2.客户端获取该路径下所有已经创建的子节点,如果发现自己创建的node1的序号是最小的,就认为这个客户端获得了锁。
3.如果发现node1不是最小的,则监听比自己创建节点序号小的最大的节点,进入等待。
Redisson实现分布式锁—原理
redission实现了JDK中的Lock接口,所以使用方式一样,只是Redssion的锁是分布式的
https://www.cnblogs.com/qdhxhz/p/11046905.html
redis红锁:
RLock lock1=redisson.getLock();
lock1.lock();
RLock lock2=redisson.getLock();
…
RLock lock3=redisson.getLock();
…
RedissonRedLock redLock=new RedissonRedLock(RLock… locks);
redLock.lock();
可以传入多个RLock(可重入锁)实例,每一个RLock都去获得他自己的锁,判断成功还是失败,然后把所有RLock 结果去做聚合运算,如(true,true,false),如果两个成功那么这个加锁才算是加锁成功,也就是说把锁的标记存储在不同的redis节点,可以提升他加锁的可靠性,那么随之他的效率的降下来了,如果想使用红锁的话需要提供多套主从关系,而且主节点之间不能是集群的关系.
Redis 如何设置密码及验证密码?
设置密码:config set requirepass 123456
授权密码:auth 123456
说说 Redis 哈希槽的概念?
Redis 集群没有使用一致性 hash,而是引入了哈希槽的概念,Redis 集群有 16384 个哈希槽, 每个 key 通过 CRC16 校验后对 16384 取模来决定放置哪个槽,集群的每个节点负责一部分hash 槽。
循环校验码(CRC码):
是数据通信领域中最常用的一种差错校验码,其特征是信息字段和校验字段的长度可以任意选定。
Redis 集群方案应该怎么做?都有哪些方案?
1.codis:目前用的最多的集群方案,基本和twemproxy 一致的效果,但它支持在节点数量改变情况下,旧节点数据可恢复到新hash节点。
2.redis cluster3.0自带的集群,特点在于他的分布式算法不是一致性hash,而是hash槽的概念,以及自身支持节点设置从节点。
3.在业务代码层实现,起几个毫无关联的redis实例,在代码层,对key 进行hash计算,然后去对应的redis实例操作数据。这种方式对hash层代码要求比较高,考虑部分包括,节点失效后的替代算法方案,数据震荡后的自动脚本恢复,实例的监控,等等。
Redis 集群会有写操作丢失吗?为什么?
Redis 并不能保证数据的强一致性,这意味这在实际中集群在特定的条件下可能会丢失写操作。
Redis 集群之间是如何复制的?
异步复制
Redis 集群最大节点个数是多少?
16384 个。
Redis 集群如何选择数据库?
Redis 集群目前无法做数据库选择,默认在 0 数据库。
怎么测试 Redis 的连通性?
ping
Redis 中的管道有什么用?
一次请求/响应服务器能实现处理新的请求即使旧的请求还未被响应。这样就可以将多个命令同时发送到服务器,而不用等待回复,最后在一个步骤中读取该答复。 这就是管道(pipelining),是一种几十年来广泛使用的技术。例如许多 POP3 协议已经实现支持这个功能,大大加快了从服务器下载新邮件的过程。
redis管道在什么情况下可以使用?
设置指令, 删除指令-获取指令不建议使用管道, 因为获取指令需要立即指令, 此时使用管道来执行意义不大
Redis 如何做内存优化?
尽可能使用散列表(hashes),他使用的内存非常小,所以你应该尽可能的将你的数据模型抽象到一个散列表里面。比如你的 web 系统中有一个用户对象,不要为这个用户的名称,姓氏,邮箱,密码设置单独的 key,而是应该把这个用户的所有信息存储到一张散列表里面
Redis 回收进程如何工作的?
一个客户端运行了新的命令,添加了新的数据。
Redi 检查内存使用情况,如果大于 maxmemory 的限制, 则根据设定好的策略进行回收,一旦满足这个条件就会重复执行相应的回收操作。
Redis 回收使用的是什么算法?
LRU 算法 (选择最近最少被访问的数据进行淘汰)
Redis 如何做大量数据插入?
Redis2.6 开始 Redis-cli 客户端支持一种新的被称之为 pipe mode(管道模式) 的新模式用于执行大量数据插入工作
Redis集群中的节点如何保证数据一致?
主从复制:
使用Redis主从复制功能,redis的复制功能是支持多个数据库之间的数据同步。一类是主数据库(master)一类是从数据库(slave),主数据库可以进行读写操作,当发生写操作的时候自动将数据同步到从数据库,而从数据库一般是只读的,并接收主数据库同步过来的数据,一个主数据库可以有多个从数据库,而一个从数据库只能有一个主数据库。
通过redis的复制功能可以很好的实现数据库的读写分离,提高服务器的负载能力。主数据库主要进行写操作,而从数据库负责读操作。
主从复制工作原理?
当一个从数据库启动时,会向主数据库发送sync命令,主数据库接收到sync命令后开始在后台保存快照(执行rdb操作),并将保存期间接收到的命令缓存起来,那么当快照完成后,redis会将快照文件和所有缓存的命令发送给从数据库。最后,从数据库收到后,会载入快照文件并执行收到的缓存的命令。
redis 主从复制如何实现的?redis的集群模式如何实现?redis的key是如何寻址的?
当一个从数据库启动时,会向主数据库发送sync命令,主数据库接收到sync命令后开始在后台保存快照(执行rdb操作),并将保存期间接收到的命令缓存起来,那么当快照完成后,redis会将快照文件和所有缓存的命令发送给从数据库。最后,从数据库收到后,会载入快照文件并执行收到的缓存的命令。
分片方式:
-客户端分片
-基于代理的分片
●Twemproxy
●codis
-路由查询分片
● Redis-cluster(本身提供了自动将数据分散到Redis Cluster不同节点的能力,整个数据集合的某个数据子集存储在哪个节点对于用户来说是透明的)
redis-cluster分片原理:Cluster中有一个16384长度的槽(虛拟槽),编号分别为0-16383。每个Master节点都会负责一部分的槽, 当有某个key被映射到某个Master负责的槽,那么这个Master负责为这个key提供服务,至于哪个Master节点负责哪个槽,可以由用户指定,也可以在初始一个16384/8字节的位序列,Master 节点用bit来标识对于某个槽自己是否拥有。比如对于编号为1的槽,Master 只要判断序列的第二位(索引从0开始)是不是为1即可。这种结构很容易添加或者删除节点。比如如果我想新添加个节点D,我需要从节点A、B、C中得部分槽到D
你知道有哪些 Redis 分区实现方案?
客户端分区就是在客户端就已经决定数据会在哪个 Redis 节点存储或读取,大多数客户端已经实现了客户端分区。
代理分区 意味着客户端将请求发送给代理,然后代理决定去哪个节点写读写数据。代理根据分区规则决定请求哪些 Redis 实例,然后根据 Redis 的响应结果返回给客户端。
Redis 和 memcached 的一种代理实现就是 Twemproxy查询路由(Query routing) 他的意思是客户端随机地请求任意一个 Redis 实例,然后由 Redis将请求转发给正确的 Redis 节点。 Redis Cluster 实现了一种混合形式的查询路由,但并不是直接将请求从一个 Redis 节点转发到另一个 Redis 节点,而是在客户端的帮助下直接redirected 到正确的 Redis 节点。
为什么要做 Redis 分区?
分区可以让 Redis 管理更大的内存,Redis 将可以使用所有机器的内存。如果没有分区,你最多只能使用一台机器的内存。分区使 Redis 的计算能力通过简单地增加计算机得到成倍提升,Redis 的网络带宽也会随着计算机和网卡的增加而成倍增长。
Redis 分区有什么缺点?
预分片技术
预分片技术是指在开始时就启动足够多的redis实例(例如32或64个,估计一下够以后扩展用就行了),等到后续需要扩容的时候,只需要将其中一部分的redis实例转移到新增加的机子上即可,在redis实例迁移过程中使用redis的复制功能可以最大限度的降低redis的停工时间甚至可以做到没有停工时间。由于redis实例是轻量级的进程,而且占用内存较少,这里指单纯的空的redis实例,一个空的redis实例大约占用1M的内存;因此,这种方式即不会占用太多系统开销,又便于实现;
Redis的预分片技术可以按照以下步骤进行实例迁移操作:
(1)在新机子上启动新的redis实例;
(2)将新redis实例作为slave将原redis实例作为master,将数据从原redis实例迁移到新redis实例上;
(3)停止客户端(分片操作在客户端上时)或代理服务器(分片操作在代理上)
(4)更新客户端或者代理服务器中的配置信息,去掉被迁移的原redis实例的ip和端口等信息,加上新启动redis实例的IP地址和端口;
(5)向新启动的redis发送SLAVEOF NOONE命令,终止新redis实例对原redis实例的从属关系;
(6)重启客户端程序或者代理程序,此时它们将会使用新的redis实例;
(7)关掉被迁移走数据的原redis实例;
Redis 持久化数据和缓存怎么做扩容?
如果 Redis 被当做缓存使用,使用一致性哈希实现动态扩容缩容。
如果 Redis 被当做一个持久化存储使用,必须使用固定的 keys-to-nodes 映射关系,节点的数量一旦确定不能变化。否则的话(即 Redis 节点需要动态变化的情况),必须使用可以在运行时进行数据再平衡的一套系统,而当前只有 Redis 集群可以做到这样。
分布式 Redis 是前期做还是后期规模上来了再做好?为什么?
既然 Redis 是如此的轻量(单实例只使用 1M 内存),为防止以后的扩容,最好的办法就是一开始就启动较多实例。即便你只有一台服务器,你也可以一开始就让 Redis 以分布式的方式运行,使用分区,在同一台服务器上启动多个实例。一开始就多设置几个 Redis 实例,例如 32 或者 64 个实例,对大多数用户来说这操作起来可能比较麻烦,但是从长久来看做这点牺牲是值得的。
这样的话,当你的数据不断增长,需要更多的 Redis 服务器时,你需要做的就是仅仅将 Redis 实例从一台服务迁移到另外一台服务器而已(而不用考虑重新分区的问题)。一旦你添加了另一台服务器,你需要将你一半的 Redis 实例从第一台机器迁移到第二台机器。
Twemproxy 是什么?
Twemproxy 是 Twitter 维护的(缓存)代理系统,代理 Memcached 的 ASCII(美国信息交换标准代码) 协议和 Redis协议。它是单线程程序,使用 c 语言编写,运行起来非常快。它是采用 Apache 2.0 license 的开源软件。
Twemproxy 支持自动分区,如果其代理的其中一个 Redis 节点不可用时,会自动将该节点排除(这将改变原来的 keys-instances 的映射关系,所以你应该仅在把 Redis 当缓存时使 用 Twemproxy)。
Twemproxy 本身不存在单点问题,因为你可以启动多个 Twemproxy 实例,然后让你的客户端去连接任意一个 Twemproxy 实例。
Twemproxy 是 Redis 客户端和服务器端的一个中间层,由它来处理分区功能应该不算复杂,并且应该算比较可靠的。
支持一致性哈希的客户端有哪些?
Redis-rb、PRedis 等。
Redis 与其他 key-value 存储有什么不同?
Redis 有着更为复杂的数据结构并且提供对他们的原子性操作,这是一个不同于其他数据库的进化路径。Redis 的数据类型都是基于基本数据结构的同时对程序员透明,无需进行额外的抽象。
Redis 运行在内存中但是可以持久化到磁盘,所以在对不同数据集进行高速读写时需要权衡内存,保证数据量不能大于硬件内存。在内存数据库方面的另一个优点是, 相比在磁盘上相同的复杂的数据结构,在内存中操作起来非常简单,这样 Redis 可以做很多内部复杂性很强的事情。 同时,在磁盘格式方面他们是紧凑的以追加的方式产生的,因为他们并不需要进行随机访问。
查看 Redis 使用情况及状态信息用什么命令?
info
一个 Redis 实例最多能存放多少的 keys?List、Set、Sorted Set 他们最多能存放多少元素?
理论上 Redis 可以处理多达 232 的 keys,并且在实际中进行了测试,每个实例至少存放了 2亿 5 千万的 keys。 任何 list、set、和 sorted set 都可以放 232 个元素。换句话说,Redis 的存储极限是系统中的可用内存值。
Redis 常见性能问题和解决方案?
(1) Master 最好不要做任何持久化工作,如 RDB 内存快照和 AOF 日志文件
(2) 如果数据比较重要,某个 Slave 开启 AOF 备份数据,策略设置为每秒同步一次
(3) 为了主从复制的速度和连接的稳定性,Master 和 Slave 最好在同一个局域网内
(4) 尽量避免在压力很大的主库上增加从库
(5) 主从复制不要用树状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3…
这样的结构方便解决单点故障问题,实现 Slave 对 Master 的替换。如果 Master 挂了,可以立刻启用 Slave1 做 Master,其他不变。
redis 和 memcached 的区别
redis 设置过期时间
Redis中有个设置时间过期的功能,即对存储在 redis 数据库中的值可以设置一个过期时间。作为一个缓存数据库,这是非常实用的。如我们一般项目中的 token 或者一些登录信息,尤其是短信验证码都是有时间限制的,按照传统的数据库处理方式,一般都是自己判断过期,这样无疑会严重影响项目性能。我们 set key 的时候,都可以给一个 expire time,就是过期时间,通过过期时间我们可以指定这个 key 可以存活的时间。
如果假设你设置了一批 key 只能存活1个小时,那么接下来1小时后,redis是怎么对这批key进行删除的?
定期删除+惰性删除。
定期删除:redis默认是每隔 100ms 就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除。注意这里是随机抽取的。为什么要随机呢?你想一想假如 redis 存了几十万个 key ,每隔100ms就遍历所有的设置过期时间的 key 的话,就会给 CPU 带来很大的负载!
惰性删除 :定期删除可能会导致很多过期 key 到了时间并没有被删除掉。所以就有了惰性删除。假如你的过期key,靠定期删除没有被删除掉,还停留在内存里,除非你的系统去查一下那个 key,才会被redis给删除掉。这就是所谓的惰性删除!
如果仅仅通过设置过期时间还是有问题的。我们想一下:如果定期删除漏掉了很多过期 key,然后你也没及时去查,也就没走惰性删除,此时会怎么样?如果大量过期key堆积在内存里,导致redis内存块耗尽了。怎么解决这个问题呢?
redis 内存淘汰机制。
redis 内存淘汰机制(MySQL里有2000w数据,Redis中只存20w的数据,如何保证Redis中的数据都是热点数据?)
redis内存数据集大小上升到一定大小的时候,会施行数据淘汰策略!
redis 提供 6种数据淘汰策略:
redis 持久化机制(怎么保证 redis 挂掉之后再重启数据可以进行恢复)
很多时候我们需要持久化数据也就是将内存中的数据写入到硬盘里面,大部分原因是为了之后重用数据(比如重启机器、机器故障之后回复数据),或者是为了防止系统故障而将数据备份到一个远程位置。
Redis不同于Memcached的很重要一点就是,Redis支持持久化,而且支持两种不同的持久化操作。Redis的一种持久化方式叫快照(snapshotting,RDB),另一种方式是只追加文件(append-only fifile,AOF).这两种方法各有千秋!
快照(snapshotting)持久化(RDB)
Redis可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。Redis创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis主从结构,主要用来提高Redis性能),还可以将快照留在原地以便重启服务器的时候使用。默认会在指定目录下生成一个 dump.rdb 文件。Redis 重启会通过加载 dump.rdb 文件恢复数据。
快照持久化是Redis默认采用的持久化方式,在redis.conf配置文件中默认有此下配置:
save 900 1 #在900秒(15分钟)之后,如果至少有1个key发生变化,Redis就会自动触发BGSAVE命令 创建快照。
save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,Redis就会自动触发BGSAVE命令创 建快照。
save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生变化,Redis就会自动触发BGSAVE命令创 建快照。
AOF(append-only fifile)持久化
与快照持久化相比,AOF持久化的实时性更好,因此已成为主流的持久化方案。默认情况下Redis没有开启,开启AOF持久化后每执行一条会更改Redis中的数据的命令,Redis就会将该命令写入硬盘中的AOF文件。AOF文件的保存位置和RDB文件的位置相同,都是通过dir参数设置的,默认的文件名是appendonly.aof。
AOF(append only fifile)方式的持久化,可以通过appendonly参数开启:
appendonly yes
在Redis的配置文件中存在三种不同的 AOF 持久化方式,它们分别是:
appendfsync always #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度
appendfsync everysec #每秒钟同步一次,显示地将多个写命令同步到硬盘
appendfsync no #让操作系统决定何时进行同步
为了兼顾数据和写入性能,用户可以考虑 appendfsync everysec选项 ,让Redis每秒同步一次AOF文件,Redis性能几乎没受到任何影响。而且这样即使出现系统崩溃,用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候,Redis还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。
Redis 4.0 对于持久化机制的优化
Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 aof-use-rdb-preamble 开启)。
如果把混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。这样做的好处是可以结合 RDB和 AOF 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的, AOF 里面的 RDB 部分是压缩格式不再是AOF 格式,可读性较差。
补充内容:AOF 重写
AOF重写可以产生一个新的AOF文件,这个新的AOF文件和原有的AOF文件所保存的数据库状态一样,但体积更小。
AOF重写是一个有歧义的名字,该功能是通过读取数据库中的键值对来实现的,程序无须对现有AOF文件进行任何读入、分析或者写入操作。
在执行 BGREWRITEAOF 命令时,Redis 服务器会维护一个 AOF 重写缓冲区,该缓冲区会在子进程创建新AOF文件期间,记录服务器执行的所有写命令。当子进程完成创建新AOF文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新AOF文件的末尾,使得新旧两个AOF文件所保存的数据库状态一致。最后,服务器用新的AOF文件替换旧的AOF文件,以此来完成AOF文件重写操作
如何解决 Redis 的并发竞争 Key 问题
分布式锁(zookeeper 和 redis 都可以实现分布式锁)。(如果不存在 Redis 的并发竞争 Key 问题,不要使用分布式锁,这样会影响性能)基于zookeeper临时有序节点可以实现的分布式锁。
redis:
1.线程A使用setnx(上锁的对象,超时的时间戳t1),如果返回true,获得锁。
2.线程B用get获取t1,与当前时间戳比较判断是是否超时,没超时false,若超时执行第3步;
3.计算新的超时时间t2,使用getset命令返回t3(该值可能其他线程已经修改过),如果t1==t3,获得锁,如果t1!=t3说明锁被其他线程获取了。
4.获取锁后,处理完业务逻辑,再去判断锁是否超时,如果没超时删除锁,如果已超时,不用处理(防止删除其他线程的锁)。
zk:
1.客户端对某个方法加锁时,在zk上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点node1;
2.客户端获取该路径下所有已经创建的子节点,如果发现自己创建的node1的序号是最小的,就认为这个客户端获得了锁。
3.如果发现node1不是最小的,则监听比自己创建节点序号小的最大的节点,进入等待。
在实践中,当然是从以可靠性为主。所以首推Zookeeper。
如何保证缓存与数据库双写时的数据一致性?
一般来说,就是如果你的系统不是严格要求缓存+数据库必须一致性的话,缓存可以稍微的跟数据库偶尔有不一致的情况.
最好不要做这个方案,读请求和写请求串行化,串到一个内存队列里去,这样就可以保证一定不会出现不一致的情况串行化之后,就会导致系统的吞吐量会大幅度的降低,用比正常情况下多几倍的机器去支撑线上的一个请求
Redis 有哪些数据类型及使用场景
字符串(strings)
string——适合最简单的 k-v 存储,类似于 memcached 的存储结构,短信验证码,配置信息等,就用这种类型来存储。
散列(hashes), 适用于:存储、读取、修改用户属性。
列表(lists),适用于:消息队列。
集合(sets),在微博应用中,可以将一个用户所有的关注人存在一个集合中,Redis 还为集合提供了求交集、并集、差集等操作,可以非常方便的实现如共同关注、共同喜好、 二度好友等功能,对上面的所有集合操作,你还可以使用不同的命令选择将结果返回给客户端还是存集到一个新的集合中。
有序集合(sorted sets ):适用于:排行榜
Redis当中有哪些数据结构
常用:字符串String、字典Hash、列表List、集合Set、有序集合SortedSet。HyperLogLog(基数统计)、Geo(地理位置)、 Pub/Sub(发布订阅)。
假如Redis里面有1亿个key, 其中有10w个key是以某个固定的已知的前缀开头的,如果将它们全部找出来?
使用keys指令可以扫出指定模式的key列表。
如果这个redis正在给线上的业务提供服务,那使用keys指令会有什么问题?
redis的单线程的。keys 指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用scan指令,scan 指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。
一个字符串类型的值能存储最大容量是多少?
512M
怎么理解 Redis 事务?
事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。
Redis 事务相关的命令有哪几个?
MULTI(开始)、EXEC(执行)、DISCARD(取消)、WATCH(监视)
Redis key 的过期时间和永久有效分别怎么设置?
EXPIRE(设置过期时间) 和 PERSIST(移除过期时间) 命令。
缓存与数据库不一致怎么办?
假设采用的主存分离,读写分离的数据库
如果一个线程A先删除缓存数据,然后将数据写入到主库当中,这个时候,主库和从库同步没有完成,线程B从缓存当中读取数据失败,从从库当中读取到旧数据,然后更新至缓存,这个时候,缓存当中的就是旧的数据。发生上述不一致的原因在于,主从库数据不一致问题,加入了缓存之后,主从不一致的时间被拉长了处理思路:在从库有数据更新之后,将缓存当中的数据也同时进行更新,即当从库发生了数据更新之后,向缓存发出删除,淘汰这段时间写入的旧数据。
主从数据库不一致如何解决
场景描述,对于主从库,读写分离,如果主从库更新同步有时差,就会导致主从库数据的不一致
1、忽略这个数据不一致,在数据一致性要求不高的业务下,未必需要时时一致性
2、强制读主库,使用一个高可用的主库,数据库读写都在主库,添加一个缓存,提升数据读取的性能。
3、选择性读主库,添加一个缓存,用来记录必须读主库的数据,将哪个库,哪个表,哪个主键,作为缓存的key,设置缓存失效的时间为主从库同步的时间,如果缓存当中有这个数据,直接读取主库,如果缓存当中没有这个主键,就到对应的从库中读取。
使用Redis做过异步队列吗,是如何实现的?
使用list 类型保存数据信息,rpush 生产消息,Ipop 消费消息,当Ipop没有消息时,可以sleep一段时间,然后再检查有没有信息,如果不想sleep的话,可以使用blpop,在没有信息的时候,会一直阻塞, 直到信息的到来。redis 可以通过pub/sub主题订阅模式实现一个生产者,多个消费者,当然也存在一定的缺点,当消费者下线时,生产的消息会丢失。
Redis 如何实现延时队列
使用sortedset,使用时间戳做score,消息内容作为key,调用zadd来生产消息,消费者使用zrangbyscore获取n秒之前的数据做轮询处理。
知道redis的持久化吗?底层如何实现的?有什么优点缺点?
rdb(redis database) 和 aof(append only file)默认开启 rdb, 如果两者同时开用 aof ,当 redis 重启的时候会优先载入 AOF 文件来恢复原始的数据,因为在通常情况下 AOF 文件保存的数据集要比 RDB 文件保存的数据集要完整。
RDB 优点:
1 适合大规模的数据恢复。
2 如果业务对数据完整性和一致性要求不高,RDB 是很好的选择。
rdb 缺点:
1 数据的完整性和一致性不高,因为 RDB 可能在最后一次备份时宕机了。
2 备份时占用内存,因为 Redis 在备份时会独立创建一个子进程,将数据写入到一个临时文件(此时内存中的数据是原来的两倍哦),最后再将临时文件替换之前的备份文件
AOF :Redis 默认不开启。它的出现是为了弥补 RDB 的不足(数据的不一致性),所以它采用日志的形式来记录每 个写操作,并追加到文件中。Redis 重启的会根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
AOF 优点:数据的完整性和一致性更高
AOF 缺点:因为 AOF 记录的内容多,文件会越来越大,数据恢复也会越来越慢。
bgsave做镜像全量持久化,aof 做增量持久化。因为bgsave会消耗比较长的时间,不够实时,在停机的时候会导致大量的数据丢失,需要aof来配合,在redis实例重启时,优先使用aof来恢复内存的状态,如果没有aof日志,就会使用rdb文件来恢复。Redis 会定期做aof重写,压缩aof文件日志大小。Redis4.0 之后有了混合持久化的功能,将bgsave的全量和aof的增量做了融合处理,这样既保证了恢复的效率又兼顾了数据的安全性。bgsave 的原理,fork 和cow, fork是指redis通过创建子进程来进行bgsave操作,cow指的是copy on write,子进程创建后,父子进程共享数据段,父进程继续提供读写服务,写的页面数据会逐渐和子进程分离开来。
如何选择合适的持久化方式?
一般来说, 如果想达到足以媲美 PostgreSQL 的数据安全性, 你应该同时使用两种持久化功能。如果你非常关心你的数据, 但仍然可以承受数分钟以内的数据丢失,那么你可以只使用 RDB 持久化。有很多用户都只使用 AOF 持久化,但并不推荐这种方式:因为定时生成 RDB 快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快,除此之外, 使用 RDB 还可以避免之前提到的 AOF 程序的 bug(4.0.9后修复了)。
Redis 的内存占用情况怎么样?
给你举个例子: 100 万个键值对(键是 0 到 999999 值是字符串“hello world”)在我的 32 位的笔记本上 用了 100MB。同样的数据放到一个 key里只需要 16M2B, 这是因为键值有一个很大的开销。 在 Memcached 上执行也是类似的结果,但是相对 Redis 的开销要小一点点,因为 Redis 会记录类型信息引用计数等等。
当然,大键值对时两者的比例要好很多。
64 位的系统比 32 位的需要更多的内存开销,尤其是键值对都较小时,这是因为 64 位的系统里指针占用了 8 个字节。当然,64 位系统支持更大的内存,所以为了运行大型的 Redis 服务器或多或少的需要使用 64 位的系统
都有哪些办法可以降低 Redis 的内存使用情况呢?
如果你使用的是 32 位的 Redis 实例,可以好好利用 Hash,list,sorted set,set 等集合类型数据,因为通常情况下很多小的 Key-Value 可以用更紧凑的方式存放到一起。
Redis 的内存用完了会发生什么?
如果达到设置的上限,Redis 的写命令会返回错误信息(但是读命令还可以正常返回。)name这个时候你可以将 Redis 当缓存来使用配置淘汰机制,当 Redis 达到内存上限时会冲刷掉旧的内容。
redis 是单线程的,为什么那么快
1)完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于 HashMap,而HashMap 的优势就是查找和操作的时间复杂度都是O
2)数据结构简单,对数据操作也简单,Redis 中的数据结构是专门进行设计的
3)采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗
4)使用多路 I/O 复用模型,非阻塞 IO
5)使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis 直接自己构建了 VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求
Redis 是单线程的,如何提高多核 CPU 的利用率?
可以在同一个服务器部署多个 Redis 的实例,并把他们当作不同的服务器来使用,在某些时候,无论如何一个服务器是不够的, 所以,如果你想使用多个 CPU,你可以考虑一下分片(shard)。
Redis 相比 memcached 有哪些优势?
(1) memcached 所有的值均是简单的字符串,redis 作为其替代者,支持更为丰富的数据类型
(2) redis 的速度比 memcached 快很多
(3) redis 可以持久化其数据
在选择缓存时,什么时候选择redis,什么时候选择memcached
选择redis的情况:
1、复杂数据结构,value 的数据是哈希,列表,集合,有序集合等这种情况下,会选择redis,因为memcache无法满足这些数据结构,最典型的的使用场景是 ,用户消息,帖子评论等。
2、需要进行数据的持久化功能,但是注意,不要把redis当成数据库使用,如果redis挂了,内存能够快速恢复热数据,不会将压力瞬间压在数据库上,没有cache预热的过程。对于只读和数据一致性要求不高的场景可以采用持久化存储
3、高可用,redis 支持集群,可以实现主动复制,读写分离,而对于memcache如果想要实现高可用,需要进行二次开发。
4、存储的内容比较大,memcache 存储的value最大为1M。
选择memcache的场景:
1、纯KV,数据量非常大的业务,使用memcache更合适,原因是
a)memcache的内存分配采用的是预分配内存池的管理方式,能够省去内存分配的时间,redis 是临时申请空间,可能导致碎片化。
b)虚拟内存使用,memcache将所有的数据存储在物理内存里,redis 有自己的vm机制,理论上能够存储比物理内存更多的数据,当数据超量时,引发swap,把冷数据刷新到磁盘上,从这点上,数据量大时,memcache更快
c)网络模型,memcache 使用非阻塞的I0复用模型,redis 也是使用非阻塞的I0复用模型,但是redis还提供了一些非KV存储之外的排序,聚合功能,复杂的CPU计算,会阻塞整个i0调度,从这点上由于redis 提供的功能较多,memcache 更快些
d)线程模型,memcache使用多线程,主线程监听,worker子线程接受请求,执行读写,这个过程可能存在锁冲突。redis 使用的单线程,虽然无锁冲突,但是难以利用多核的特性提升吞吐量。
LRU算法知道吗?写一下Java代码实现一下?
class LRUCache extends LinkedHashMap {
private final int CACHE_SIZE;
/**
* 传递进来最多能缓存多少数据
*
* @param cacheSize 缓存大小
*/
public LRUCache(int cacheSize) {
// true 表示让 linkedHashMap 按照访问顺序来进行排序,最近访问的放在头部,最老访问的放在尾部。
super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true);
CACHE_SIZE = cacheSize;
}
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
// 当 map中的数据量大于指定的缓存个数的时候,就自动删除最老的数据。
return size() > CACHE_SIZE;
}
}
LRU算法实现:
1.通过双向链表来实现,新数据插入到链表头部;每当缓存命中(即缓存数据被访问),则将数据移到链表头部;当链表满的时候,将链表尾部的数据丢弃。
LinkedHashMap:HashMap和双向链表集合= E为一即是LinkedHashMap。
HashMap是无序的,LinkedHashMap 通过维护一个额外的双向链表保证了迭代顺序。该迭代顺序可以是插入顺序(默认),也可以是访问顺序。
修改配置不重启 Redis 会实时生效吗?
针对运行实例,有许多配置选项可以通过 CONFIG SET 命令进行修改,而无需执行任何形式的重启。 从 Redis 2.2 开始,可以从 AOF 切换到 RDB 的快照持久性或其他方式而不需要重启 Redis。检索 ‘CONFIG GET *’ 命令获取更多信息。
但偶尔重新启动是必须的,如为升级 Redis 程序到新的版本,或者当你需要修改某些目前 CONFIG 命令还不支持的配置参数的时候。
Redis 缓存处理流程
前台请求,后台先从缓存中取数据,取到直接返回结果,取不到时从数据库中取,数据库取到更新缓存,并返回结果,数据库也没取到,那直接返回空结果。
Redis 缓存穿透
描述:缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为 id 为“-1”的数据或 id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
解决方案:
(1)缓存层缓存空值,并给个空值过期时间
(2)布隆过滤器:将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对DB的查询。
Redis 缓存雪崩
描述:
缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至 down 机。和缓存击穿不同的是, 缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决方案:
将缓存失效时间分散开,比如可以在原有的失效时间基础上增加一个随机值,比如 1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
Redis 缓存击穿
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
解决方案:
1.使用互斥锁:当缓存失效时,不立即去load db,先使用如Redis的setnx去设置一个互斥锁,当操作成功返回时再进行load db的操作并回设缓存,否则重试get缓存的方法。
2.永远不过期:物理不过期,但逻辑过期(后台异步线程去刷新)。
哨兵模式(Sentinel)模式?工作原理是什么?
如果 Master 异常,则会进行 Master-Slave 切换,通过投票的方式将其中一台Slae 作为 Master,将之前的 Master 作为 Slave
下线:
①主观下线:Subjectively Down,简称 SDOWN,指的是当前 Sentinel 实例对某个 redis 服务器做出的下线判断。
②客观下线:Objectively Down, 简称 ODOWN,指的是多个 Sentinel 实例在对 Master(服务器) 做出 SDOWN 判断,并且通过 Sentinel
is-master-down-by-addr 命令互相交流之后,得出的 Master服务器下线判断,然后开启 failover.
工作原理:
(1)每个 Sentinel 以每秒钟一次的频率向它所知的 Master,Slave 以及其他 Sentinel 实例发送一个 PING 命令
(2)如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 则这个实例会被 Sentinel 标记为主观下线;
(3)如果一个 Master 被标记为主观下线,则正在监视这个 Master 的所有 Sentinel 要以每秒一次的频率确认 Master 的确进入了主观下线状态;
(4)当有足够数量的 Sentinel(大于等于配置文件指定的值)在指定的时间范围内确认 Master 的确进入了主观下线状态, 则 Master 会被标记为客观下线 ;
(5)在一般情况下, 每个 Sentinel 会以每 10 秒一次的频率向它已知的所有 Master,Slave 发送 INFO 命令
(6)当 Master 被 Sentinel 标记为客观下线时,Sentinel 向下线的 Master 的所有 Slave 发送 INFO 命令的频率会从 10 秒一次改为每秒一次 ;
(7)若没有足够数量的 Sentinel 同意 Master 已经下线, Master 的客观下线状态就会被移除;若 Master 重新向 Sentinel 的 PING 命令返回有效回复, Master 的主观下线状态就会被移除;
悲观锁
执行操作前假设当前的操作肯定(或有很大几率)会被打断(悲观)。基于这个假设,我们在做操作前就会把相关资源锁定,不允许自己执行期间有其他操作干扰。
Redis 不支持悲观锁。Redis 作为缓存服务器使用时,以操作为主,很少写操作,相应的操作被打断的几率较少。不采用悲观锁是为了防止降低性能。
乐观锁
执行操作前假设当前操作不会被打断(乐观)。基于这个假设,我们在做操作前不会锁定资源,万一发生了其他操作的干扰,那么本次操作将被放弃。
什么是消息队列
可以把消息队列比作是一个存放消息的容器,当我们需要使用消息的时候可以取出消息供自己使用。
消息队列是分布式系统中重要的组件,使用消息队列主要是为了通过异步处理提高系统性能和削峰、降低系统耦合性。目前使用较多的消息队列有ActiveMQ,RabbitMQ,Kafka,RocketMQ。
为什么要用消息队列
主要有两点好处:
1.通过异步处理提高系统性能(削峰、减少响应所需时间);
2.降低系统耦合性。
(1) 通过异步处理提高系统性能(削峰、减少响应所需时间)
在不使用消息队列服务器的时候,用户的请求数据直接写入数据库,在高并发的情况下数据库压力剧增,使得响应速度变慢。但是在使用消息队列之后,用户的请求数据发送给消息队列之后立即返回,再由消息队列的消费者进程从消息队列中获取数据,异步写入数据库。由于消息队列服务器处理速度快于数据库(消息队列也比数据库有更好的伸缩性),因此响应速度得到大幅改善。通过以上分析我们可以得出消息队列具有很好的削峰作用的功能—>即通过异步处理,将短时间高并发产生的事务消息存储在消息队列中,从而削平高峰期的并发事务。
举例:在电子商务一些秒杀、促销活动中,合理使用消息队列可以有效抵御促销活动刚开始大量订单涌入对系统的冲击,因为用户请求数据写入消息队列之后就立即返回给用户了,但是请求数据在后续的业务校验、写数据库等操作中可能失败。因此使用消息队列进行异步处理之后,需要适当修改业务流程进行配合,比如用户在提交订单之后,订单数据写入消息队列,不能立即返回用户订单提交成功,需要在消息队列的订单消费者进程真正处理完该订单之后,甚至出库后,再通过电子邮件或短信通知用户订单成功,以免交易纠纷。
(2) 降低系统耦合性
我们知道如果模块之间不存在直接调用,那么新增模块或者修改模块就对其他模块影响较小,这样系统的可扩展性无疑更好一些。
最常见的事件驱动架构类似生产者消费者模式,在大型网站中通常利用发布-订阅模式工作,消息发送者(生产者)发布消息,一个或多个消息接受者(消费者)订阅消息。他们之间没有直接耦合,消息发送者将消息发送至分布式消息队列即结束对消息的处理,消息接受者从分布式消息队列获取该消息后进行后续处理,并不关心该消息从何而来。对新增业务,只要对该类消息感兴趣,即可订阅该消息,对原有系统和业务没有任何影响,从而实现网站业务的可扩展性设计。
消息接受者对消息进行过滤、处理、包装后,构造成一个新的消息类型,将消息继续发送出去,等待其他消息接受者订阅该消息。可以理解为他是一系列流程。另外为了避免消息队列服务器宕机造成消息丢失,他会将成功发送到消息队列的消息存储在消息生产者服务器上,等消息真正被消费者服务器处理后才删除消息。在消息队列服务器宕机后,生产者服务器会选择分布式消息队列服务器集群中的其他服务器发布消息。
使用消息队列带来的一些问题
系统可用性降低: 系统可用性在某种程度上降低,为什么这样说呢?在加入MQ之前,你不用考虑消息丢失或者说MQ挂掉等等的情况,但是,引入MQ之后你就需要去考虑了!
系统复杂性提高: 加入MQ之后,需要保证消息没有被重复消费、处理消息丢失的情况、保证消息传递的顺序性等等问题!
一致性问题: 消息队列可以实现异步,消息队列带来的异步确实可以提高系统响应速度。但是,万一消息的真正消费者并没有正确消费消息就会导致数据不一致的情况了!
JMS 简介
JMS(JAVA Message Service,java消息服务)是java的消息服务,JMS的客户端之间可以通过JMS服务进行异步的消息传输。他是一个消息服务的标准或者说是规范,允许应用程序组件基于JavaEE平台创建、发送、接收和读取消息。它使分布式通信耦合度更低,消息服务更加可靠以及异步性。ActiveMQ 就是基于 JMS 规范实现的。
JMS两种消息模型
点到点(P2P)模型
(Queue)队列作为消息通信载体;满足生产者与消费者模式,一条消息只能被一个消费者使用,未被消费的消息在队列中保留直到被消费或超时。
发布/订阅(Pub/Sub)模型
(Topic)作为消息通信载体,类似于广播模式;发布者发布一条消息,该消息通过主题传递给所有的订阅者,在一条消息广播之后才订阅的用户则是收不到该条消息的。
JMS 五种不同的消息正文格式
JMS定义了五种不同的消息正文格式,以及调用的消息类型,允许你发送并接收以一些不同形式的数据,提供现有消息格式的一些级别的兼容性。
StreamMessage – Java原始值的数据流
MapMessage–一套名称-值对
TextMessage–一个字符串对象
ObjectMessage–一个序列化的 Java对象
BytesMessage–一个字节的数据流
AMQP
AMQP,即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消息队列协议(二进制应用层协议),是应用层协议的一个开放标准,为面向消息的中间件设计,兼容 JMS。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件同产品,不同的开发语言等条件的限制。RabbitMQ 就是基于 AMQP 协议实现的。
JMS和AMQP有什么区别?
AMQP 为消息定义了线路层(wire-level protocol)的协议,而JMS所定义的是API规范。在 Java 体系中,多个client均可以通过JMS进行交互,不需要应用修改代码,但是其对跨平台的支持较差。而AMQP天然具有跨平台、跨语言特性。
JMS 支持TextMessage、MapMessage 等复杂的消息类型;而 AMQP 仅支持 byte[] 消息类型(复杂的类型可序列化后发送)。
由于Exchange 提供的路由算法,AMQP可以提供多样化的路由方式来传递消息到消息队列,而 JMS 仅支持队列和主题/订阅方式两种。
常见的消息队列对比
ActiveMQ 的社区算是比较成熟,但是较目前来说,ActiveMQ 的性能比较差,而且版本迭代很慢,不推荐使用。
RabbitMQ 在吞吐量方面虽然稍逊于 Kafka 和 RocketMQ ,但是由于它基于 erlang 开发,所以并发能力很强,性能极其好,延时很低,达到微秒级。但是也因为 RabbitMQ 基于 erlang 开发,所以国内很少有公司有实力做erlang源码级别的研究和定制。如果业务场景对并发量要求不是太高(十万级、百万级),RabbitMQ 一定是你的首选。
RocketMQ 阿里出品,Java系的开源项目,源代码我们可以直接阅读,然后可以定制自己公司的MQ,并RocketMQ 有阿里巴巴的实际业务场景的实战考验。RocketMQ 社区活跃度相对较为一般!
kafka 的特点其实很明显,就是仅仅提供较少的核心功能,但是提供超高的吞吐量,ms(毫秒) 级的延迟,极高的可用性以及可靠性,而且分布式可以任意扩展。同时 kafka 最好是支撑较少的 topic 数量即可,保证其超高吞吐量。 kafka 唯一的一点劣势是有可能消息重复消费,那么对数据准确性会造成极其轻微的影响,在大数据领域中以及日志采集中,这点轻微影响可以忽略不计,这个特性天然适合大数据实时计算以及日志收集。
RabbitMQ 是什么
RabbitMQ 是一个开源的消息代理和队列服务器,通过普通协议在完全不同的应用之间共享数据, 他是使用 Erlang 语言来编写的。
ConnectionFactory(连接管理器):应用程序与 RabbitMQ 之间建立连接的管理器,程序代码中使用;
Channel(信道):消息推送使用的通道;
Exchange(交换器):用于接受、分配消息;
Queue(队列):用于存储生产者的消息;
RoutingKey(路由键):用于把生成者的数据分配到交换器上;
BindingKey(绑定键):用于把交换器的消息绑定到队列上;
RabbitMQ 消息持久化
消息持久化
RabbitMQ 队列默认情况下重启服务器会导致消息丢失,那么怎么保证 Rabbit 在重启的时候不丢失呢?答案就是消息持久化。
设置消息持久化必须先设置队列持久化,要不然队列不持久化,消息持久化,队列都不存在了,消息存在还有什么意义。消息持久化需要将交换机持久化、队列持久化、消息持久化,才能最终达到持久化的目的。
持久化的缺点
消息持久化的优点显而易见,但缺点也很明显,那就是性能,因为要写入硬盘要比写入内存性能较低很多, 从而降低了服务器的吞吐量
RabbitMQ中的Broker是指什么?Cluster又是指什么?
Broker(代理服务器)是指一个或多个erlang node的逻辑分组,且node上运行着RabbitMQ应用程序。Cluster(集群) 是在broker的基础之上,增加了node之间共享元数据的约束。
什么是元数据?元数据分为哪些类型?包括哪些内容?与Cluster 相关的元数据有哪些?元数据是如何保存的?元数据在Custer中是如何分布的?
在非Cluster 模式下,元数据主要分为Queue元数据(queue名字和属性等)、Exchange 元数据(exchange名字、类型和属性等)、Binding 元数据(存放路由关系的查找表)、Vhost 元数据(vhost范围内针对前三者的名字空间约束和安全属性设置)。在Cluster模式下,还包括Cluster中node位置信息和node关系信息。元数据按照erlang node的类型确定是仅保存于RAM(内存)中,还是同时保存在RAM和Disk(磁盘)上。元数据在Cluster中是全node分布的。
RAM node和Disk node的区别?
RAM node仅将fabric(即queue、exchange 和binding等RabbitMQ基础构件)相关元数据保存到内存中,但Disk node会在内存和磁盘中均进行存储。RAM node上唯一会存储到磁盘上的元数据是Cluster中使用的Disk node的地址。要求在RabbitMQ Cluster中至少存在一个Disk node。
RabbitMQ上的一个Queue中存放的Message是否有数量限制?
可以认为是无限制,因为限制取决于机器的内存,但是消息过多会导致处理效率的下降。
在单Node系统和多Node构成的Cluster系统中声明Queue、Exchange , 以及进行Binding会有什么不同?
当你在单node上声明Queue时,只要该node上相关元数据进行了变更,你就会得到Queue Declare-ok回应;而在Cluster上声明Queue则要求Cluster上的全部node都要进行元数据成功更新,才会得到Queue Declare-ok回应。另外,若node类型为RAM node则变更的数据仅保存在内存中,若类型为Disk node则还要变更保存在磁盘上的数据。
客户端连接到Cluster中的任意node上是否都能正常工作?
是的。客户端感觉不到有何不同。
若Cluster中拥有某个Queue的Owner node失效了,且该Queue被声明具有Durable属性,是否能够成功从其他node上重新声明该Queue ?
不能,在这种情况下,将得到404 NOT_ FOUND错误。只能等queue所属的node恢复后才能使用该queue。但若该queue本身不具有durable属性,则可在其他node上重新声明。
Cluster 中node的失效会对Consumer(消费者)产生什么影响?若是在Cluster中创建了Mirrored Queue(镜像队列),这时node失效会对Consumer产生什么影响?
若是consumer所连接的那个node失效(无论该node是否为consumer所订阅queue的owner node),则Consumer会在发现TCP连接断开时,按标准行为执行重连逻辑,并根据“Assume Nothing"原则重建相应的fabric即可。 若是失效的node为Consumer订阅Queue的Owner node,则Consumer只能通过6机制来检测与该queue订阅关系的终止,否则会出现傻等却没有任何消息来到的问题。
能够在地理上分开的不同数据中心使用RabbitMQ Cluster么?
不能。
第一,你无法控制所创建的Queue实际分布在Cluster里的哪个node上(一般使用HAProxy + cluster模型时都是这样),这可能会导致各种跨地域访问时的常见问题;
第二,Erlang的OTP通信框架对延迟的容忍度有限,这可能会触发各种超时,导致业务疲于处理;
第三,在广域网上的连接失效问题将导致经典的“脑裂”问题,而RabbitMQ目前无法处理(该问题主要是说Mnesia)。 脑裂(brain-split):脑裂是指在主备切换时,由于切换不彻底或其他原因,导致客户端和Slave误以为出现两个active master,最终使得整个集群处于混乱状态解决脑裂问题,通常采用隔离(Fencing)机制 包括三个方面: 共享存储fencing:确保只有一个Master往共享存储中写数据。 客户端fencing:确保只有一个Master可以响应客户端的请求。 Slave fencing:确保只有一个Master可以向Slave下发命令。
为什么Heavy RPC的使用场景下不建议采用disk node?
Heavy RPC是指在业务逻辑中高频调用RabbitMQ提供的RPC机制,导致不断创建、销毁Reply Queue,进而造成Disk node的性能问题(因为会针对元数据不断写盘)。所以在使用RPC机制时需要考虑自身的业务场景。
向不存在的Exchange(交换器)发Publish(发布)消息会发生什么?向不存在的Queue执行Consume(消费)动作会发生什么?
都会收到Channel Close信令告之不存在(内含原因404 NOT_ FOUND)。
Routing_ key(路由键)和Binding_ key(绑定路由键)的最大长度是多少?
255字节
RabbitMQ允许发送的Message最大可达多大?
根据AMQP协议规定,消息体的大小由64-bit 的值来指定,所以你就可以知道到底能发多大的数据了。
什么情况下Producer(消费)不主动创建Queue是安全的?
1.Message是允许丢失的;
2.实现了针对未处理消息的Republish功能(例如采用Publisher Confirm机制)。
为什么说保证Message被可靠持久化的条件是Queue和Exchange具有Durable属性,同时Message具有Persistent(持久化)属性才行?
Binding关系可以表示为Exchange - Binding - Queue。从文档中我们知道,若要求投递的Message能够不丢失,要求Message本身设置Persistent属性,要求Exchange和Queue都设置Durable属性。其实这问题可以这么想,若Exchange或Queue未设置Durable属性,则在其Crash之后就会无法恢复,那么即使Message设置了Persistent 属性,仍然存在Message虽然能恢复但却无处容身的问题;同理若Message本身未设置Persistent属性,则Message的持久化更无从谈起。
什么情况下会出现Blackholed问题?
Blackholed问题是指向Exchange投递了Message ,而由于各种原因导致该Message丢失,但发送者却不知道。可导致Blackholed的情况:
1.向未绑定Queue的Exchange发送Message;
2.Exchange以Binding_ key key A绑定了Queue queue A,但向该Exchange发送Message使用的Routing_ key却是key_ B。
如何防止出现Blackholed问题?
没有特别好的办法,只能在具体实践中通过各种方式保证相关fabric的存在。另外,如果在执行BasicPublish时设置mandatory=true,则在遇到可能出现Blackholed情况时服务器会通过返回BasicReturn告之当前Message无法被正确投递(内含原因312 NO_ _ROUTE)。
Consumer Cancellation Notification机制用于什么场景?
用于保证当镜像Queue中Master挂掉时,连接到Slave上的Consumer可以收到自身Consume被取消的通知,进而可以重新执行Consume动作从新选出的Master出来获得消息。若不采用该机制,连接到Save上的Consumer将不会感知Master挂掉这个事情,导致后续无法再收到新Master广播出来的Message。另外,因为在镜像Queue模式下,存在将Message进行Requeue(重新入队)的可能,所以实现Consumer的逻辑时需要能够正确处理出现重复Message的情况。
为什么不应该对所有的Message都使用持久化机制?
首先,必然导致性能的下降,因为写磁盘比写RAM慢的多,Message 的吞吐量可能有10 倍的差距。其次Message的持久化机制用在RabbitMQ的内置Cluster方案时会出现“坑爹”问题。矛盾点在于,若Message设置了Persistent 属性,但Queue未设置Durable属性,那么当该Queue的Owner node出现异常后,在未重建该Queue前,发往该Queue的Message将被Blackholed ;若Message设置了Persistent 属性同时Queue也设置了Durable 属性,那么当Queue的Owner node异常且无法重启的情况下,则该Queue无法在其他node上重建,只能等待其Owner node重启后才能恢复该Queue的使用,而在这段时间内发送给该Queue的Message将被Blackholed。
所以,是否要对Message进行持久化,需要综合考虑性能需要,以及可能遇到的问题。若想达到100,000条/秒以上的消息吞吐量(单RabbitMQ服务器),则要么使用其他的方式来确保Message的可靠Delivery(送达) ,要么使用非常快速的存储系统以支持全持久化(例如使用SSD)。另外一种处理原则是:仅对关键消息作持久化处理(根据业务重要程度),且应该保证关键消息的量不会导致性能瓶颈。
RabbitMQ中的Cluster、Mirrored Queue,以及Warrens机制分别用于解决什么问题?存在哪些问题?
Cluster是为了解决当Cluster中的任意node失效后,Producer 和Consumer均可以通过其他node继续工作,提高了可用性;另外可以通过增加node数量增加Cluster的消息吞吐量。Cluster 本身不负责Message的可靠性问题(该问题由producer通过各种机制自行解决);Cluster无法解决跨数据中心的问题(即脑裂问题)。另外,在Cluster前使用HAProxy可以解决node的选择问题,即业务无需知道Cluster中多个node的ip地址。可以利用HAProxy进行失效node的探测,负载均衡。
Mirrored Queue是为了解决使用Cluster时所创建的Queue的完整信息仅存在于单一node上的问题,从另一个角度增加可用性。若想正确使用该功能,需要保证:
1.Consumer需要支持Consumer Cancellation Notification机制;
2.Consumer必须能够正确处理重复Message。
Warrens是为了解决Cluster中Message可能被Blackholed的问题,即不能接受Producer不停Republish Message但RabbitMQ server无回应的情况。Warrens有两种构成方式:
一种模型是两台独立的RabbitMQ server + HAProxy,其中两个server的状态分别为active和hot-standby(热备用)。该模型的特点为:两台server之间无任何数据共享和协议交互,两台server可以基于不同的RabbitMQ版本。
另一种模型为两台共享存储的RabbitMQ server + keepalived,其中两个server的状态分别为active和cold-standby(冷备用)。该模型的特点为:两台server基于共享存储可以做到完全恢复,要求必须基于完全相同的RabbitMQ版本。
Warrens模型存在的问题:
对于第一种模型,虽然理论上讲不会丢失消息,但若在该模型上使用持久化机制,就会出现这样一种情况,即若作为active的server异常后,持久化在该server上的消息将暂时无法被consume,因为此时该queue将无法在作为hot-standby的server上被重建,所以,只能等到异常的active server恢复后,才能从其上的queue中获取相应的message进行处理。
对于第二种模型,因为是基于共享存储的模式,所以导致active server 异常的条件,可能同样会导致cold-standby server异常;另外,在该模型下,要求active和cold-standby的server必须具有相同的node名和UID,否则将产生访问权限问题;最后,由于该模型是冷备方案,故无法保证cold-standby server能在你要求的时限内成功启动。
RabbitMQ Vhost
Vhost可以理解为虚拟Broker,即mini-RabbitMQ server,其内部均含有独立的Queue、Exchange和Binding等,但最最重要的是,其拥有独立的权限系统,可以做到Vhost范围的用户控制。当然,从RabbitMQ的全局角度,Vhost 可以作为不同权限隔离的手段(一个典型的例子就是不同的应用可以跑在不同的Vhost中)。
RabbitMQ 概念里的 Channel、Exchange 和 Queue 是逻辑概念,还是对应着进程实体?分别起什么作用?
Queue 具有自己的 erlang 进程;Exchange 内部实现为保存 Binding 关系的查找表;Channel 是实际进行路由工作的实体,即负责按照 Routing_key 将 Message 投递给 Queue 。由 AMQP 协议描述可知,Channel 是真实 TCP 连接之上的虚拟连接,所有 AMQP 命令都是通过 Channel 发送的,且每一个 Channel 有唯一的 ID。一个 Channel 只能被单独一个操作系统线程使用,故投递到特定 Channel 上的 Message 是有顺序的。但一个操作系统线程上允许使用多个 Channel
消息基于什么传输?
由于TCP连接的创建和销毁开销较大,且并发数受系统资源限制,会造成性能瓶颈。RabbitMQ使用信道的方式来传输数据。信道是建立在真实的TCP连接内的虚拟连接,且每条TCP连接上的信道数量没有限制。
消息如何分发?
若该队列至少有一个消费者订阅,消息将以循环(round-robin)的方式发送给消费者。每条消息只会分发给一个订阅的消费者(前提是消费者能够正常处理消息并进行确认)。
如何防止生产者弄丢数据?如何防止 Rabbit 弄丢数据?
可以开启 Confirm(发送方确认模式),在生产者那里设置开启 Confirm 模式之后,你每次写的消息都会分配一个唯一的 id,然后写入了 Rabbitmq 中,Rabbitmq 会给你回传一个 ACK 消息,告诉你说这个消息 OK了。如果 Rabbitmq 没能处理这个消息,会回调你一个 Nack 接口,告诉你这个消息接收失败。而且你可以结合这个机制自己在内存里维护每个消息 id 的状态,如果超过一定时间还没接收到这个消息的回调,那么你可以重发!
补充:也可以用事务机制但事务机制和 Cnofirm 机制最大的不同在于,事务机制是同步的,你提交一个事务之后会阻塞在那儿,但是 Confirm 机制是异步的,你发送个消息之后就可以发送下一个消息,然后那个消息 Rabbitmq 接收了之后会异步回调你一个接口通知你这个消息接收到了
如何保证消息的可靠性投递?如何确保消息正确地发送至 RabbitMQ?
发送方确认模式:
将信道设置成Confirm模式(发送方确认模式),则所有在信道上发布的消息都会被指派一个唯一的ID。一旦消息被投递到目的队列后,或者消息被写入磁盘后(可持久化的消息),信道会发送一个确认给生产者(包含消息唯一ID)。如果RabbitMQ发生内部错误从而导致消息丢失,会发送一条Nack(not acknowledged,未确认)消息。发送方确认模式是异步的,生产者应用程序在等待确认的同时,可以继续发送消息。当确认消息到达生产者应用程序,生产者应用程序的回调方法就会被触发来处理确认消息。
接收方消息确认机制: 消费者接收每一条消息后都必须进行确认(消息接收和消息确认是两个不同操作)。只有消费者确认了消息,RabbitMQ才能安全地把消息从队列中删除。这里并没有用到超时机制,RabbitMQ仅通过Consumer的连接中断来确认是否需要重新发送消息。也就是说,只要连接不中断,RabbitMQ给了Consumer足够长的时间来处理消息。保证数据的最终一致性;
下面罗列几种特殊情况:
如果消费者接收到消息,在确认之前断开了连接或取消订阅,RabbitMQ会认为消息没有被分发,然后重新分发给下一个订阅的消费者。(可能存在消息重复消费的隐患,需要去重)如果消费者接收到消息却没有确认消息,连接也未断开,则RabbitMQ认为该消费者繁忙,将不会给该消费者分发更多的消息。
消息幂等性
生产者方面:可以对每条消息生成一个msgID,以控制消息重复投递 消费者方面:消息体中必须携带一个业务ID,如银行流水号,消费者可以根据业务ID去重,避免重复消费
//消费者
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
porperties.messageId(String.valueOF(UUID.randomUUID()))
消息如何被优先消费?
//生产者
Map argss = new HashMap();
argss.put("x-max-priority",10);
//消费者
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
.priority(5) // 优先级,默认为5,配合队列的 x-max-priority 属性使用
如何保证消息的顺序性
一个队列只有一个消费者的情况下才能保证顺序,否则只能通过全局ID实现(每条消息都一个msgId,关联的消息拥有一个parentMsgId。可以在消费端实现前一条消息未消费,不处理下一条消息;也可以在生产端实现前一条消息未处理完毕,不发布下一条消息)
RabbitMQ的集群模式和集群节点类型
普通模式:默认模式,以两个节点(rabbit01,rabbit02)为例来进行说明,对于Queue来说,消息实体只存在于其中一个节点,两个节点仅有相同的元数据,即队列结构。当消息进入rabbit01节点的Queue后,Consumer从Rabbit02节点消费时,RabbitMQ会临时在Rabbit01,Rabbit02间进行消息传输,把A中的消息实体取出并经过B发送给Consumer,所以Consumer应尽量连接每一个节点,从中取消息。即对于同一个逻辑队列,要在多个节点建立物理Queue。否则无论consumer连rabbit01或rabbit02,出口总在rabbit01,会产生瓶颈。当rabbit01节点故障后,rabbit02节点无法取到rabbit01节点中还未消费的消息实体。如果做了消息持久化,那么等到rabbit01节点恢复,然后才可被消费。如果没有消息持久化,就会产生消息丢失的现象。
镜像模式:把需要的队列做成镜像队列,存在与多个节点属于RabibitMQ的HA方案,该模式解决了普通模式中的问题,其实质和普通模式不同之处在于,消息体会主动在镜像节点间同步,而不是在客户端取数据时临时拉取,该模式带来的副作用也很明显,除了降低系统性能外,如果镜像队列数量过多,加之大量的消息进入,集群内部的网络带宽将会被这种同步通讯大大消耗掉,所以在对可靠性要求比较高的场合中适用
节点分为内存节点(保存状态到内存,但持久化的队列和消息还是会保存到磁盘),磁盘节点(保存状态到内存和磁盘),一个集群中至少需要一个磁盘节点
如何自动删除长时间没有消费的消息
// 通过队列属性设置消息过期时间
Map argss = new HashMap();
argss.put("x-message-ttl",6000);
// 对每条消息设置过期时间
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
.expiration("10000") // TTL
如何确保消息接收方消费了消息?
接收方消息确认机制:消费者接收每一条消息后都必须进行确认(消息接收和消息确认是两个不同操作)。只有消费者确认了消息,RabbitMQ才能安全地把消息从队列中删除。这里并没有用到超时机制,RabbitMQ仅通过Consumer的连接中断来确认是否需要重新发送消息。也就是说,只要连接不中断,RabbitMQ给了Consumer足够长的时间来处理消息。
下面罗列几种特殊情况:
如何避免消息重复投递或重复消费?
在消息生产时,MQ 内部针对每条生产者发送的消息生成一个 id,作为去重和幂等的依据(消息投递失败并重传), 避免重复的消息进入队列;在消息消费时,要求消息体中必须要有一个 id(对于同一业务全局唯一,如支付 ID、订单ID、帖子 ID 等)作为去重和幂等的依据,避免同一条消息被重复消费。
死信队列和延迟队列的使用?
死信消息:
过期消息:
在 rabbitmq 中存在2种方可设置消息的过期时间,第一种通过对队列进行设置,这种设置后,该队列中所有的消息都存在相同的过期时间,第二种通过对消息本身进行设置,那么每条消息的过期时间都不一样。如果同时使用这2种方法,那么以过期时间小的那个数值为准。当消息达到过期时间还没有被消费,那么那个消息就成为了一个死信消息。
队列设置:在队列申明的时候使用 x-message-ttl 参数,单位为 毫秒
单个消息设置:是设置消息属性的 expiration 参数的值,单位为 毫秒
消息队列的作用与使用场景
异步:批量数据异步处理(批量上传文件)
削峰:高负载任务负载均衡(电商秒杀抢购)
解耦:串行任务并行化(退货流程解耦)
广播:基于Pub/Sub实现一对多通信
多个消费者监听一个队列时,消息如何分发?
轮询: 默认的策略,消费者轮流,平均地接收消息
公平分发: 根据消费者的能力来分发消息,给空闲的消费者发送更多消息
//当消费者有x条消息没有响应ACK时,不再给这个消费者发送消息
channel.basicQos(int x)
无法被路由的消息去了哪里?
无设置的情况下,无法路由(Routing key错误)的消息会被直接丢弃
解决方案:
将mandatory设置为true,并配合ReturnListener,实现消息的回发
声明交换机时,指定备份的交换机
Map arguments = new HashMap();
arguments.put("alternate-exchange","备份交换机名");
RabbitMQ如何实现延时队列?
延时队列:在rabbitmq中不存在延时队列,但是我们可以通过设置消息的过期时间和死信队列来模拟出延时队列。消费者监听死信交换器绑定的队列,而不要监听消息发送的队列。
利用TTL(队列的消息存活时间或者消息存活时间),加上死信交换机
// 设置属性,消息10秒钟过期
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
.expiration("10000") // TTL
// 指定队列的死信交换机
Map arguments = new HashMap();
arguments.put("x-dead-letter-exchange","DLX_EXCHANGE");
消息怎么路由?
从概念上来说,消息路由必须有三部分:交换器、路由、绑定。生产者把消息发布到交换器上;绑定决定了消息如何从路由器路由到特定的队列;消息最终到达队列,并被消费者接收。
消息发布到交换器时,消息将拥有一个路由键(routing key),在消息创建时设定。
通过队列路由键,可以把队列绑定到交换器上。
消息到达交换器后,RabbitMQ会将消息的路由键与队列的路由键进行匹配(针对不同的交换器有不同的路由规则)。如果能够匹配到队列,则消息会投递到相应队列中;如果不能匹配到任何队列,消息将进入 “黑洞”。
RabbitMQ 常用交换器
direct:如果路由键完全匹配,消息就被投递到相应的队列
fanout:如果交换器收到消息,将会广播到所有绑定的队列上
topic:通配交换器
Mycat 是什么
MyCat 是一个开源的分布式数据库系统,是一个实现了 MySQL 协议的服务器,前端用户可以把它看作是一 个数据库代理,用 MySQL 客户端工具和命令行访问,而其后端可以用 MySQL 原生协议与多个 MySQL 服务器通信, 也可以用 JDBC 协议与大多数主流数据库服务器通信,其核心功能是分表分库
Mycat 有哪些功能和特性
支持 JDBC 连接多数据库
支持 NoSQL 数据库
自动故障切换,高可用性
支持读写分离,支持 Mysql 双主多从,以及一主多从的模式
支持自增长主键、支持 Oracle 的序列 机制
为什么要用Mycat?
① Java与数据库紧耦合。
② 高访问量高并发对数据库的压力。
③ 读写请求数据不一致
数据库中间件对比
① Cobar属于阿里B2B事业群,始于2008年,在阿里服役3年多,接管3000+个MySQL数据库的schema,集群日处理在线SQL请求50亿次以上。由于Cobar发起人的离职,Cobar停止维护。
② Mycat是开源社区在阿里cobar基础上进行二次开发,解决了cobar存在的问题,并且加入了许多新的功能在其中。青出于蓝而胜于蓝。
③ OneProxy基于MySQL官方的proxy思想利用c进行开发的,OneProxy是一款商业收费的中间件。舍弃了一些功能,专注在性能和稳定性上。
④ kingshard由小团队用go语言开发,还需要发展,需要不断完善。
⑤ Vitess是Youtube生产在使用,架构很复杂。不支持MySQL原生协议,使用需要大量改造成本。
⑥ Atlas是360团队基于mysql proxy改写,功能还需完善,高并发下不稳定。
⑦ MaxScale是mariadb(MySQL原作者维护的一个版本) 研发的中间件
⑧ MySQLRoute是MySQL官方Oracle公司发布的中间件
Mycat能干什么?
1.读写分离
2.数据分片
垂直拆分(分库)、水平拆分(分表)、垂直+水平拆分(分库分表)
3、多数据源整合
Mycat原理
Mycat 的原理中最重要的一个动词是“拦截”,它拦截了用户发送过来的 SQL 语句,首先对 SQL语句做了一些特定的分析:如分片分析、路由分析、读写分离分析、缓存分析等,然后将此 SQL 发往后端的真实数据库,并将返回的结果做适当的处理,最终再返回给用户。
这种方式把数据库的分布式从代码中解耦出来,程序员察觉不出来后台使用 Mycat 还是MySQL。
什么是 ElasticSearch?
Elasticsearch 是一个基于 Lucene 的搜索引擎。它提供了具有 HTTP Web 界面和无架构 JSON 文档的 分布式,多租户能力的全文搜索引擎。Elasticsearch 是用 Java 开发的,根据 Apache 许可条款作为开源发布
ElasticSearch 的倒排索引是什么
倒排索引是搜索引擎的核心。搜索引擎的主要目标是在查找发生搜索条件的文档时提供快速搜索。倒排索引是一种像数据结构一样的散列图,可将用户从单词导向文档或网页。它是搜索引擎的核心。其主要目标是快速搜索从数百万文件中查找数据。
ElasticSearch 索引 类型 文档 字段是什么
Relational DB -> Databases -> Tables -> Rows -> Columns
Elasticsearch -> Indices -> Types -> Documents -> Fields
索引:相当于关系型数据库
类型:相当于表
文档:相当于表中的一行
字段:相当行中的列
Springboot 是什么
Spring Boot 是 Spring 开源组织下的子项目,是 Spring 组件一站式解决方案,主要是简化了使用 Spring的难度,简省了繁重的配置,提供了各种启动器,开发者能快速上手
SpringBoot启动类@SpringBootApplication都有那些注解组成,他们的作用分别是什么?
@Target 说明Annotation所修饰的对象范围:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)
@Retention
(保留)信息注解说明,这种类型的注解会被保留到那个阶段. 有三个值:
1.RetentionPolicy.SOURCE —— 这种类型的Annotations只在源代码级别保留,编译时就会被忽略
2.RetentionPolicy.CLASS —— 这种类型的Annotations编译时被保留,在class文件中存在,但JVM将会忽略
3.RetentionPolicy.RUNTIME —— 这种类型的Annotations将被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用.
@Documented
表明这个注解应该被 javadoc工具记录. 默认情况下,javadoc是不包括注解的. 但如果声明注解时指定了@Documented,则它会被 javadoc 之类的工具处理, 所以注解类型信息也会被包括在生成的文档中
@Inherited
这是一个稍微复杂的注解类型. 它指明被注解的类会自动继承. 更具体地说,如果定义注解时使用了 @Inherited 标记,然后用定义的注解来标注另一个父类, 父类又有一个子类(subclass),则父类的所有属性将被继承到它的子类中.
@SpringBootConfiguration
@SpringBootConfiguration继承自@Configuration,二者功能也一致,标注当前类是配置类,并会将当前类内声明的一个或多个以@Bean注解标记的方法的实例纳入到spring容器中,并且实例名就是方法名
@EnableAutoConfiguration: @EnableAutoConfiguration注释,此注释自动载入应用程序所需的所有Bean——这依赖于Spring Boot在类路径中的查找。
@ComponentScan: 自动扫描组件
什么是JavaConfig
Spring JavaConfig是Spring社区的产品,它提供了配置Spring loC容器的纯Java方法。因此它有助于避免使用XML配置。使用JavaConfig的优点在于:面向对象的配置。由于配置被定义为JavaConfig中的类,因此用户可以充分利用Java中的面向对象功能。一个配置类可以继承另一个,重写它的@Bean方法等。
减少或消除XML配置。基于依赖注入原则的外化配置的好处已被证明。但是,许多开发人员不希望在XML和Java之间来回切换。JavaConfig 为开发人员提供了一种纯Java方法来配置与XML配置概念相似的Spring容器。从技术角度来讲,只使用JavaConfig配置类来配置容器是可行的,但实际上很多人认为将JavaConfig与XML混合匹配是理想的。类型安全和重构友好。JavaConfig提供了一种类 型安全的方法来配置Spring容器。由于Java 5.0对泛型的支持,现在可以按类型而不是按名称检索bean,不需要任何强制转换或基于字符串的查找。
Spring Boot 有哪些缺点?
Spring Boot 虽然上手很容易,但如果你不了解其核心技术及流程,所以一旦遇到问题就很棘手,而且现在的解决方案也不是很多,需要一个完善的过程。
使用Spring Boot开发分布式微服务时,我们面临以下问题
●与分布式系统相关的复杂性-这种开销包括网络问题,延迟开销,带宽问题,安全问题。
●服务发现-服务发现工具管理群集中的流程和服务如何查找和互相交谈。它涉及一个服务目录,在该目录中注册服务,然后能够查找并连接到该目录中的服务。
●冗余-分布式系统中的冗余问题。
●负载平衡–负载平衡改善跨多个计算资源的工作负荷,诸如计算机,计算机集群,网络链路,中央处理单元,或磁盘驱动器的分布。
●性能-问题由于各种运营开销导致的性能问题。
●部署复杂性-Devops技能的要求
Springboot 有什么优点
敏捷开发
嵌入式 Tomcat,Jetty 容器,
无需部署 WAR 包
支持热部署
提供大量 starter 简化 Maven 配置
对主流开发框架的无配置集成
如何重新加载Spring Boot上的更改,而无需重新启动服务器?
这可以使用DEV工具来实现。通过这种依赖关系,您可以节省任何更改,嵌入式tomcat将重新启动。Spring Boot有一个开发工具(DevTools)模块,它有助于提高开发人员的生产力。Java 开发人员面临的一个主要挑战是将文件更改自动部署到服务器并自动重启服务器。开发人员可以重新加载Spring Boot上的更改,而无需重新启动服务器。这将消除每次手动部署更改的需要。Spring Boot在发布它的第一个版本时没有这个功能。这是开发人员最需要的功能。DevTools 模块完全满足开发人员的需求。该模块将在生产环境中被禁用。它还提供H2数据库控制台以更好地测试应用程序
org.springframework.boot spring-boot-devtools true
Spring Boot 的核心配置文件有哪几个?它们的区别是什么?
Spring Boot 的核心配置文件是 application 和 bootstrap 配置文件。
application 配置文件这个容易理解,主要用于 Spring Boot 项目的自动化配置。
bootstrap 配置文件的特性:
boostrap 由父 ApplicationContext 加载,比 applicaton 优先加载
boostrap 里面的属性不能被覆盖
bootstrap 配置文件有以下几个应用场景:
使用 Spring Cloud Config 配置中心时,这时需要在 bootstrap 配置文件中添加连接到配置中心的配置属性来加载外部配置中心的配置信息;
一些固定的不能被覆盖的属性;
一些加密/解密的场景;
Spring Boot中的监视器是什么?
Spring boot actuator是spring启动框架中的重要功能之一。Spring boot监视器可帮助您访问生产环境中正在运行的应用程序的当前状态。有几个指标必须在生产环境中进行检查和监控。即使-些外部应用程序可能正在使用这些服务来向相关人员触发警报消息。监视器模块公开了一-组可直接作为HTTP URL访问的REST端点来检查状态。
如何在Spring Boot中禁用Actuator端点安全性?
默认情况下,所有敏感的HTTP端点都是安全的,只有具有ACTUATOR角色的用户才能访问它们。安全性是使用标准的HttpServletRequest.isUserlnRole方法实施的。我们可以使用management. security.enabled = false来禁用安全性。只有在执行机构端点在防火墙后访问时,才建议禁用安全性。
如何在自定义端口.上运行Spring Boot应用程序?
为了在自定义端口上运行Spring Boot应用程序,可以在application.properties中指定端口。server.port = 8090
什么是YAML
YAML是一种人类可读的数据序列化语言.它通常用于配置文件.与属性文件相比,如果我们想要在配置文件中添加复杂的属性,YAML文件就更加结构化,而且更少混淆.可以看出YAML具有分层配置数据.
如何实现Spring Boot应用程序的安全性?
为了实现Spring Boot的安全性,我们使用spring-boot-starter-security依赖项,并且必须添加安全配置。它只需要很少的代码。配置类将必须扩展WebSecurityConfigurerAdapter并覆盖其方法。
如何集成Spring Boot和ActiveMQ?
对于集成Spring Boot和ActiveMQ,我们使用spring-boot -starter-activemq依赖关系。它只需要很少的配置,并且不需要样板代码。
什么是Swagger?你用Spring Boot实现了它吗?
Swagger广泛用于可视化API,使用Swagger UI为前端开发人员提供在线沙箱。Swagger 是用于生成RESTful Web服务的可视化表示的工具,规范和完整框架实现。它使文档能够以与服务器相同的速度更新。当通过Swagger正确定义时,消费者可以使用最少量的实现逻辑来理解远程服务并与其进行交互。因此,Swagger消除了调用服务时的猜测。
什么是Spring Batch?
Spring Boot Batch提供可重用的函数,这些函数在处理大量记录时非常重要,包括日志/跟踪,事务管理,作业处理统计信息,作业重新启动,跳过和资源管理。它还提供了更先进的技术服务和功能,通过优化和分区技术,可以实现极高批量和高性能批处理作业。简单以及复杂的大批量批处理作业可以高度可扩展的方式利用框架处理重要大量的信息。
什么是FreeMarker模板?
FreeMarker是一个基于Java的模板引擎,最初专注于使用MVC软件架构进行动态网页生成。使用Freemarker的主要优点是表示层和业务层的完全分离。程序员可以处理应用程序代码,而设计人员可以处理html页面设计。最后使用freemarker可以将这些结合起来,给出最终的输出页面。
如何使用Spring Boot实现异常处理?
Spring提供了一种 使用ControllerAdvice处理异常的非常有用的方法。通过实现一个ControlerAdvice类,来处理控制器类抛出的所有异常。
什么是CSRF攻击?
CSRF代表跨站请求伪造。这是一种攻击,迫使最终用户在当前通过身份验证的Web应用程序.上执行不需要的操作。CSRF攻击专门针对状态改变请求,而不是数据窃取,因为攻击者无法查看对伪造请求的响应。
什么是WebSockets?
WebSocket是一种计算机通信协议,通过单个TCP连接提供全双工通信信道。WebSocket是双向的-使用WebSocket客户端或服务器可以发起消息发送。WebSocket是全双工的-客户端和服务器通信是相互独立的。单个TCP连接-初始连接使用HTTP,然后将此连接升级到基于套接字的连接。然后这个单一连接用于所有未来的通信.Light -与http相比,WebSocket 消息数据交换要轻得多。
我们如何监视所有Spring Boot微服务?
Spring Boot提供监视器端点以监控各个微服务的度量。这些端点对于获取有关应用程序的信息(如它们是否已启动)以及它们的组件(如数据库等)是否正常运行很有帮助。但是,使用监视器的一个主要缺点或困难是,我们必须单独打开应用程序的知识点以了解其状态或健康状况。想象一下涉及50个应用程序的微服务,管理员将不得不击中所有50个应用程序的执行终端。
Spring Boot 的配置文件有哪几种格式?它们有什么区别?
.properties 和 .yml,它们的区别主要是书写格式不同。另外,.yml 格式不支持 @PropertySource 注解导入配置。
Spring Boot 的核心注解是哪个?它主要由哪几个注解组成的?
启动类上面的注解是@SpringBootApplication,它也是 Spring Boot 的核心注解,主要组合包含了以下 3 个注解:
@SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。
@EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,
◼ 如关闭数据源自动配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。
@ComponentScan:Spring 组件扫描
开启 Spring Boot 特性有哪几种方式?
1)继承 spring-boot-starter-parent 项目
2)如果项目已经继承了其他父项目,则可以导入 spring-boot-dependencies 项目依赖
Spring Boot 需要独立的容器运行吗?
可以不需要,内置了 Tomcat/ Jetty 等容器。
运行 Spring Boot 有哪几种方式?
1)打包命令或者放到容器中运行
2)用 Maven/ Gradle 插件运行
3)直接执行 main 方法运行
Spring Boot 自动配置原理是什么?
注解 @EnableAutoConfiguration, @Configuration, @ConditionalOnClass 就是自动配置的核心,
首先它得是一个配置文件,其次根据类路径下是否有这个类去自动配置。
@EnableAutoConfiguration 是实现自动配置的注解
@Configuration 表示这是一个配置文件
具体参考文档:
https://mp.weixin.qq.com/s?__biz=MzI3ODcxMzQzMw==&mid=2247484365&idx=1&sn=a4ab1d977d6b03bf122b4d596d7ee1ab&scene=21
如何在 Spring Boot 启动的时候运行一些特定的代码?
可以实现接口 ApplicationRunner 或者 CommandLineRunner,这两个接口实现方式一样,它们都只提供了一个 run 方法
Spring Boot 有哪几种读取配置的方式?
Spring Boot 可以通过 @PropertySource,@Value,@Environment, @ConfigurationProperties 来绑定变量
你如何理解 Spring Boot 中的 Starters?
Starters 可以理解为启动器,它包含了一系列可以集成到应用里面的依赖包,你可以一站式集成 Spring 及其他技术,而不需要到处找示例代码和依赖包。如你想使用 Spring JPA 访问数据库,只要加入 spring-boot-starter-data-jpa 启动器依赖就能使用了。
Starters 包含了许多项目中需要用到的依赖,它们能快速持续的运行,都是一系列得到支持的管理传递性依赖。
如何在 Spring Boot 启动的时候运行一些特定的代码?
可以实现接口 ApplicationRunner 或者 CommandLineRunner,这两个接口实现方式一样,它们都只提供了一个 run 方法
Spring Boot 有哪几种读取配置的方式?
Spring Boot 可以通过 @PropertySource,@Value,@Environment, @ConfigurationProperties 来绑定变量
Spring Boot 支持哪些日志框架?推荐和默认的日志框架是哪个?
Spring Boot 支持 Java Util Logging, Log4j2, Lockback 作为日志框架,如果你使用 Starters 启动器,Spring Boot 将使用 Logback 作为默认日志框架
SpringBoot 实现热部署有哪几种方式?
主要有两种方式:Spring Loaded Spring-boot-devtools
你如何理解 Spring Boot 配置加载顺序?
在 Spring Boot 里面,可以使用以下几种方式来加载配置。
1)properties 文件;
2)YAML 文件;
3)系统环境变量;
4)命令行参数;
Spring Boot 如何定义多套不同环境配置?
applcation.properties
application-dev.properties
application-test.properties
application-prod.properties
运行时指定具体的配置文件
Spring Boot 可以兼容老 Spring 项目吗,如何做?
可以兼容,使用 @ImportResource 注解导入老 Spring 项目配置文件。
Spring Boot 2.X 有什么新特性?与 1.X 有什么区别?
依赖 JDK 版本升级:2.x 里面的许多方法应用了 JDK 8 的许多高级新特性,至少需要 JDK 8 的支持;
第三方类库升级:2.x 对第三方类库升级了所有能升级的稳定版本,例如:Spring Framework 5+、Tomcat 8.5+、Hibernate 5.2+、Thymeleaf 3+;
响应式 Spring 编程:2.x 通过启动器和自动配置全面支持 Spring 的响应式编程,响应式编程是完全异步和非阻塞的,它是基于事件驱动模型,而不是传统的线程模型;
连接池:2.x 默认使用 HikariCP 连接池;
json:提供了一个 spring-boot-starter-json 启动器对 JSON 读写的支持;
Quartz:2.x 提供了一个 spring-boot-starter-quartz 启动器对定时任务框架 Quartz 的支持;
HTTP/2 支持:提供对 HTTP/2 的支持,如:Tomcat, Undertow, Jetty;
Actuator 加强:在 2.x 中,对执行器端点进行了许多改进,所有的 HTTP 执行端点现在都暴露在 /actuator 路径下,并对 JSON 结果集也做了改善。
Spring Boot 和 Spring 是什么关系
SpringBoot 底层就是 Spring,简化使用 Spring 的方式而已,多加了好多的自动配置
Springboot 与 Springmvc 的区别
spring boot 就是一个大框架里面包含了许许多多的东西,其中 spring 就是最核心的内容之一,包含 spring mvc。
spring mvc 是只是 spring 处理 web 层请求的一个模块
Spring Boot 支持哪些日志框架?推荐和默认的日志框架是哪个?
Spring Boot 支持 Java Util Logging, Log4j2, Lockback 作为日志框架,如果你使用 Starters 启动器,Spring Boot 将使用 Logback 作为默认日志框架
SpringBoot 实现热部署有哪几种方式?
主要有两种方式:
Spring Loaded Spring-boot-devtools Spring-boot-devtools
Springboot 常用 starter
spring-boot-starter-data-jpa
spring-boot-starter-data-mongodb
spring-boot-starter-amqp
spring-boot-starter-data-elasticsearch
spring-boot-starter-web
什么是微服务架构
微服务架构是⼀种架构模式,它提倡将单⼀应⽤程序划分成⼀组⼩的服务,服务之间互相协调、互相配合,为⽤户提供最终价值。每个服务运⾏在其独⽴的进程中,服务与服务间采⽤轻量级的通信机制互相协作(通常是基于 HTTP 协议的 RESTful API)。每个服务都围绕着具体业务进⾏构建,并且能够被独⽴的部署到⽣产环境。
微服务哪些框架
Dubbo,是阿里巴巴服务化治理的核心框架,并被广泛应用于阿里巴巴集团的各成员站点。阿里巴巴近几年对开源社区的贡献不论在国内还是国外都是引人注目的,比如:
JStorm 捐赠给 Apache 并加入 Apache 基金会等,为中国互联网人争足了面子,使得阿里巴巴在国人眼里已经从电商升级为一家科技公司了。
Spring Cloud,从命名我们就可以知道,它是 Spring Source 的产物,Spring 社区的强大背书可以说是 Java 企业界最有影响力的组织了,除了 Spring Source 之外,还有
Pivotal 和 Netfix 是其强大的后盾与技术输出。其中 Netflix 开源的整套微服务架构套件是Spring Cloud 的核心。
说说 RPC 的实现原理
首先需要有处理网络连接通讯的模块,负责连接建立、管理和消息的传输。其次需要有编解码的模块,因为网络通讯都是传输的字节码,需要将我们使用的对象序列化和反序列化。剩下的就是客户端和服务器端的部分,服务器端暴露要开放的服务接口,客户调用服务接口的一个代理实现,这个代理实现负责收集数据、编码并传输给服务器然后等待结果返回。
为什么要分布式?
从开发角度来讲单体应用的代码都集中在一起,而分布式系统的代码根据业务被拆分。所以,每个团队可以负责一个服务的开发,这样提升了开发效率。另外,代码根据业务拆分之后更加便于维护和扩展。
另外,我觉得将系统拆分成分布式之后不光便于系统扩展和维护,更能提高整个系统的性能。你想一想嘛?把整个系统拆分成不同的服务/系统,然后每个服务/系统 单独部署在一台服务器上,是不是很大程度上提高了系统性能呢?
微服务架构有什么优势和劣势
微服务架构的优点:
每个服务都比较简单,只关注于一个业务功能。
微服务架构方式是松耦合的,可以提供更高的灵活性。
微服务可通过最佳及最合适的不同的编程语言与工具进行开发
每个微服务可由不同团队独立开发,互不影响,加快推出市场的速度。
微服务架构的缺点:
运维工作量增加,应用运维管理复杂。
服务器内存占用量更高
事务处理麻烦
Springcloud 是什么
springCloud是分布式微服务架构下的一站式解决方案,是各个微服务架构落地技术的集合体,俗称微服务全家桶
Springcloud 与 SpringBoot 有什么区别
SpringBoot 可以离开 SpringCloud 独立使用开发项目,但是 SpringCloud 离不开 SpringBoot,属于依赖的关系.
SpringBoot 专注于快速、方便的开发单个微服务个体,SpringCloud 关注全局的服务治理框架。
什么是Hystrix?它如何实现容错?
Hystrix是一个延迟和容错库,旨在隔离远程系统,服务和第三方库的访问点,当出现故障是不可避免的故障时,停止级联故障并在复杂的分布式系统中实现弹性。通常对于使用微服务架构开发的系统,涉及到许多微服务。这些微服务彼此协作。
现在假设由于某种原因,某个互相调用公开的服务会抛出异常。在这种情况下使用Hystrix 定义了一个回退方法。这种后备方法应该具有与公开服务相同的返回类型。如果暴露服务中出现异常,则回退方法将返回一些值。如果这个服务中的异常继续发生,则Hystrix电路将中断,并且服务使用者将一起跳过这个方法,并直接调用回退方法。断路器的目的是给第一页方法或第一页方法可能调用的其他方法留出时间,并导致异常恢复。可能发生的情况是,在负载较小的情况下,导致异常的问题有更好的恢复机会.
Eureka(注册发现)
Eureka 是 Netflix 开发的服务注册发现框架,本身是一个基于 REST 的服务。
Eureka 包含两个组件:Eureka Server 和 Eureka Client。
Eureka Server 提供服务注册服务,各个节点启动后,会在 Eureka Server 中进行注册,这样 EurekaServer 中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到。
Eureka Client 是一个 java 客户端,用于简化与 Eureka Server 的交互,客户端同时也就是一个内置的、使用轮询 (round-robin)负载算法的负载均衡器。
三个注册中心异同点
组件名 语言 CAP 服务健康检查 对外暴露接口 SpringCloud继承
Eureka Java AP 可配支持 HTTP 已集成
Consul GO CP 支持 HTTP/DNS 已集成
Zookeeper Java CP 支持 客户端 已集成
Eureka 和 zookeeper 的区别
著名的 CAP 理论指出,一个分布式系统不可能同时满足 C(一致性)、A(可用性)和 P(分区容错性)。由于分区容错性在是分布式系统中必须要保证的,因此我们只能在 A 和 C 之间进行权衡。在此 Zookeeper 保证的是 CP, 而 Eureka 则是 AP。
Zookeeper 保证 CP
当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的注册信息,但不能接受服务直接 down 掉不可用。也就是说,服务注册功能对可用性的要求要高于一致性。但是 zk 会出现这样一种情况,当 master 节点因为网络故障与其他节点失去联系时,剩余节点会重新进行 leader 选举。
问题在于,选举 leader 的时间太长,30 ~ 120s, 且选举期间整个 zk 集群都是不可用的,这就导致在选举期间注册服务瘫痪。在云部署的环境下,因网络问题使得 zk 集群失去 master 节点是较大概率会发生的事,虽然服务能够最终恢复,但是漫长的选举时间导致的注册长期不可用是不能容忍的。
Eureka 保证 AP
Eureka 看明白了这一点,因此在设计时就优先保证可用性。Eureka 各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。而 Eureka 的客户端在向某个 Eureka 注册或时如果发现连接失败,则会自动切换至其它节点,只要有一台 Eureka 还在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)。除此之外,Eureka 还有一种自我保护机制,如果在 15 分钟内超过 85%的节点都没有正常的心跳,那么 Eureka 就认为客户端与注册中心出现了网络故障,此时会出现以下几种情况:
因此, Eureka 可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像 zookeeper 那样使整个注册服务瘫痪。
总结
Eureka 作为单纯的服务注册中心来说要比 zookeeper 更加“专业”,因为注册服务更重要的是可用性,我们可以接受短期内达不到一致性的状况。不过 Eureka目前 1.X 版本的实现是基于 servlet 的 java web 应用,它的极限性能肯定会受到影响。期待正在开发之中的 2.X 版本能够从 servlet 中独立出来成为单独可部署执行的服务。
GateWay三大核心概念
Route(路由) 路由是构建网关的基本模板,他由ID,目标URL,一系列的断言和过滤器组成,如果断言为true则匹配该路由
Predicate(断言) 参考的是Java8的java.util.function.Predicate,开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由
Filter(过滤) 指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改
总结:web请求,通过一-些匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行些精细化控制。
predicate就是我们的匹配条件;而filter, 就可以理解为一个无所不能的拦截器。有了这两个元索,再加上目标uri, 就可以实现具体的路由了.
Spring Cloud Ribbon(负载均衡)
Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。通过Spring Cloud 的封装,可以让我们轻松地将面向服务的 REST 模版请求自动转换成客户端负载均衡的服务调用。Spring
Cloud Ribbon 虽然只是一个工具类框架,它不像服务注册中心、配置中心、API 网关那样需要独立部署
Spring Cloud Feign(负载均衡)
作为Spring Cloud的子项目之一,Spring Cloud OpenFeign以将OpenFeign集成到Spring Boot应用中的方式,为微服务架构下服务之间的调用提供了解决方案。首先,利用了OpenFeign的声明式方式定义Web服务客户端;其次还更进一步,通过集成Ribbon或Eureka实现负载均衡的HTTP客户端
https://blog.csdn.net/taiyangdao/article/details/81359394
Spring Cloud hystrix(断路器)
Hystrix 是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix 能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性
Spring Cloud Config(配置中心)
分布式系统中,为了方便服务配置文件统一管理,实时更新,所以需要分布式配置中心组件。在 Spring Cloud 中, 有分布式配置中心组件 springCloud Config ,它支持从远程 Git 仓库中读取配置文件并存放到本地 Git 仓库
SpringCloud zuul (服务网关)
Zuul 包含了对请求的路由和过滤两个最主要的功能:其中路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础而过滤器功能则负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础
Dubbo 是什么
Dubbo 是一款高性能、轻量级的开源 Java RPC 框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
Springcloud 与 dubbo 的区别
SpringCloud 是一套目前比较网站微服务框架了,整合了分布式常用解决方案遇到了问题注册中心 Eureka、负载均衡器 Ribbon ,客户端调用工具 Rest 和 Feign,分布式配置中心 Config,服务保护 Hystrix,网关 Zuul ,消息总线Bus 等。
Dubbo 内部实现功能没有 SpringCloud 强大(全家桶),只是实现服务治理,缺少分布式配置中心、网关、链路、总线等,如果需要用到这些组件,需要整合其他框架。
最大区别:SpringCloud 抛弃了 Dubbo 的 RPC 通信,采用的是基于 HTTP 的 REST 方式。
Eureka 与 zookeeper 的区别
Zookeeper 保证 CP:当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的注册信息,但不能接受服务直接 down 掉不可用。也就是说,服务注册功能对可用性的要求要高于一致性。但是 zk 会出现这样一种情况, 当 master 节点因为网络故障与其他节点失去联系时,剩余节点会重新进行 leader 选举。问题在于,选举 leader 的时间太长,30 ~ 120s, 且选举期间整个 zk 集群都是不可用的,这就导致在选举期间注册服务瘫痪。在云部署的环境下, 因网络问题使得 zk 集群失去 master 节点是较大概率会发生的事,虽然服务能够最终恢复,但是漫长的选举时间导致的注册长期不可用是不能容忍的。
Eureka 保证AP:Eureka 各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。而 Eureka 的客户端在向某个 Eureka 注册或时如果发现连接失败,则会自动切换至其它节点,只要有一台 Eureka 还在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)。除此之外,Eureka 还有一种自我保护机制,如果在 15 分钟内超过 85%的节点都没有正常的心跳,那么 Eureka 就认为客户端与注册中心出现了网络故障
分布式 CAP 定理是什么
CAP 理论告诉我们,一个分布式系统不可能同时满足一致性(C:Consistency),可用性(A: Availability)和分区容错性(P:Partition tolerance)这三个基本需求,最多只能同时满足其中的 2 个
选项 描述
C(Consistence) 一致性,值数据在多个副本之间能够保持一致的特性(严格的一致性)
A(Availability) 可用性,指系统提供的服务必须一直处于可用的状态,每次请求都能获取到非错的响应-----但是不保证获取的数据为最新数据
P(Networkpartitioning) 分区容错性,分布式系统在遇到任何网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务,除非整个网络环境都发生了故障
组合 分析结果
CA 满足原子和可用,放弃分区容错,说白了就是一个整体的应用
CP 满足原子和分区容错,也就是说,要放弃可用,当系统被分区,为了保证原子性,必须放弃可用性,让服务停用
AP 满足可用性和分区容错,当出现分区,同时为了保证可用性,必须让节点继续对外服务,这样必然导致失去原子性
三高 三 V 是什么
大数据时代的 3V:
海量 Volume 多样 Variety 实时 Velocity
互联网需求的 3 高:
高并发 高可拓 高性能
CAP(分布式架构永远要保证P:分区容错性,那么其他两者只能任选其一,不能三者得谦,理论关注粒度是数据,而不是整体系统设计的策略)
C:Consistency (强一致性)
A:Availability (可用性)
P:Partition tolerance (分区容错性)
最多只能同时满足两个。
CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,
因此,根据CAP原理将NoSQL数据库分成了满足CA原则、满足CP原则和满足AP原则三大类:
CA-单点集群,满足-致性,可用性的系统,通常在可扩展性上不太强大。
CP-满足一致性, 分区容忍必的系统,通常性能不是特别高。
AP-满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。
如何解决分布式事务-2PC
分布式事务指事务的操作位于不同的节点上,需要保证事务的 AICD 特性。
例如在下单场景下,库存和订单如果不在同一个节点上,就涉及分布式事务。
一、两阶段提交(2PC)
两阶段提交(Two-phase Commit,2PC),通过引入协调者(Coordinator)来协调参与者的行为,并最终决定这些参与者是否要真正执行事务
1.1 准备阶段
协调者询问参与者事务是否执行成功,参与者发回事务执行结果。
1.2 提交阶段
如果事务在每个参与者上都执行成功,事务协调者发送通知让参与者提交事务;否则,协调者发送通知让参与者回滚事务。
需要注意的是,在准备阶段,参与者执行了事务,但是还未提交。只有在提交阶段接收到协调者发来的通知后,才进行提交或者回滚。
存在的问题
2.1 同步阻塞 所有事务参与者在等待其它参与者响应的时候都处于同步阻塞状态,无法进行其它操作。
2.2 单点问题 协调者在 2PC 中起到非常大的作用,发生故障将会造成很大影响。特别是在阶段二发生故障,所有参与者会一直等待状态,无法完成其它操作。
2.3 数据不一致 在阶段二,如果协调者只发送了部分 Commit 消息,此时网络发生异常,那么只有部分参与者接收到 Commit 消息,也就是说只有部分参与者提交了事务,使得系统数据不一致。
如何解决分布式事务-TCC
TCC 其实就是采用的补偿机制,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。
它分为三个阶段:
Try 阶段主要是对业务系统做检测及资源预留
Confirm 阶段主要是对业务系统做确认提交,Try 阶段执行成功并开始执行 Confirm 阶段时,默认 Confirm阶段是不会出错的。即:只要 Try 成功,Confirm 一定成功。
Cancel 阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。
举个例子,假入 Bob 要向 Smith 转账,思路大概是:
我们有一个本地方法,里面依次调用
1、首先在 Try 阶段,要先调用远程接口把 Smith 和 Bob 的钱给冻结起来。
2、在 Confirm 阶段,执行远程调用的转账的操作,转账成功进行解冻。
3、如果第 2 步执行成功,那么转账成功,如果第二步执行失败,则调用远程冻结接口对应的解冻方法 (Cancel)。
优点: 跟 2PC 比起来,实现以及流程相对简单了一些,但数据的一致性比 2PC 也要差一些
缺点: 缺点还是比较明显的,在 2,3 步中都有可能失败。TCC 属于应用层的一种补偿方式,所以需要程序员在实现的时候多写很多补偿的代码,在一些场景中,一些业务流程可能用 TCC 不太好定义及处理。
如何解决分布式事务-本地事务表(重点)
本地消息表这种实现方式应该是业界使用最多的,其核心思想是将分布式事务拆分成本地事务进行处理,这种思路是来源于 ebay。我们可以从下面的流程图中看出其中的一些细节:
基本思路就是:
消息生产方,需要额外建一个消息表,并记录消息发送状态。消息表和业务数据要在一个事务里提交,也就是说他们要在一个数据库里面。然后消息会经过 MQ 发送到消息的消费方。如果消息发送失败,会进行重试发送。
消息消费方,需要处理这个消息,并完成自己的业务逻辑。此时如果本地事务处理成功,表明已经处理成功了,如果 处理失败,那么就会重试执行。如果是业务上面的失败,可以给生产方发送一个业务补偿消息,通知生产方进行回滚等操作。
生产方和消费方定时扫描本地消息表,把还没处理完成的消息或者失败的消息再发送一遍。如果有靠谱的自动对账补 账逻辑,这种方案还是非常实用的。
这种方案遵循 BASE 理论,采用的是最终一致性,笔者认为是这几种方案里面比较适合实际业务场景的,即不会出现像 2PC 那样复杂的实现(当调用链很长的时候,2PC 的可用性是非常低的),也不会像 TCC 那样可能出现确认或者回滚不了的情况。
优点: 一种非常经典的实现,避免了分布式事务,实现了最终一致性。在 .NET 中 有现成的解决方案。
缺点: 消息表会耦合到业务系统中,如果没有封装好的解决方案,会有很多杂活需要处理。
如何设计一个秒杀系统
秒杀系统的整体架构可以概括为“稳、准、快”几个关键字。
所谓“稳”,就是整个系统架构要满足高可用,流量符合预期时肯定要稳定,超出预期时也同样不能掉链子,你要保证秒杀活动顺利完成,即秒杀商品顺利地卖出去,这个是最基本的前提。
然后就是“准”,你的业务需求是秒杀 10 台 iPhone XS,那就只能成交 10 台,多一台少一台都不行。一旦库存不对,那平台就要承担损失,所以“准”就是要求保证数据的一致性。
最后再看“快”,“快”其实很好理解,它就是说系统的性能要足够高,否则你怎么支撑这么大的流量呢?
不光是服务端要做极致的性能优化,而且在整个请求链路上都要做协同的优化,每个地方快一点,整个系统就完美了。 所以,一般优化设计思路:将请求拦截在系统上游,降低下游压力。
在一个并发量大,实际需求小的系统中,应当尽量在前端拦截无效流量,降低下游服务器和数据库的压力,不然很可能造成数据库读写锁冲突,甚至导致死锁,最终请求超时。
限流:前端直接限流,允许少部分流量流向后端。
削峰:瞬时大流量峰值容易压垮系统,解决这个问题是重中之重。常用的消峰方法有异步处理、缓存和消息中间件等技术。
异步处理:秒杀系统是一个高并发系统,采用异步处理模式可以极大地提高系统并发量,其实异步处理就是削峰的一种实现方式。
内存缓存:秒杀系统最大的瓶颈一般都是数据库读写,由于数据库读写属于磁盘 IO,性能很低,如果能 够把部分数据或业务逻辑转移到内存缓存,效率会有极大地提升。
消息队列:消息队列可以削峰,将拦截大量并发请求,这也是一个异步处理过程,后台业务根据自己的处理能力,从消息队列中主动的拉取请求消息进行业务处理。
可拓展:当然如果我们想支持更多用户,更大的并发,最好就将系统设计成弹性可拓展的,如果流量来了,拓展机器就好了,像淘宝、京东等双十一活动时会临时增加大量机器应对交易高峰。
Docker 是什么
Docker 作为一个软件集装箱化平台,可以让开发者构建应用程序时,将它与其依赖环境一起打包到一个容器中,然后很容易地发布和应用到任意平台中 docker 有 3 大核心:镜像、容器、仓库
Docker 镜像、容器、仓库的关系
Docker 镜像可以看作是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数。类似虚拟机镜像
Docker 容器是镜像的一个运行实例,不同的是它带有额外的可写层。 可认为 docker 容器就是独立运行的一个或一组应用,以及它们所运行的必需环境。
Docker 仓库是用来包含镜像的位置,Docker 提供一个注册服务器(Register)来保存多个仓库,每个仓库又可以包含多个具备不同 tag 的镜像。Docker 运行中使用的默认仓库是 Docker Hub 公共仓库。
Spring 中用到的设计模式
工厂方法 单例模式 适配器 代理 观察者 策略 模板方法
Redis 如何与 mysql 同步
一、对强一致时要求比较高的,应采用实时同步方案,即查询缓存查询不到再从 DB 查询,保存到缓存;
二、高并发时读操作和上面一样,写操作是异步写,写入 Redis 后直接返回,然后定期写入 MySQL 。可采用异步队列的方式同步,可采用 kafka 等消息中间件处理消息生产和消费。
spring3+提供了注解的方式进行缓存编程
@Cacheable:查询时使用,注意 Long 类型需转换为 Sting 类型,否则会抛异常
@CachePut:更新时使用,使用此注解,一定会从 DB 上查询数据
@CacheEvict:删除时使用;
行锁锁 和 表锁 悲观锁 和 乐观锁
(1)行锁:访问数据库的时候,锁定整个行数据,防止并发错误。开销小,加锁快,不会出现死锁;锁定力度大,发生锁冲突概率高,并发度最低
(2)表锁:访问数据库的时候,锁定整个表数据,防止并发错误。 开销大,加锁慢,会出现死锁;锁定粒度小,发生锁冲突的概率低,并发度高
(1)悲观锁:顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会 block 直到它拿到锁。
(2)乐观锁: 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量
(3)悲观锁 和 乐观锁的区别:
两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下,即冲突真的很少发 生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果经常产生冲突、所以这种情况下用悲观锁就比较合适。
算法
冒泡排序(Bubble Sort)
算法描述:
⚫ 比较相邻的元素。如果第一个比第二个大,就交换它们两个;
⚫ 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
⚫ 针对所有的元素重复以上的步骤,除了最后一个;
⚫ 重复步骤 1~3,直到排序完成。
如果两个元素相等,不会再交换位置,所以冒泡排序是一种稳定排序算法。
package com.atguigu.interview.chapter02;
/**
* @author helen
* @since 2019/7/22
* 冒泡排序
*/
public class BubbleSort {
/**
* @param data 被排序的数组
*/
public static void bubbleSort(int[] data) {
int arrayLength = data.length;
for (int i = 1; i < arrayLength; i++) {//第 i 次排序
for (int j = 0; j < arrayLength - i; j++) {//从索引为 j 的数开始
if (data[j] > data[j + 1]) { //相邻元素两两对比
int temp = data[j + 1]; // 元素交换
data[j + 1] = data[j];
data[j] = temp;
}
}
System.out.println("第" + i + "次排序:\n" + java.util.Arrays.toString(data));
}
}
public static void main(String[] args) {
int[] data = {3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48};
System.out.println("排序之前:\n" + java.util.Arrays.toString(data));
bubbleSort(data);
System.out.println("排序之后:\n" + java.util.Arrays.toString(data));
}
}
快速排序(Quick Sort)
算法描述:
使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:
⚫ 从数列中挑出一个元素,称为 “基准”(pivot);
⚫ 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,
该基准就处于数列的中间位置。这个称为分区(partition)操作;
⚫ 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
key 值的选取可以有多种形式,例如中间数或者随机数,分别会对算法的复杂度产生不同的影响。
package com.atguigu.interview.chapter02;
/**
* @author helen
* @since 2019/7/22
* 快速排序
*/
public class QuickSort {
public static void quickSort(int[] data, int low, int high) {
int i, j, temp, t;
if (low > high) {
return;
}
i = low;
j = high;
//temp 就是基准位
temp = data[low];
System.out.println("基准位:" + temp);
while (i < j) {
//先看右边,依次往左递减
while (temp <= data[j] && i < j) {
j--;
}
//再看左边,依次往右递增
while (temp >= data[i] && i < j) {
i++;
}
//如果满足条件则交换
if (i < j) {
System.out.println("交换:" + data[i] + "和" + data[j]);
t = data[j];
data[j] = data[i];
data[i] = t;
System.out.println(java.util.Arrays.toString(data));
}
}
//最后将基准位与 i 和 j 相等位置的数字交换
System.out.println("基准位" + temp + "和 i、j 相遇的位置" + data[i] + "交换");
data[low] = data[i];
data[i] = temp;
System.out.println(java.util.Arrays.toString(data));
//递归调用左半数组
quickSort(data, low, j - 1);
//递归调用右半数组
quickSort(data, j + 1, high);
}
public static void main(String[] args) {
int[] data = {3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48};
System.out.println("排序之前:\n" + java.util.Arrays.toString(data));
quickSort(data, 0, data.length - 1);
System.out.println("排序之后:\n" + java.util.Arrays.toString(data));
}
}
归并排序(Merge Sort)
算法描述:
⚫ 把长度为 n 的输入序列分成两个长度为 n/2 的子序列;
⚫ 对这两个子序列分别采用归并排序;
⚫ 将两个排序好的子序列合并成一个最终的排序序列。
package com.atguigu.interview.chapter02;
/**
* @author helen
* @since 2019/7/22
*/
public class MergeSort {
public static void mergeSort(int[] data) {
sort(data, 0, data.length - 1);
}
public static void sort(int[] arr, int l, int r) {
if(l == r) {
return;
}
int mid = l + ((r - l) >> 1);
sort(arr, l, mid);
sort(arr, mid + 1, r);
merge(arr, l, mid, r);
}
public static void merge(int[] arr, int l, int mid, int r) {
int[] temp = new int[r - l + 1];
int i = 0;
int p1 = l;
int p2 = mid + 1;
// 比较左右两部分的元素,哪个小,把那个元素填入 temp 中
while(p1 <= mid && p2 <= r) {
temp[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
}
// 上面的循环退出后,把剩余的元素依次填入到 temp 中
// 以下两个 while 只有一个会执行
while(p1 <= mid) {
temp[i++] = arr[p1++];
}
while(p2 <= r) {
temp[i++] = arr[p2++];
}
// 把最终的排序的结果复制给原数组
for(i = 0; i < temp.length; i++) {
arr[l + i] = temp[i];
}
}
public static void main(String[] args) {
int[] data = {3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48};
System.out.println("排序之前:\n" + java.util.Arrays.toString(data));
mergeSort(data);
System.out.println("排序之后:\n" + java.util.Arrays.toString(data));
}
}
二分查找(Binary Search)
算法描述:
⚫ 二分查找也称折半查找,它是一种效率较高的查找方法,要求列表中的元素首先要进行有序排列。
⚫ 首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;
⚫ 否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。
⚫ 重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。
package com.atguigu.interview.chapter02;
/**
* @author Helen
* @since 2019/7/22
*/
public class BinarySearch {
/**
* 二分查找 时间复杂度 O(log2n);空间复杂度 O(1)
*
* @param arr 被查找的数组
* @param left
* @param right
* @param findVal
* @return 返回元素的索引
*/
public static int binarySearch(int[] arr, int left, int right, int findVal) {
if (left > right) {//递归退出条件,找不到,返回-1
return -1;
}
int midIndex = (left + right) / 2;
if (findVal < arr[midIndex]) {//向左递归查找
return binarySearch(arr, left, midIndex, findVal);
} else if (findVal > arr[midIndex]) {//向右递归查找
return binarySearch(arr, midIndex, right, findVal);
} else {
return midIndex;
}
public static void main(String[] args){
//注意:需要对已排序的数组进行二分查找
int[] data = {-49, -30, -16, 9, 21, 21, 23, 30, 30};
int i = binarySearch(data, 0, data.length, 21);
System.out.println(i);
}
}
拓展需求:
当一个有序数组中,有多个相同的数值时,如何将所有的数值都查找到。
package com.atguigu.interview.chapter02;
import java.util.ArrayList;
import java.util.List;
/**
* @author Helen
* @since 2019/7/22
*/
public class BinarySearch2 {
/**
* {1, 8, 10, 89, 1000, 1000, 1234}
* 一个有序数组中,有多个相同的数值,如何将所有的数值都查找到,比如这里的 1000.
* 分析:
* 1. 返回的结果是一个列表 list
* 2. 在找到结果时,向左边扫描,向右边扫描 [条件]
* 3. 找到结果后,就加入到 ArrayBuffer
*
* @return
*/
public static List binarySearch2(int[] arr, int left, int right, int findVal) {
//找不到条件?
List list = new ArrayList<>();
if (left > right) {//递归退出条件,找不到,返回-1
return list;
}
int midIndex = (left + right) / 2;
int midVal = arr[midIndex];
if (findVal < midVal) {//向左递归查找
return binarySearch2(arr, left, midIndex - 1, findVal);
} else if (findVal > midVal) { //向右递归查找
return binarySearch2(arr, midIndex + 1, right, findVal);
} else {
System.out.println("midIndex=" + midIndex);
//向左边扫描
int temp = midIndex - 1;
while (true) {
if (temp < 0 || arr[temp] != findVal) {
break;
}
if (arr[temp] == findVal) {
list.add(temp);
}
temp -= 1;
}
//将中间这个索引加入
list.add(midIndex);
//向右边扫描
temp = midIndex + 1;
while (true) {
if (temp > arr.length - 1 || arr[temp] != findVal) {
break;
}
if (arr[temp] == findVal) {
list.add(temp);
}
temp += 1;
}
return list;
}
}
public static void main(String[] args){
//注意:需要对已排序的数组进行二分查找
int[] data = {1, 8, 10, 89, 1000, 1000, 1234};
List list = binarySearch2(data, 0, data.length, 1000);
System.out.println(list);
}
}
单例模式(Binary Search)
单例模式定义
单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡
的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的能。每台计算机可以有若干个打印机,但只能有一个 Printer Spooler,以避免
两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态。
单例模式的特点
⚫ 单例类只能有一个实例。
⚫ 单例类必须自己创建自己的唯一实例。
⚫ 单例类必须给所有其他对象提供这一实例。
单例模式保证了全局对象的唯一性,比如系统启动读取配置文件就需要单例保证配置的一致性。
单例的四大原则
⚫ 构造私有
⚫ 以静态方法或者枚举返回实例
⚫ 确保实例只有一个,尤其是多线程环境
⚫ 确保反序列换时不会重新构建对象
实现单例模式的方式
(1)饿汉式(立即加载):
饿汉式单例在类加载初始化时就创建好一个静态的对象供外部使用,除非系统重启,这个对象不会改变,所以本身就是线程安全的。
Singleton 通过将构造方法限定为 private 避免了类在外部被实例化,在同一个虚拟机范围内,Singleton 的唯一实例只能通过 getInstance()方法访问。(事
实上,通过 Java 反射机制是能够实例化构造方法为 private 的类的,会使 Java 单例实现失效)
package com.atguigu.interview.chapter02;
/**
* @author helen
* @since 2019/7/22
*
* 饿汉式(立即加载)
*/
public class Singleton1 {
/**
* 私有构造
*/
private Singleton1() {
System.out.println("构造函数 Singleton1");
}
/**
* 初始值为实例对象
*/
private static Singleton1 single = new Singleton1();
/**
* 静态工厂方法
* @return 单例对象
*/
public static Singleton1 getInstance() {
System.out.println("getInstance");
return single;
}
public static void main(String[] args){
System.out.println("初始化");
Singleton1 instance = Singleton1.getInstance();
}
}
(2)懒汉式(延迟加载):
该示例虽然用延迟加载方式实现了懒汉式单例,但在多线程环境下会产生多个 Singleton 对象
package com.atguigu.interview.chapter02;
/**
* @author helen
* @since 2019/7/22
*
* 懒汉式(延迟加载)
*/
public class Singleton2 {
/**
* 私有构造
*/
private Singleton2() {
System.out.println("构造函数 Singleton2");
}
* 初始值为 null
*/
private static Singleton2 single = null;
/**
* 静态工厂方法
* @return 单例对象
*/
public static Singleton2 getInstance() {
if(single == null){
System.out.println("getInstance");
single = new Singleton2();
}
return single;
}
public static void main(String[] args){
System.out.println("初始化");
Singleton2 instance = Singleton2.getInstance();
}
}
(3)同步锁(解决线程安全问题):
在方法上加 synchronized 同步锁或是用同步代码块对类加同步锁,此种方式虽然解决了多个实例对象问题,但是该方式运行效率却很低下,下一个线程想要获取对象,就必须等待上一个线程释放锁之后,才可以继续运行。
package com.atguigu.interview.chapter02;
/**
* @author helen
* @since 2019/7/22
*
* 同步锁(解决线程安全问题)
*/
public class Singleton3 {
/**
* 私有构造
*/
private Singleton3() {}
/**
* 初始值为 null
*/
private static Singleton3 single = null;
public static Singleton3 getInstance() {
// 等同于 synchronized public static Singleton3 getInstance()
synchronized(Singleton3.class){
// 注意:里面的判断是一定要加的,否则出现线程安全问题
if(single == null){
single = new Singleton3();
}
}
return single;
}
}
(4)双重检查锁(提高同步锁的效率):
使用双重检查锁进一步做了优化,可以避免整个方法被锁,只对需要锁的代码部分加锁,可以提高执行效率。
package com.atguigu.interview.chapter02;
/**
* @author helen
* @since 2019/7/22
* 双重检查锁(提高同步锁的效率)
*/
public class Singleton4 {
/**
* 私有构造
*/
private Singleton4() {}
/**
* 初始值为 null
*/
private static Singleton4 single = null;
/**
* 双重检查锁
* @return 单例对象
*/
public static Singleton4 getInstance() {
if (single == null) {
synchronized (Singleton4.class) {
if (single == null) {
single = new Singleton4();
}
}
}
return single;
}
}
(5) 静态内部类:
这种方式引入了一个内部静态类(static class),静态内部类只有在调用时才会加载,它保证了 Singleton 实例的延迟初始化,又保证了实例的唯一性。
它把 singleton 的实例化操作放到一个静态内部类中,在第一次调用getInstance() 方法时,JVM 才会去加载 InnerObject 类,同时初始化 singleton 实例,所以能让 getInstance() 方法线程安全。
特点是:即能延迟加载,也能保证线程安全。
静态内部类虽然保证了单例在多线程并发下的线程安全性,但是在遇到序列化对象时,默认的方式运行得到的结果就是多例的。
package com.atguigu.interview.chapter02;
/**
* @author helen
* @since 2019/7/22
*
* 静态内部类(延迟加载,线程安全)
*/
public class Singleton5 {
/**
* 私有构造
*/
private Singleton5() {}
/**
* 静态内部类
*/
private static class InnerObject{
private static Singleton5 single = new Singleton5();
}
public static Singleton5 getInstance() {
return InnerObject.single;
}
}
(6)内部枚举类实现(防止反射攻击):
事实上,通过 Java 反射机制是能够实例化构造方法为 private 的类的。这也就是我们现在需要引入的枚举单例模式
package com.atguigu.interview.chapter02;
/**
* @author helen
* @since 2019/7/22
*/
public class SingletonFactory {
/**
* 内部枚举类
*/
private enum EnumSingleton{
Singleton;
private Singleton6 singleton;
//枚举类的构造方法在类加载是被实例化
private EnumSingleton(){
singleton = new Singleton6();
}
public Singleton6 getInstance(){
return singleton;
}
}
public static Singleton6 getInstance() {
return EnumSingleton.Singleton.getInstance();
}
}
class Singleton6 {
public Singleton6(){}
}
Nginx
简单介绍一下Nginx
Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器。 Nginx 主要提供反向代理、负载均衡、动静分离(静态资源服务)等服务。下面我简单地介绍一下这些名词。
反向代理
谈到反向代理,就不得不提一下正向代理。无论是正向代理,还是反向代理,说到底,就是代理模式的衍生版本罢了
正向代理:某些情况下,代理我们用户去访问服务器,需要用户手动的设置代理服务器的ip和端口号。正向代理比较常见的一个例子就是 VPN了。
反向代理: 是用来代理服务器的,代理我们要访问的目标服务器。代理服务器接受请求,然后将请求转发给内部网络的服务器,并将从服务器上得到的结果返回给客户端,此时代理服务器对外就表现为一个服务器。
所以,简单的理解,就是正向代理是为客户端做代理,代替客户端去访问服务器,而反向代理是为服务器做代理,代替服务器接受客户端请求。
负载均衡
在高并发情况下需要使用,其原理就是将并发请求分摊到多个服务器执行,减轻每台服务器的压力,多台服务器(集群)共同完成工作任务,从而提高了数据的吞吐量。
Nginx支持的weight轮询(默认)、ip_hash、fair、url_hash这四种负载均衡调度算法,感兴趣的可以自行查阅。
负载均衡相比于反向代理更侧重的时将请求分担到多台服务器上去,所以谈论负载均衡只有在提供某服务的服务器大于两台时才有意义。
动静分离
动静分离是让动态网站里的动态网页根据一定规则把不变的资源和经常变的资源区分开来,动静资源做好了拆分以后,我们就可以根据静态资源的特点将其做缓存操作,这就是网站静态化处理的核心思路。
请列举Nginx的一些特性。
Nginx服务器的特性包括:
反向代理/L7负载均衡器
嵌入式Perl解释器
动态二进制升级
可用于重新编写URL,具有非常好的PCRE支持
为什么要用 Nginx?
Nginx 有以下5个优点:
Nginx 的四个主要组成部分了解吗?
Nginx 二进制可执行文件:由各模块源码编译出一个文件
Nginx.conf 配置文件:控制Nginx 行为
acess.log 访问日志: 记录每一条HTTP请求信息
error.log 错误日志:定位问题
请解释Nginx如何处理HTTP请求。
Nginx使用反应器模式。主事件循环等待操作系统发出准备事件的信号,这样数据就可以从套接字读取,在该实例中读取到缓冲区并进行处理。单个线程可以提供数万个并发连接。
在Nginx中,如何使用未定义的服务器名称来阻止处理请求?
只需将请求删除的服务器就可以定义为:
Server { listen 80;server_ name ““ ;return 444;}
这里,服务器名被保留为一个空字符串,它将在没有“主机”头字段的情况下匹配请求,而一个特殊的Nginx的非标准代码444被返回,从而终止连接.
使用“反向代理服务器”的优点是什么?
反向代理服务器可以隐藏源服务器的存在和特征。它充当互联网云和web服务器之间的中间层。这对于安全方面来说是很好的,特别是当您使用web托管服务时。
请列举Nginx服务器的最佳用途。
Nginx服务器的最佳用法是在网络.上部署动态HTTP内容,使用SCGI、WSGI应用程序服务器、用于脚本的FastCGl处理程序。它还可以作为负载均衡器。
请解释Nginx服务器上的Master和Worker进程分别是什么?
Master进程:读取及评估配置和维持
Worker进程:处理请求
请解释你如何通过不同于80的端口开启Nginx?
为了通过一个不 同的端口开启Nginx,你必须进入/etc/Nginx/sites-enabled/,如果这是默认文件,那么你必须打开名为“default"的文件。编辑文件,并放置在你想要的端口:
Like server { listen 81; }
请解释是否有可能将Nginx的错误替换为502错误、503?
502 =错误网关503 =服务器超载
有可能,但是您可以确保fastcgi_ intercept_ errors被设置为ON,并使用错误页面指令。
Location / {fastcgi_pass 127.0.01:9001;fastcgi_intercept_error
s on;error_page 502 =503/error_page.html;#...}
请解释ngx_ http_ upstream_ module 的作用是什么?
要在URL中保留双斜线,就必须使用merge_ slashes_ _off ;
语法: merge_ _slashes [on/off]
默认值: merge_ slashes on
环境: http, server
请解释ngx_ http_ upstream_ module 的作用是什么?
ngx_ http_ upstream module 用于定义可通过fastcgi 传递、proxy 传递、uwsgi传递、memcached传递和scgi传递指令来引用的服务器组。
请解释什么是C10K问题?
C10K问题是指无法同时处理大量客户端(10,000)的网络套接字。
请陈述stub_ status; 和sub_ filter指令的作用是什么?
Stub_ status 指令:该指令用于了解Nginx当前状态的当前状态,如当前的活动连接,接受和处理当前读/写/等待连接的总数
Sub_ filter 指令:它用于搜索和替换响应中的内容,并快速修复陈旧的数据
解释Nginx是否支持将请求压缩到上游?
您可以使用Nginx模块gunzip将请求压缩到上游。gunzip 模块是一个过滤器,它可以对不支持“gzip”编码方法的客户机或服务器使用“内容编码:gzip"来解压缩响应。
解释如何在Nginx中获得当前的时间?
要获得Nginx的当前时间,必须使用SSI模块、| $date_ _gmt 和 date_local的变量
Proxy_set_header THE-TIME $date_gmt;
用Nginx服务器解释-s的目的是什么?
用于运行Nginx -s参数的可执行文件。
解释如何在Nginx服务器上添加模块?
在编译过程中,必须选择Nginx模块,因为Nginx不支持模块的运行时间选择。
Nginx 轮询方式
Ip_hash:
采用 ip_hash 的轮询方法,每个 ip 在一定时间内会被固定的后端服务器,这样我们不用解决 session 共享问题
Url_hash
同一个 url(也就是同一个资源请求)会到达同一台机器
least_conn(最小连接)
算法很简单,首选遍历后端集群,比较每个后端的连接数,选取该值最小的后端。如果有多个后端的连接数值同为最
小的,那么对它们采用加权轮询算法。
轮询(默认)
轮询算法是把请求平均的转发给各个后端,使它们的负载大致相同。当然也可以设置 weight 值 weight 越 大 权重越大
Linux
Linux 常用命令
命令 命令解释
top 查看内存
df -h 查看磁盘存储情况
netstat -tunlp | grep 端口号 查看端口占用情况
ps aux 查看进程
chmod 修改或文件目录权限
tar 打包压缩或解压文件
touch 创建文件
mkdir 创建目录
rpm 安装 rpm 文件
cut 截取文件内容
df 检查文件系统的磁盘空间占用情况
rm 删除软件
tailf 动态查看日志
如何杀掉某个服务的进程
kill 命令用于终止进程
-9 强迫进程立即停止
如何查看测试项目的日志
一般测试的项目里面,有个 logs 的目录文件,会存放日志文件,有个 xxx.out 的文件,可以用 tail -f 动态实时查看后端日志
先 cd 到 logs 目录(里面有 xx.out 文件)
tail -f xx.out
这时屏幕上会动态实时显示当前的日志,ctr+c 停止
如何查看最近 1000 行日志
tail -1000 xx.out
LINUX 中如何查看某个端口是否被占用
netstat -anp | grep 端口号
find 查找文件
find / -name httpd.conf #在根目录下查找文件 httpd.conf,表示在整个硬盘查找
find /etc -name httpd.conf #在/etc 目录下文件 httpd.conf
find /etc -name ‘srm’ #使用通配符(0 或者任意多个)。表示在/etc 目录下查找文件名中含有字符串‘srm’的文件
find . -name ‘srm’ #表示当前目录下查找文件名开头是字符串‘srm’的文件
按照文件特征查找
find / -amin -10 # 查找在系统中最后 10 分钟访问的文件(access time)
find / -atime -2 # 查找在系统中最后 48 小时访问的文件
find / -empty # 查找在系统中为空的文件或者文件夹
find / -group cat # 查找在系统中属于 group 为 cat 的文件
find / -mmin -5 # 查找在系统中最后 5 分钟里修改过的文件(modify time)
find / -mtime -1 #查找在系统中最后 24 小时里修改过的文件
find / -user fred #查找在系统中属于 fred 这个用户的文件
find / -size +10000c #查找出大于 10000000 字节的文件(c:字节,w:双字,k:KB,M:MB,G:GB)
vim(vi)编辑器
有命令模式、输入模式、末行模式三种模式。
命令模式:查找内容(/abc、跳转到指定行(20gg)、跳转到尾行(G)、跳转到首行(gg)、删除行(dd)、插入行(o)、复制粘贴(yy,p)
输入模式:编辑文件内容
末行模式:保存退出(wq)、强制退出(q!)、显示文件行号(set number)
在命令模式下,输入 a 或 i 即可切换到输入模式,输入冒号(:)即可切换到末行模式;在输入模式和末行模式下,按 esc 键切换到命令模式
文件-rw-r–r--分别代表什么
r:read 就是读权限 --数字 4 表示
w:write 就是写权限 --数字 2 表示
x:excute 就是执行权限 --数字 1 表示
读、写、运行三项权限可以用数字表示,就是 r=4,w=2,x=1。所以,-rw-r–r--用数字表示成 644。
数据结构
Queue
什么是队列
队列是数据结构中比较重要的一种类型,它支持 FIFO,尾部添加、头部删除(先进队列的元素先出队列),跟我们生活中的排队类似。
队列的种类
单队列(单队列就是常见的队列, 每次添加元素时,都是添加到队尾,存在“假溢出”的问题也就是明明有位置却不能添加的情况)
循环队列(避免了“假溢出”的问题)
Java 集合框架中的队列 Queue
Java 集合中的 Queue 继承自 Collection 接口 ,Deque, LinkedList, PriorityQueue, BlockingQueue 等类都实现了它。 Queue 用来存放 等待处理元素 的集合,这种场景一般用于缓冲、并发访问。 除了继承 Collection 接口的一些方法,Queue 还添加了额外的 添加、删除、查询操作。
Set
什么是 Set
Set 继承于 Collection 接口,是一个不允许出现重复元素,并且无序的集合,主要 HashSet 和 TreeSet 两大实现类。
在判断重复元素的时候,Set 集合会调用 hashCode()和 equal()方法来实现。
补充:有序集合与无序集合说明
有序集合:集合里的元素可以根据 key 或 index 访问 (List、Map)
无序集合:集合里的元素只能遍历。(Set)
HashSet 和 TreeSet 底层数据结构
HashSet 是哈希表结构,主要利用 HashMap 的 key 来存储元素,计算插入元素的 hashCode 来获取元素在集合中的位置;TreeSet 是红黑树结构,每一个元素都是树中的一个节点,插入的元素都会进行排序;
List
什么是List
在 List 中,用户可以精确控制列表中每个元素的插入位置,另外用户可以通过整数索引(列表中的位置)访问元素,并搜索列表中的元素。 与 Set 不同,List 通常允许重复的元素。 另外 List 是有序集合而 Set 是无序集合。
List的常见实现类
ArrayList 是一个数组队列,相当于动态数组。它由数组实现,随机访问效率高,随机插入、随机删除效率低。
LinkedList 是一个双向链表。它也可以被当作堆栈、队列或双端队列进行操作。LinkedList随机访问效率低,但随机插入、随机删除效率高。
Vector 是矢量队列,和ArrayList一样,它也是一个动态数组,由数组实现。但是ArrayList是非线程安全的,而Vector是线程安全的。
Stack 是栈,它继承于Vector。它的特性是:先进后出(FILO, First In Last Out)。
性能优化
Tomcat的优化经验
Tomcat作为web服务器,它的处理性能直接关系到用户体验,下面是常见的优化措施:
●去掉对web. xml的监视,把jsp提前编辑成Servlet。有富余物理内存的情况,加大tomcat使用的jvm的内存。
●去掉对web. xml的监视,把jsp提前编辑成Servlet。有富余物理内存的情况,加大tomcat使用的jvm的内存。
●服务器资源服务器所能提供CPU、内存、硬盘的性能对处理能力有决定性影响。
●对于高并发情况下会有大量的运算,那么CPU的速度会直接影响到处理速度。
●内存在大量数据处理的情况下,将会有较大的内存容量需求,可以用 -Xmx- -Xms- -XX:MaxPermSize等参数对内存不同功能块进行划分。我们之前就遇到过内存分配不足,导致虚拟机一直处于full GC,从而导致处理能力严重下降。
●硬盘主要问题就是读写性能,当大量文件进行读写时,磁盘极容易成为性能瓶颈。最好的办法还是利用下面提到的缓存。
●利用缓存和压缩对于静态页面最好是能够缓存起来,这样就不必每次从磁盘.上读。这里我们采用了Nginx作为缓存服务器,将图片、CSS、 js文件都进行了缓存,有效的减少了后端tomcat的访问。另外,为了能加快网络传输速度,开启gzip压缩也是必不可少的。但考虑到tomcat已经需要处理很多东西了,所以把这个压缩的工作就交给前端的Nginx来完成。
除了文本可以用gzip压缩,其实很多图片也可以用图像处理工具预先进行压缩,找到一个平衡点可以让画质损失很小而文件可以减小很多。曾经我就见过一个图片从300多kb压缩到几十kb,自己几乎看不出来区别。
●采用集群单个服务器性能总是有限的,最好的办法自然是实现横向扩展,那么组建tomcat集群是有效提升性能的手段。我们还是采用了Nginx来作为请求分流的服务器,后端多个tomcat共享session来协同工作。可以参考之前写的《利用nginx+tomcat+memcached组建web服务器负载均衡》。
●优化tomcat参数这里以tomcat7的参数配置为例,需要修改conf/server.xml文件,主要是优化连接配置,关闭客户端dns查询。
Location / {fastcgi_pass 127.0.01:9001;fastcgi_intercept_error
s on;error_page 502 =503/error_page.html;#...}Server { listen 80;server_ name怎么理解 RESTful
http://www.cnblogs.com/artech/p/3506553.html
说说如何设计一个良好的 API
https://juejin.im/entry/59b8d34c6fb9a00a4455dd04
如何理解 RESTful API 的幂等性
http://blog.720ui.com/2016/restful_idempotent/
如何保证接口的幂等性
http://www.spring4all.com/article/914
说说 CAP 定理、 BASE 理论
http://my.oschina.net/foodon/blog/372703
怎么考虑数据一致性问题
https://opentalk.upyun.com/310.html
说说最终一致性的实现方案
http://www.cnblogs.com/soundcode/p/5590710.html
你怎么看待微服务
http://dockone.io/article/394
微服务与 SOA 的区别
http://dockone.io/article/2399
如何拆分服务
http://dockone.io/article/2516
微服务如何进行数据库管理
http://www.uml.org.cn/wfw/201705271.asp
如何应对微服务的链式调用异常
http://blog.720ui.com/2017/msa_design/?utm_source=tuicool&utm_medium=
referral
对于快速追踪与定位问题
依赖日志
微服务的安全
http://dockone.io/article/1507