java

synchronized

对象结构:markword(8 bytes),类指针,实例对象,对齐

markword:锁信息,GC信息,hashCode

锁消除

是发生在编译器级别的一种锁优化方式,有时候写的代码不需要加锁却执行了加锁操作;

锁粗化

通常情况下,为了保证多线程间的有效并发,会要求每个线程持有锁的时间尽可能短,但是大某些情况下,一个程序对同一个锁不间断、高频地请求、同步与释放,会消耗掉一定的系统资源,因为锁的讲求、同步与释放本身会带来性能损耗,这样高频的锁请求就反而不利于系统性能的优化了,虽然单次同步操作的时间可能很短。锁粗化就是告诉我们任何事情都有个度,有些情况下我们反而希望把很多次锁的请求合并成一个请求,以降低短时间内大量锁请求、同步、释放带来的性能损耗。

接口和抽象类的区别

1.抽象类可以有构造方法,接口中不能有构造方法;

2.抽象类中可以有普通成员变量,接口中没有普通成员变量;

3.抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法;

4.抽象类中的抽象方法的访问类型可以使public,protected,但是接口中的抽象方法必须是public的;

5.抽象类中可以包含静态方法,接口中不能包含静态方法;

6.抽象类和接口中都可以包含静态成员变量,抽象类中静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static final类型,并且默认即为public static final类型;

7.一个类只能继承一个抽象类,但能实现多个接口;

双亲委派机制的好处

避免重复加载

当父类已经加载了该类时,就没有必要让子类加载器再加载一次;

避免核心类被篡改

假设网络传递一个名为java.lang.Integer的类,通过双亲委派机制传递到启动类加载器,而启动类加载器在核心API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递过来的java.lang.Integer,而是直接返回已加载过的类,这样便可以防止核心API库被随意篡改。

java如何实现序列化

序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可以将流化后的对象传输于网络之间。

要实现序列化,需要让一个类实现Serializable接口,该接口是一个标识性接口,标识该类对象是可被序列化的,然后使用一个输出流来构造一个对象输出流并通过writeObject(Object obj)方法就可以将实现对象写出。如果需要反序列化则可以用一个输入流建立对象输入流,然后通过readObject方法从流中读取对象。序列化除了能够实现对象的持久化之外,还能够用于对象的深度克隆。

意义

可以使用序列化技术将对象持久化到磁盘或数据库;

数据只能以二进制形式才能在网络上进行传输,所以也需要用到序列化技术。

java泛型

java中的泛型是什么,有什么好处

泛型是一种参数化类型的机制,它可以使得代码适用于各种类型,从而编写更加通用的代码,例如集合框架;

泛型是一种编译时类型确认机制,提供了编译期的类型安全,确保在泛型类型上只能使用正确类型的对象,避免了在运行时出现ClassCastException。

java泛型如何工作,什么是类型擦除

泛型的正常工作是依赖编译器在编译源码的时候,先进行类型检查,然后进行类型擦除并且在类型参数出现的地方插入强制转换的相关指令实现的。

编译器在编译时擦除了所有类型相关的信息,所以在运行时不存在任何类型相关的信息。

java中如何实现深拷贝

类加载

加载

1.通过一个类的全限定名来获取定义此类的二进制字节流;

2.将这个字节流所代表的存储结构转化为方法区的运行时数据结构;

3.在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口;

验证

确保Class文件的字节流中包含的信息符号虚拟机规范的全部约束要求;

准备

正式为类中定义的静态变量分配内存并设置类变量初始值;

解析

是java虚拟机将常量池中的符号引用替换为直接引用的过程;

初始化

之前的几个加载阶段除了在加载阶段用户应用程序可以自定义类加载器的方式局部参与外,其余动作完全由java虚拟机来主导控制,直到初始化阶段,java虚拟机才真正开始执行类中编写的java程序代码,将主导权移交给应用程序。

如何打破双亲委派机制

  • 继承ClassLoader类,并重写findClass和loadClass方法,默认的loadClass方法是实现了双亲委派机制的逻辑,即会先让父类加载器加载,当无法加载时才由自己加载。这里为了破坏双亲委派机制必须重写loadClass方法。
  • 使用线程上下文类加载器,JNDI服务使用这个线程上下文类加载器去加载所需的SPI服务代码,这是一种父类加载器去请求子类加载器完成类加载的行为,这种行为实际打通了双亲委派模型的层次结构来逆向使用类加载器,已经违背了双亲委派模型的一般性原则。

协程

线程数量很多的时候会产生问题:一是系统线程会占用非常多的内存空间,而是过多的线程切换会占用大量的系统时间。

协程运行在线程之上,当一个协程执行完成后,可以选择主动让出,让另一个协程运行在当前线程之上。协程没有增加线程数量,只是在线程的基础之上通过分时复用的方式运行多个协程,而且协程的切换在用户态完成,切换的代价比线程从用户态到内核态的代价小很多。

JDK监控工具

  • jps:查看所有java进程启动类、传入参数和java虚拟机参数等信息。

    • jps:显示虚拟机执行主类名称以及这些进程的本地虚拟机唯一ID。
    • jps -q:只输出进程的本地虚拟机唯一ID。
    • jps -l:输出主类的全名,如果进程执行的是Jar包,输出Jar路径。
    • jps -v:输出虚拟机进程启动时JVM参数。
    • jps -m:输出传递给java进程main函数的参数。
  • jstat:用于监视虚拟机各种运行状态信息的命令行工具。它可以显示虚拟机进程中的类信息、内存、垃圾收集、JIT编译等运行数据,在没有GUI,只提供了纯文本控制台环境的服务器上,它将是运行期间定位虚拟机性能问题的首选工具。
    • jstat命令使用格式: jstat -[-t] [-h] [[]],比如jstat -gc -h3 31736 1000 10表示分析进程id为31736的gc情况,每个1000ms打印一次记录,打印10次停止,每3行后打印指标头部。
    • jstat -class vmid:显示ClassLoader的相关信息。
    • jstat -compiler vmid:显示JIT编译的相关信息。
    • jstat -gc vmid:显示与GC相关的堆信息。
    • jstat -gccapacity vmid:显示各个代的容量及使用情况。
    • jstat -gcnew vmid:显示新生代信息。
    • jstat -gcnewcapacity vmid:显示新生代大小与使用情况。
    • jstat -gcold vmid:显示老年代和永久代的行为统计,1.8之后仅表示老年代,因为永久代被移除了。
    • jstat -gcoldcapacity vmid:显示老年代的大小。
    • jstat -gcpermcapacity vmid:显示永久代大小,1.8之后该选项不存在了,因为永久代被移除了。
    • jstat -gcutil vmid:显示垃圾收集信息。
    • 另外加上-t参数可以在输出信息上加一个Timestamp列,显示程序的运行时间。
  • jinfo:输出当前JVM进程的全部参数和系统属性(第一部分是系统属性,第二部分是JVM的参数)
    • jinfo -flag name vmid:输出对应名称的参数的具体值。
    • 使用jinfo可以在不重启虚拟机的情况下动态修改JVM的参数。jinfo -flag [+|-]name vmid开启或者关闭对应名称的参数。
  • jmap:生成堆转储快照。
  • jhat:用于分析heapdump文件,它会建立一个HTTP/HTML服务器,让用户可以在浏览器上查看分析结果。
  • jstack:生成虚拟机当前时刻的线程快照,线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合。
    • 生成线程快照的目的主要是定位线程长时间出现停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等都是导致线程长时间停顿的原因。线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做些什么事情,或者在等待些什么资源。
  • 可视化分析工具
    • jconsole:检测死锁,显示内存信息,细化到Eden去,survivor区的详细情况,可强制执行GC。
    • visual vm。

MySQL

MySQL整体架构

客户端

MySQL Server

连接器:控制用户的连接
分析器:词法分析,语法分析
优化器:优化sql语句,规定执行流程
执行器:sql语句的实际执行

存储引擎:不同的存放位置,不同的文件格式

InnoDB:磁盘
MyISAM:磁盘
memory:内存

为什么使用B+树索引

Hash索引

1.利用hash存储的话需要将所有的数据文件添加到内存,比较耗费内存空间;

2.如果所有查询都是等值查询,那么hash确实很快,但是企业或者实际工作环境中范围查找数据更多,而不是等值查询,因此hash就不太合适了。

二叉树和红黑树

无论是二叉树还是红黑树,都会因为树的过深而造成IO次数变多,影响数据读取的效率。

B+树

相比B树每个节点可以包含更多的节点,这样做的原因有两个,一个是为了降低树的高度,另一个原因是将数据范围变为多个区间,区间越多,数据检索更快。

MySQL事务特性怎么保证

原子性:undo log,回滚日志。

在操作任何数据前,首先将数据备份到一个地方(这个存储数据备份的地方称为undo log),系统可以利用undo log中的备份将数据恢复到事务开始之前的状态。

持久性:redo log。

隔离性:锁;

MyISAM:共享锁,独占锁
InnoDB:共享锁,排他锁

一致性:数据库通过原子性,隔离性,持久性来保证一致性。

索引分类

回表:根据普通索引查找到聚簇索引的key值之后,再根据key值在聚簇索引中获取所有行记录。

最左匹配:对于联合索引的查询,如果精确匹配联合索引的左边连续一列或者多列,则mysql会一直向右匹配直到遇到范围查询就停止匹配。MySQL会对第一个索引字段数据进行排序,在第一个字段基础上,再对第二个字段进行排序。

索引覆盖:指一个索引包含或覆盖了所有需要查询的值,不需要回表查询,即索引本身存了对应的值。

索引下推:在多个条件查询中(一般在普通索引中使用),使用索引下推不会忽略其他条件,在本次查询中就过滤出来,无需再回表进行其他条件的判断,可以有效减少回表的次数,大大提升了查询的效率。

主从一致

解决方法:

1.忽略,业务可以接受,可以不优化;

2.强制读主库,高可用主库,用缓存提高读性能;

3.在cache里记录哪些记录发生过写请求,来路由读主还是读从;

四种隔离级别实现原理

读未提交

读不会加任何锁,写会加排他锁;

读已提交

写数据时加排他锁,读数据时不加锁使用了MVCC机制;

可重复读

与读提交级别不同的是MVCC版本的生成时机,一次事务只在第一次select时生成版本,后续的查询都是在这个版本上进行,从而实现了可重复读;

串行化

MVCC+next-key lock;

MySQL为什么往往以集群提供服务

在高并发流量下,数据库往往是服务端的瓶颈,由于数据库需要确保落地,同时保持数据同步、数据即时性、有效性的问题。

分库分表

MyBatis中#和$区别

1、#将传入的数据都当成一个字符串,会对传入的数据加一个双引号,如where username=#{username},传入111则为username=“111”,传入id则解析为username=“id”

2、$将传入的数据直接显示生成在sql中。如果传入111,解析为username=111;

3、#方式能很大程度防止SQL注入,$方式无法防止。

4、$方式一般用于传入数据库对象,例如传入表名。

5、一般能用#就不用$,若不得不使用就要手工的做好过滤工作,来防止SQL注入攻击。

6、在MyBatis中,xxx这样格式的参数会直接参与SQL编译,从而不能避免注入攻击。但涉及到动态表名和列名时,只能使用{xxx}这样格式的参数会直接参与SQL编译,从而不能避免注入攻击。但涉及到动态表名和列名时,只能使用xxx这样格式的参数会直接参与SQL编译,从而不能避免注入攻击。但涉及到动态表名和列名时,只能使用{}这样的格式,所以这样的参数在我们的代码中需要手工处理来防止注入。

什么是SQL注入

攻击者在界面的表单信息或URL上输入一些奇怪的SQL片段(例如‘1’=‘1’这样的语句),有可能侵入参数检验不足的应用程序。所以在我们的应用中需要做一些工作来防备这样的攻击方式。在一些安全性要求很高的应用中,经常使用将SQL语句全部替换为存储过程这样的方式来防止SQL注入。这当然是一种很安全的方式,但我们平时开发中可能不需要这种死板的方式。

MyBatis如何防止sql注入

MyBatis的SQL是一个具有“输入+输出”的功能,类似于函数的结构。其中parameterType表示了输入的参数类型,resultType表示了输出的参数类型。如果想要防止SQL注入,理所当然要在输入参数上下功夫。上面代码中使用#的即输入参数在SQL中拼接的部分,传入参数后,打印出执行的SQL语句,会看到SQL是这样的:

select id,username,password,role from user where username=? and password=?

不管输入什么参数,打印出的SQL都是这样的。这是因为MyBatis启动了预编译功能,在SQL执行前,会先将上面的SQL发送给数据库进行编译,执行时,直接使用编译好的SQL,替换占位符“?”就好了。因为SQL注入只能对编译过程起作用,所以这样的方式就很好的避免了SQL注入的问题。

Redis

编码方式

String:int raw embstr

如果一个字符串string保存的是整数值,如 set number 10086 ,那么这个整数值可以用 long 类型标识。那么该字符串的 redisObject会把10086这个数值保存在ptr属性中,并将encoding设置成int。如果字符串string 保存的是一个字符串值。并且这个字符串大于39个字节,那么字符串对象将使用一个 简单动态字符串(SDS) 来保存这个字符串值,并将 redisObject的encoding设置为raw。如果字符串string 保存的是一个字符串值,并且这个字符串小于39个字节,那么字符串将使用 embstr 编码的方式来保存这个字符串。

List:ziplist linkedlist

压缩列表是 节省内存而设计的内存结构(是redis创造的)。优点是节省内存,缺点是 比其他结构要消耗更多的时间,所以redis在数据量少的时候使用压缩列表存储。即列表长度少于512,并且所有元素的长度都少于64个字节时,使用压缩列表存储,否则使用linkedlist存储。

Hash:ziplist hashtable

当哈希对象保存的键值对数量少于512,且所有键值对的长度都少于64字节时,使用压缩列表保存。若哈希对象保存的键值对个数大于512,并且其中有键值对大于64个字节,就使用hashtable保存。

Set:intset hashtable

当 集合的长度少于 512 时,并且所有元素都是整数,使用 intset存储。否则使用 hashtable。

zset:ziplist skiplist

当zset的长度少于128,并且所有元素的长度都少于64字节时,用ziplist存储。

SDS

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-va0FLRuc-1628683151275)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210512101017836.png)]

底层是动态字符串int len:记录buf数组中已使用字节的长度int free:记录buf数组中未使用字节的长度char buf[]:字节数组,用于保存字符串
  • 常数复杂度获取字符串长度;

  • 杜绝缓冲区溢出;

当SDS需要进行修改时,会先检查SDS的空间是否满足修改所需的要求,如果不满足的话,API会自动将SDS的空间扩展至执行修改所需的大小,不会出现C字符串出现的缓冲区溢出问题。

  • 减少修改字符串时带来的内存重分配次数;

为了避免C字符串内存重分配次数过多的缺点,SDS通过未使用空间解除了字符串长度和底层数组长度之间的关联:在SDS中,通过未使用空间,SDS实现了空间预分配和惰性空间释放两种策略。

空间预分配用于优化SDS的字符串增长操作:当SDS的API对一个SDS进行修改,并且需要对SDS进行空间扩展的时候,程序不仅会为SDS分配修改必须要的空间,还会为SDS分配额外的未使用空间。  - 如果对SDS修改之后,SDS长度将小于1MB,那么程序分配和len属性同样大小的未使用空间。  - 如果修改之后将大于1MB,那么程序会分配1MB的未使用空间。
惰性空间释放用于优化SDS字符串缩短操作:当SDS的API需要缩短SDS保存的字符串时,程序并不立即使用内存重分配来回收缩短后多出来的字节,而是使用free属性将这些字节的数量记录起来,并等待将来使用。

LinkedList

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KMa4bSCw-1628683151276)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210512103815573.png)]

链表节点:

ListNode *prevListNode *next*value

链表结构:

*head:表头指针*tail:表尾指针len:链表长度计数器dup函数:用于复制链表节点所保存的值free函数:用于释放链表节点保存的值match函数:用于对比链表节点所保存的值和另一个输入值是否相等;

HashTable

哈希表:

table数组:数组中每个元素都是指向哈希表节点的指针size:哈希表大小used:哈希表目前已有节点的数量sizemask:size-1,与哈希值一起决定一个键该被放在table数组的哪个索引上面;

哈希表节点:

key:保存键值对中的键;v:键值对中的值*next:指向另一个哈希表的指针,解决哈希冲突问题;

SkipList

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-37j73IyA-1628683151278)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210512111710340.png)]

header:指向跳跃表的表头节点;
tail:指向跳跃表的表尾节点;
level:记录目前跳跃表内,层数最大的那个节点的层数(表头节点的层数不计算在内);
length:记录跳跃表的长度,也即是跳跃表目前包含节点的数量(表头节点不计算在内);level:每个层都带有两个属性:前进指针和跨度。前进指针用于访问位于表尾方向的其他节点,而跨度则记录了前进指针所指向节点和当前节点的距离。
后退指针:节点中BW字样标记节点的后退指针,它指向位于当前节点的前一个节点。后退指针在程序从表尾向表头遍历时使用。
分值:在跳跃表中,节点按各自所保存的分支从小到大排列。
成员对象:各个节点中的o1,o2等是节点保存的成员对象。

IntSet

contents数组:是整数集合的底层实现,整数集合的每个元素都是contents数组的一个数组项,各个项在数组中按值的大小从小到大有序的排列,并且数组中不包含重复项;
length:记录整数集合包含的元素数量,也即是contents数组的长度。

整数集合只支持升级操作,不支持降级操作。

ZipList

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D9oQXGnz-1628683151278)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210512140709079.png)]

zlbytes:记录整个压缩列表占用的内存字节数;
zltail:记录压缩列表表尾节点距离压缩列表起始地址有多少个字节;
zllen:记录了压缩列表包含的节点数量;
entryX:压缩列表包含的各个节点,节点的长度由节点保存的内容决定;
zlend:用于标记压缩列表的末端。

压缩列表节点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pB8LlTED-1628683151279)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210512141127236.png)]

previous_entry_length:记录了压缩列表中前一个节点的长度;
encoding:记录了节点的content属性所保存数据的类型以及长度;
content:负责保存节点的值,节点值可以是一个字节数组或者整数,值的类型和长度由节点的encoding属性决定。

为什么用缓存

高性能,高并发

怎么保证缓存和数据库一致性

淘汰缓存还是更新缓存

选择淘汰缓存,数据可能为简单数据也可能为较复杂数据,复杂数据进行缓存的更新操作,成本较高,因此推荐淘汰缓存。

先淘汰缓存还是先更新数据库

假入先更新数据库,再淘汰缓存,假如缓存淘汰失败,那么后面的请求都会得到脏数据,直至缓存过期;加入先淘汰缓存再更新数据库,如果数据库更新失败,只会产生一次缓存miss,相比而言,后者对业务影响更小一点。

延时双删策略

同时有一个请求A进行更新操作,另一个请求B进行查询操作:
1、请求A进行写操作,删除缓存;
2、请求B查询发现缓存不存在;
3、请求B去查询数据库得到旧值;
4、请求B将旧值写入缓存;
5、请求A将新值写入数据库;
这时就出现了数据不一致问题。

Redis适合的场景

会话缓存

全页缓存

队列

排行榜/计数器

RDB和AOF的优缺点

RDB优点

RDB会生成多个数据文件,每个数据文件都代表了某一个时刻的数据,这种多个数据文件的方式,非常适合做冷备份;

RDB在Redis对外提供读写服务的时候,影响非常的小,因为Redis主进程只需要fork一个子进程出来,让子进程对磁盘IO来进行持久化;

RDB在恢复大数据集时速度比AOF快;

RDB缺点

如果要Redis尽量避免在故障时丢失数据,那么RDB不如AOF。因为定时保存,因此可能丢失某一时间段的数据;

RDB每次fork出子进程执行RDB快照生成文件时,如果文件非常大,可能会导致客户端暂停提供服务一段时间;

AOF优点

AOF可以更好的保护数据不丢失,一般AOF会以每个1秒通过后台一个线程去执行一次fsync操作,如果Redis挂掉了,最多丢失1秒的数据;

AOF以append-only的模式写入,所以没有任何磁盘寻址的开销,写入性能非常高;

AOF日志文件的命令通过非常可读的方式进行记录,非常适合做灾难性的误删除恢复,如果某人不小心用flushall命令清空了所有数据,只要这个时候还没执行rewrite,就可以将日志文件中的flushall删除进行恢复;

AOF缺点

对于同一份数据备份文件,AOF比RDB大;

AOF开启后支持写的QPS会比RDB执行写的QPS低,因为AOF一般配置成每秒fsync操作。不过一般情况下,这种情况性能依然很高;

数据恢复比较慢,不适合做冷备;

如何选择

不要仅仅使用RDB,会丢失很多数据;

不要仅仅使用AOF,因为这样会有两个问题:第一通过AOF做冷备没有RDB做冷备恢复的速度快;第二RDB每次简单粗暴生成数据快照,更加健壮;

综合RDB和AOF两种持久化方式,用AOF来保证数据不丢失,作为恢复数据的第一选择;用RDB来做不同程度的冷备,在AOF文件都丢失或损坏不可用的时候,可以使用RDB进行快速的数据恢复。

使用Redis有哪些好处

(1) 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)

(2) 支持丰富数据类型,支持string,list,set,sorted set,hash

(3) 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行

(4) 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除

Memcache与Redis的区别都有哪些

1)存储方式

Memecache把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。

Redis有部份存在硬盘上,这样能保证数据的持久性。

2)数据支持类型

Memcache对数据类型支持相对简单。

Redis有复杂的数据类型。

3)value大小

redis最大可以达到1GB,而memcache只有1MB

Redis分区

概念: 分区是分割数据到多个Redis实例的处理过程,因此每个实例只保存key的一个子集

分区的优势:

  • 通过利用多台计算机内存的和值,允许我们构造更大的数据库
  • 通过多核和多台计算机,允许我们扩展计算机能力;通过多台计算机和网络适配器,允许我们扩展网络带宽,提高redis性能

分区的不足:

  • 多键操作是不被支持的,比如我们将要批量操作的键被映射到了不同的Redis实例中
  • 多键Redis事务是不被支持的
  • 分区的最小粒度是键,因此我们不能将关联到一个键的很大的数据集映射到不同的实例
  • 当应用分区表的时候,数据的处理是非常复杂的,比如我们需要处理多个rdb/aof文件,将分布在不同实例的文件聚集到一起备份
  • 添加和删除机器是很复杂的,例如Redis集群支持几乎运行时透明的因为增加或减少机器而需要做的realancing,然而像客户端和代理分区这种方式是不支持这种功能的。

分区的实现:

范围分区:

用户根据数据对应的ID从0到10000的用户映射R0 10000-20000用户映射到R1… 依次分区。

存在以下问题:

  • 需要一张表,用来存储ID范围到Redis实例的映射关系,比较难维护。
  • 对于以后的一种新的类型,我们都需要维护一张这样的表。每有一种新的类型我们就需要有一张映射表。
  • 如果我们要存储的数据的key不是整数,而是一组uuid就不好分区了

哈希分区:

哈希可以适应任何形式的key,而不像范围分区一样需要key的形式为int,比较简单。一个公式就可以表达:

id = hash(key)%N

hash可以使用(crc32函数)计算出一个数值型的值。然后按照分区的个数取余就行了N就是分区的个数

Redis分布式锁Redisson 实现分布式锁原理分析 - 知乎 (zhihu.com)

主要步骤

1、指定一个key作为锁标记,存入Redis中,指定一个唯一的用户标识作为value;
2、当key不存在时才能设置值,确保同一时间只有一个客户端进程获得锁,满足互斥性特性;
3、设置一个过期时间,防止因系统异常导致没能删除这个key,满足防死锁特性;
4、当处理完业务之后需要清除这个key来释放锁,清除key时需要校验value值,需要满足只有加锁的人才能释放锁。

Redisson:

加锁通过一段Lua脚本实现:
首先进行if判断,用exists myLock命令判断一下,如果要加锁的那个锁key不存在的话就进行加锁;
加锁:使用hincrby命令设置一个hash结构加锁。

锁互斥机制

锁的续期机制

Redis渐进式哈希

实际上Redis内部有两个哈希表,我们称之为ht0,ht1,传统的hashMap中只有一个。

为了避免rehash对服务器性能造成影响,服务器不是一次性将ht0里面所有的键值对全部rehash到ht1,而是分多次,渐进式的将ht0里面的键值对慢慢的rehash到ht1。

详细步骤:

1、为ht1分配空间,让字典同时持有两个哈希表;
2、在字典中维持一个索引计数器变量rehashidx,并将它的值设置为0,表示rehash工作正式开始;
3、在rehash执行期间,每次对字典执行添加、删除、查找或者更新操作时,程序除了执行指定的操作外,还会顺带将ht0哈希表在rehashidx索引上的索引键值对rehash到ht1,当rehash工作完成之后,程序将rehashidx属性的值增1;
4、随着字典操作的不断执行,最终在某个时间点上,ht0的所有键值对都会被rehash到ht1,这时程序将rehashidx的值设为-1,表示rehash操作已完成;渐进式rehash的好处在于它采取分而治之的方式,将rehash键值对所需的计算工作均摊到对字典的每个添加、删除、查找和更新操作上,从而避免了集中式rehash而带来的庞大计算量。

为什么使用跳跃表

首先因为zset要支持随机的插入和删除,所以不宜使用数组来实现。不用红黑树的原因如下:

1、性能考虑:在高并发的情况下,树形结构需要执行一些类似于rebalance这样的可能涉及整棵树的操作,相对来说跳跃表的变化只设计局部。

2、实现考虑:在复杂度与红黑树相同的情况下,跳跃表实现起来更简单,看起来也更加直观。

Redis事务

Redis事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。总结起来就是:Redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。

  • Redis事务没有隔离级别的概念:批量操作在发送EXEC命令前辈放入队列缓存,并不会被实际执行,也就不存在事务内的查询要看到事务里的更新,事务外查询不能看到。
  • Redis不保证原子性:Redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务 中任意命令执行失败,其余的指令仍会被执行。
  • Redis事务的三个阶段:开始事务、命令入队、执行事务。

Redis是单线程还是多线程

不同版本的Redis是不同的。Redis4.0之前,Redis是单线程运行的,但是单线程并不代表效率就低,因为底层采用了基于epoll的IO多路复用。

Redis是基于内存操作的,它的瓶颈在于机器的内存、网络带宽,而不是CPU,因为在CPU还没达到瓶颈的时候内存可能就先满了,或者带宽达到瓶颈了。因此CPU不是主要原因,那么自然就采用单线程了。更何况使用多线程还会面临一些额外问题,比如共享资源的保护等等。

Redis在4.0之前使用单线程的原因:

1、使用单线程模式的Redis,其开发和维护会更简单,因为单线程模型方便开发和调试;
2、即使使用单线程模型也能够并发的处理多客户端的请求,因为Redis内部使用了基于epoll的IO多路复用;
对于Redis而言,主要性能瓶颈是内存或网络带宽,而不是CPU。

单线程下运行快的几点原因:

1、基于内存操作,性能比较高;
2、数据结构简单,为自身量身打造的数据结构,查找和操作的时间复杂度都是O(1);
3、IO多路复用;
4、单线程模型,避免了不必要的上下文切换和多线程竞争;

Redis4.0开始引入多线程,但是此版本的多线程主要用于大数据量的异步删除,对于非删除操作的意义不是很大。

Redis6.0中的多线程则是真正为了提高I/O读写性能而引入的,它的主要实现思路是将主线程的I/O读写任务拆分给一组独立的子线程去执行。也就是说从socket中读数据和写数据不再由主线程负责,而是交给了多个子线程,这样就可以使多个socket的读写并行化了。这么做的原因在于,虽然Redis中使用了I/O多路复用和非阻塞IO,但我们知道数据在内核态和用户态空间之间的拷贝是无法避免的,而数据的拷贝这一步是阻塞的,并且数据量越大时拷贝所需要的时间就越多。所以Redis6.0引入了多线程,用于分摊桶内部读写IO压力,从而提升Redis的QPS。

但是注意,Redis的命令本身依旧是由Redis主线程串行执行的,只不过具体的读写操作交给独立的子线程去执行了。

Redis删除策略

被动删除:当客户端操作某个键时,Redis内部发现该键已经过期,就会自动删除该键,对CPU友好,内存不友好;

主动删除:随机选取一定数量对设置了过期时间对键,删除这批键中已经过期对键;若这批键中已过期键数占比超过25%,重复执行步骤1。(若过期键数太多,导致主动删除策略执行时长增加,会延迟客户端请求处理)

Redis内存满了怎么办

Redis定义了几种策略来处理这种情况:

noevicition(默认):对于写请求不再提供服务,直接返回错误;

allkeys-lru:从所有key中使用LRU算法进行淘汰;

volatile-lru:从设置了过期时间的key中使用LRU算法进行淘汰;

allkeys-random:从所有key中随机淘汰数据;

volatile-random:从设置了过期时间的key中随机淘汰;

volatile-ttl:在设置了过期时间的key中,根据key的过期时间进行淘汰,越早过期的越先淘汰;

Redis主从复制

1、从服务器连接主服务器,发送sync命令;

2、主服务器收到命令后,开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所有写命令;

3、主服务器BGSAVE执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令;

4、从服务器接收到快照文件后丢弃所有旧数据,载入收到的快照;

5、主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令;

6、从服务器完成对快照的载入,开始接受命令请求,并执行来自主服务器缓冲区的写命令;

Redis哨兵

它是Redis高可用性解决方案,由一个或多个sentinel实例组成的哨兵系统可以监视任意多个主服务器以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器。

主从模式的弊端就是不具备高可用性,当master挂掉以后,Redis将不再对外提供写入操作,因此哨兵模式应运而生。

Redis集群

1、单个Redis存在不稳定性,当Redis宕机了,就没有可用的服务了;

2、单个Redis的读写能力是有限的。

集群中如何判断是否有某个节点挂掉

集群中每一个节点都有这个集群中所有主节点以及从节点的信息,可以通过互相ping-pong来判断节点是否可以连接上,若是有一半以上的节点去ping一个节点都没有反应的时候,集群就会判定这个节点宕机,然后进行连接备用节点。

缓存和数据库双写时的数据一致性

采取正确更新策略,先更新数据库,再删缓存。因为可能存在删除缓存失败的问题,因此提供一个补偿措施即可,例如利用消息队列。

采用延时双删策略:

1、先淘汰缓存;
2、再写数据库
3、休眠1秒,再次淘汰缓存。

解释:

讨论三种更新策略:

1、先更新数据库,再更新缓存;
2、先删除缓存,再更新数据库;
3、先更新数据库,再删除缓存。

先删除数据库,再更新缓存:

线程安全角度:
线程A更新了数据库;
线程B更新了数据库;
线程B更新了缓存;
线程A更新了缓存;
这就出现请求A更新缓存应该比请求B更新缓存早,但是因为网络原因,B却比A更早更新了缓存,这就导致了脏数据,因此不考虑。

先删缓存,再更新数据库:

线程A删除缓存
线程B查询数据,发现缓存数据不存在
线程B查询数据库,得到旧值,写入缓存
线程A将新值更新到数据库

解决办法是延时双删。

操作系统

进程间通信的方式

1、管道/匿名管道:用于具有亲缘关系的父子进程间或者兄弟进程间的通信;

2、有名管道:匿名管道由于没有名字,只能用于亲缘关系的进程间通信。为了克服这个缺点,提出了有名管道。有名管道严格遵循先进先出,以磁盘文件的方式存在,可以实现本机任意两个进程通信;

3、信号:信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生;

4、消息队列:消息队列是消息的链表,具有特定的格式,存放在内存中并由消息队列标识符标识。管道和消息队列的通信数据都是先进先出的原则。与管道(无名管道:只存在于内存中的文件;命名管道:存在于实际的磁盘介质或者文件系统)不同的是消息队列存放在内核中,只有在内核重启(即操作系统重启)或者显式的删除一个消息队列时,该消息队列才会被真正的删除。消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取,比FIFO更有优势。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。

5、信号量:信号量是一个计数器,用于多进程对共享数据的访问,信号量的意图在于进程间同步。这种通信方式主要用于解决与同步相关的问题并避免竞争条件。

6、共享内存:使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。可以说这是最有用的进程间通信方式。

7、套接字:此方法主要用于在客户端和服务器之间通过网络进行通信,套接字是支持TCP/IP的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的双方的一种约定,用套接字中的相关函数来完成通信过程。

线程同步的几种方式

通过Object的wait和notify

通过Condition的await和signal

通过阻塞队列

通过volatile

通过synchronized

ReentrantLock

并发包

进程同步的四种方法

临界区

通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问;

优点:保证在某一时刻只有一个线程能访问数据的简便方法;

缺点:虽然临界区同步速度很快,但是只能同步本进程内的线程,而不可用来同步多个进程中的线程;

互斥量

比临界区复杂,互斥对象只有一个,只有拥有互斥对象的线程才具有访问资源的权限;

优点:使用互斥不仅仅能够在同一应用程序不同线程中实现资源的安全共享,而且可以在不同应用程序的线程之间实现对资源的安全共享;

缺点:①互斥量是可以命名的,也就是说它可以跨越进程使用,所以创建互斥量需要的资源更多,所以如果只为了在进程内部是用的话使用临界区会带来速度上的优势并能够减少资源占用量。因为互斥量是跨进程的互斥量一旦被创建,就可以通过名字打开它。

②通过互斥量可以指定资源被独占的方式使用,但如果有下面一种情况通过互斥量就无法处理,比如现在一位用户购买了一份三个并发访问许可的数据库系统,可以根据用户购买的访问许可数量来决定有多少个线程/进程能同时进行数据库操作,这时候如果利用互斥量就没有办法完成这个要求,信号量对象可以说是一种资源计数器。

信号量

它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。

优点:适用于对套接字程序中线程的同步。

缺点:①信号量机制必须有公共内存,不能用于分布式操作系统,这是它最大的弱点;

②信号量机制功能强大,但使用时对信号量的操作分散, 而且难以控制,读写和维护都很困难,加重了程序员的编码负担;

③核心操作P-V分散在各用户程序的代码中,不易控制和管理,一旦错误,后果严重,且不易发现和纠正。

事件

用来通知线程有一些事件已发生,从而启动后继任务的开始。

优点:事件对象通过通知操作的方式来保持线程的同步,并且可以实现不同进程中的线程同步操作。

进程调度算法

先到先服务调度算法

短作业优先调度算法

时间片轮转调度算法

多级反馈队列调度算法

优先级调度算法

死锁的必要条件和处理方法

条件:互斥、请求保持、不可剥夺、循环等待。

死锁预防

破坏互斥:不能被破坏;
破坏占有并等待:创建线程时要求申请所需的全部资源或在每个进程提出新的资源申请前释放它所占有的资源;
破坏不可剥夺:进行资源申请被拒绝时必须释放占有的资源或者请求另一个进程占有资源时操作系统可以抢占另一个进程,要求它释放资源;
破坏循环等待:将系统资源统一编号,依照编号顺序申请。

死锁避免

有序资源分配法
银行家算法:在系统进行资源分配之前,应先计算此次分配资源的安全性,若分配不会导致系统进入不安全状态,则分配,否则等待。为实现银行家算法,系统必须设置若干数据结构:安全序列是指一个进程序列{p1,p2,..,pn}是安全的,即对于每一个进程pi,它以后需要的资源量不超过系统当前剩余资源量与所有进程pj(j<i)当前占有资源量之和。

死锁检测

一般来说由于操作系统有并发、共享及随机性等特点,通过预防和避免的手段达到排除死锁的目的是很困难的,这需要很大的系统开销,而且不能充分利用资源。为此一种简便的方法是系统为进程分配资源时,不采取任何限制措施,但是提供了检测和解脱死锁的手段。

死锁解除

资源剥夺法:挂起某些死锁进程,并抢占它的资源,将这些资源分配给其他的死锁进程;
撤销进程法:强制撤销部分、甚至全部死锁进程并剥夺这些进程的资源;
进程回退法:让一个或多个进程回退到回避死锁的地步。

虚拟内存

是一种缓存思想,将主存看成是一个磁盘的高速缓存,主存中只保存活动区域,并根据需要在磁盘和主存之间来回传送数据。

技术实现:三种

请求分页存储管理:建立在分页管理之上,为了支持虚拟存储器功能而增加了请求调页功能和页面置换功能。请求分页是目前最常用的一种实现虚拟存储器的方法。请求分页存储管理系统中,在作业开始运行之前,仅装入当前要执行的部分段即可运行。假如在作业运行的过程中发现要访问的页面不在内存,则由处理器通知操作系统按照对应的页面置换算法将相应的页面调入到主存,同时操作系统也可以将暂时不用的页面置换到外存中。
请求分段存储管理:建立在分段存储管理之上,增加了请求调段功能、分段置换功能。请求分段存储管理方式就如同请求分页存储管理方式一样,在作业开始运行之前,仅装入当前要执行的部分段即可运行;在执行过程中,可使用请求调入中断动态装入要访问但又不在内存的程序段;当内存空间已满,而又需要装入新的段时,根据置换功能适当调出某个段,以便腾出空间而装入新的段。
请求段页式存储管理。

几种内存管理机制

  • 块式管理:将内存分为几个固定大小的块,每个块只包含一个进程,如果程序运行需要内存,操作系统就给它分配一块,如果程序运行只需要很小的空间,则分配的这块内存很大一部分就浪费了,存在很多未被利用的空间,也叫碎片。
  • 页式管理:把内存分为大小相等且固定的一页一页的形式,页比较小,相对于块式管理的划分力度更大,提高了内存利用率,减少了碎片。页式管理通过页表对应逻辑地址和物理地址。
  • 段式管理:页式管理虽然提高了内存利用率,但是其中的页没有任何实际意义,段式管理把主存分为一段一段,每一段的空间又比一页的空间小很多。但是段有实际意义,段式管理通过段表对应逻辑地址和物理地址。
  • 段页式管理:结合了段式管理和页式管理的优点,就是把主存分为若干段,每个段又分为若干页,也就是段页式管理机制中段和段之间以及段的内部都是离散的。

快表和多级页表

快表

为了解决虚拟地址到物理地址的转换速度,操作系统在页表方案的基础上引入了快表来加速虚拟地址到物理地址的转换。我们可以把快表理解为一块特殊的高速缓冲存储器,其中的内容是页表的一部分或全部内容。作为页表的缓存,它的作用和页表相似,但是提高了访问速率。由于采用页表做地址转换,读写内存数据时CPU要访问两次主存。有了快表,有时只要访问一次高速缓冲存储器,一次主存,这样可以加速查找并提高指令执行速度。

多级页表

引入多级页表的主要目的是为了避免把全部页表一直放在内存中占用过多空间,特别是那些根本不需要的页表就不需要保留在内存中。多级页表属于时间换空间的典型场景。

页面置换算法

OPT页面置换算法(最佳页面置换算法):最佳置换算法所选择的被淘汰页面将是以后永不使用的,或者是在最长时间内不再被访问的页面,这样可以保证最低的缺页率。但由于人们目前无法预知进程在内存下的若干页面中哪个是未来最长时间内不被访问的,因而该算法无法实现,一般作为衡量其他算法的方法。

FIFO页面置换算法(先进先出页面置换算法):总是淘汰最先进入内存的页面,即选择在内存中驻留时间最久的页面进行淘汰。

LRU页面置换算法(最近最久未使用页面置换算法):LRU赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间T,当需淘汰一个页面时,选择现有页面中T值最大的,即最近最久未使用的页面予以淘汰。

LFU页面置换算法(最少使用页面置换算法):选择在之前时间使用最少的页面作为淘汰页。

磁盘调度算法

先来先服务算法:根据进程请求访问磁盘的先后次序进行调度,优点是公平简单,缺点是吞吐量受限,平均寻道时间可能较长。

最短寻道时间优先算法:该算法选择这样的进程,其要求访问的磁道与当前磁头所在的磁道距离最近,以使每次的寻道时间最短,该算法可以得到比较好的吞吐量,但却不能保证平均寻道时间最短。

扫描算法(电梯调度):不仅考虑欲访问的磁道与当前磁道的距离,更优先考虑的是磁头的当前移动方向。

循环扫描算法:磁头单向移动。

阻塞式io和非阻塞式io的区别

阻塞IO:进程发起IO系统调用后,进程被阻塞,转到内核空间处理,整个IO处理完毕后返回进程。操作成功则进程获取到数据。

特点:

1、进程阻塞挂起不消耗CPU资源,及时响应每个操作;2、实现难度低,开发应用较容易;3、适用并发量小的网络应用开发;

注:不适用并发量大的应用,因为一个请求IO会阻塞进程,所以得为每个请求分配一个处理进程(线程)以及时响应,系统开销大。

非阻塞IO:进程发起IO系统调用后,如果内核缓冲区没有数据,需要到IO设备中读取,进程返回一个错误而不会被阻塞;进程发起IO系统调用后,如果内核缓冲有数据,内核就会把数据返回进程。

特点:

1、进程轮询调用,消耗CPU资源;2、实现难度低,开发应用相对阻塞IO模式较难;3、适用并发量小,且不需要及时响应的网络应用开发。

IO多路复用,讲下select、poll、epoll

IO复用模型:

多个进程的IO可以注册到一个复用器上,然后用一个进程调用该复用器,复用器会监听所有注册进来的IO。如果复用器设有监听的IO在内核缓冲区都没有可读数据,复用器调用进程会被阻塞;而当任一IO在内核缓冲区中有数据时,复用器调用就会返回。而后复用器调用进程可以自己或通知另外的进程来再次发起读取IO,读取内核中准备好的数据。

多个进程注册IO后,只有一个select调用进程被阻塞。

典型应用:select、poll、epoll三种方案,nginx可以选择使用这三个方案。

特点:

1、专一进程解决多个进程IO的阻塞问题,性能好;2、实现、开发应用难度较大;3、适用高并发服务应用开发,一个进程响应多个请求。

select、poll、epoll

1、Linux中IO复用的实现方式主要有这三种;2、select:注册IO、阻塞扫描,监听的IO最大连接数不能多于FD_SIZE;3、Poll:原理和select相似,没有数量限制,但IO数量大扫描线性性能下降;4、Epoll:事件驱动不阻塞,mmap实现内核与用户空间的消息传递,数量很大,Linux2.6之后内核支持。

select

函数接口:int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

select函数监视的文件描述符分三类,分别是writefds、readfds和exceptfds,内部使用三个位图实现。

调用后,select函数会阻塞,直到有描述符就绪(有数据可读、可写或者有except),或者超时(timeout指定等待时间,如果立即返回设为null即可),函数返回。

当select函数返回后,可以通过遍历fdset来找到就绪的描述符。select目前几乎在所有平台上支持,良好的跨平台支持也是它的一个优点。select的一个缺点在于单个进程能够监视的文件描述符数量存在最大限制,在Linux上一般为1024,可以通过修改宏定义甚至重新编译内核的方式提升这一限制,但是这样可能造成效率的下降。

poll

函数接口:int poll (struct pollfd *fds, unsigned int nfds, int timeout);

不同于select使用三个位图来表示三个fdset的方式,poll使用一个pollfds的指针实现。

pollfd结构包含了要监视的event和发生的event,不再使用select"参数-值"传递的方式。同时,pollfd并没有最大数量限制(但是数量过大后性能也会下降)。内部使用的是一个结构体的链表实现的。和select函数一样,poll返回后,需要轮训pollfd来获取就绪的描述符。select和poll都需要在返回后,通过遍历文件描述符来获取已经就绪的socket。事实上,同时连接的大量客户端在一时刻可能只是很少的处于就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降。

epoll

包括epoll_create、epoll_ctl和epoll_wait三个函数:

create函数创建epoll文件描述符,参数size并不是限制了epoll所能监听的描述符最大个数,只是对内核初始分配内部数据结构的一个建议。返回是epoll描述符,-1表示创建失败。

ctl函数控制对指定描述符fd执行op操作,event是与fd关联的监听事件。op操作有三种:添加EEPOLL_CTL_ADD,删除EPOLL_CTL_DEL,修改EPOLL_CTL_MOD。分别添加、删除和修改对fd的监听事件。

epoll_wait等待epfd上的io事件,最多返回maxevents个事件,timeout超时时间。

计算机网络

TCP粘包

TCP粘包就是指发送方发送的若干包数据到达接收方时粘成了一包,从接收缓冲区来看,后一包数据的投紧接着前一包数据的尾。原因可能是发送方也可能是接收方造成的。

发送方原因:TCP默认使用Nagle算法,将多次间隔较小、数据量较小的数据合并成一个数据量大的数据块,然后进行封包;

接收方原因:TCP将接收到的数据包保存在接收缓存里,然后应用程序主动从缓存读取收到的分组。这样一来,如果TCP接收数据包到缓存的速度大于应用程序从缓存中读取数据包的速度,多个包就会被缓存,应用程序就有可能读取到多个首尾相接粘到一起的包。

如果多个分组毫不相干,甚至是并列关系,那么这个时候就一定要处理粘包现象了。处理方法:发送方关闭Nagle算法。
接收方:接收方没有办法来处理粘包现象,只能将问题交给应用层来处理。应用层循环读取所有的数据,根据报文的长度判断每个包开始和结束的位置。

TCP为了保证可靠传输并减少额外的开销(每次发包都要验证),采用了基于流的传输,基于流的传输不认为消息是一条一条的,是无保护消息边界的。而UDP则是面向消息传输的,是有保护消息边界的,接收方一次只接受一条独立的信息,所以不存在粘包问题。

点对点与端对端的区别

点到点通信是针对数据链路层或网络层来说的,因为数据链路层只负责直接相连的两个节点之间的通信,一个节点的数据链路层接受ip层数据并封装之后,就把数据帧从链路上发送到与其相邻的下一个节点。点对点是基于MAC地址或IP地址,是指一个设备发数据给与该设备直接连接的其他设备,这台设备又在合适的时候将数据传递给与它直接相连的下一个设备,通过一台一台直接相连的设备把数据传递到接收端。

端到端通信时针对传输层来说的,传输层为网络的主机提供端到端的通信。因为无论TCP还是UDP协议,都要负责把上层交付的数据从发送端传输到接收端,不论其中间跨越多少节点。只不过TCP比UCP更可靠一些。所以称之为端到端,也就是从发送端到接收端。它是一个网络连接,指的是在数据传输之前,在发送端和接收端之间为数据的传输建立一条链路,链路建立以后,发送端就可以发送数据。

HTTP1.0、1.1、2.0之间的区别

HTTP1.1与HTTP1.0

1.默认开启长连接,在一个TCP连接上可以传送多个HTTP请求和响应;

2.缓存处理:在HTTP1.0中主要使用header里的If-Modified-Since,Expires来做为缓存判断的标准,HTTP1.1则引入了更多的缓存控制策略例如Entity tag,If-Unmodified-Since, If-Match, If-None-Match等更多可供选择的缓存头来控制缓存策略。

3.Host头处理:1.0请求的url并没有传递主机名(服务器与IP地址绑定),1.1请求和响应都支持Host头域(虚拟主机共享IP地址)

4.1.1新增24个错误状态响应码。409:请求的资源和资源的当前状态冲突,410:服务器资源永久性删除。

5.带宽优化以及网络连接的使用:1.1允许只请求资源的某个部分.

HTTP2.0与HTTP1.X

1.新的二进制格式:1.X的解析是基于文本的,而2.0的协议解析是采用二进制格式;

2.多路复用,即连接共享,即每一个request都是用作连接共享机制的;

3.header压缩;

4.服务端推送;

HTTP与HTTPS

HTTPS在传统的HTTP和TCP之间加了一层用于加密解密的SSL、TSL层;采用对称加密和非对称加密结合的方式保护浏览器和服务端之间的通信安全。

HTTPS加密过程

1.客户端向服务端发起第一次握手请求,告诉服务端客户端所支持的SSL的指定版本、加密算法及秘钥长度等信息;

2.服务端将自己的公钥发给数字证书认证机构,数字证书认证机构利用自己的私钥对服务器的公钥进行数字签名,并给服务器颁发数字证书;

3.服务端将证书发给客户端;

4.客户端利用数字证书认证机构的公钥向数字证书认证机构验证公钥证书上的数字签名,确认服务器公开秘钥的真实性;

5.客户端使用服务端的公开秘钥加密自己生成的对称秘钥,发给服务端;

6.服务端收到后利用私钥解密信息,获得客户端发来的对称秘钥;

7.通信双方可用对称秘钥来加密解密信息;

Cookie和Session区别

Cookie

Cookie是保存在客户端一个小数据块,其中包含了用户信息。当客户端向服务端发起请求,服务端
会像客户端浏览器发送一个Cookie,客户端会把Cookie存起来,当下次客户端再次请求服务端
时,会携带上这个Cookie,服务端会通过这个Cookie来确定身份。

Session

Session是通过Cookie实现的,和Cookie不同的是,Session是存在服务端的。当客户端浏览器第
一次访问服务器时,服务器会为浏览器创建一个sessionid,将sessionid放到Cookie中,存在客户
端浏览器。比如浏览器访问的是购物网站,将一本《图解HTTP》放到了购物车,当浏览器再次访
问服务器时,服务器会取出Cookie中的sessionid,并根据sessionid获取会话中的存储的信息,确
认浏览器的身份是上次将《图解HTTP》放入到购物车那个用户。

GET和POST区别

作用:get用于获取资源,post用于传输实体主体;

参数位置:get的参数放在URL中,post的参数存储在实体主体中,并且get方法提交的请求的URL中的数据最多是2048字节,post请求没有大小限制;

安全性:get方法因为参数在URL中,安全性相对post较差一些;

幂等性:GET方法是具有幂等性的,而POST方法不具有幂等性。这里幂等性指客户端连续发出多次请求,
收到的结果都是一样的。

TCP 报文结构

TCP 传送的数据单元是报文段,一个 TCP 报文段分为首部和数据两部分。首部的前 20 个字节是固定
的,后面有 4n 字节是根据需要而增加的选项,因此 TCP 首部的最小长度是 20 字节。TCP 首部的重要字
段如下:
源端口和目的端口:各占 2 字节,分别写入源端口号和目的端口号,TCP 的分用功能是通过端口实现
的,分用就是指运输层从 IP 层收到发送给各应用进程的数据后,把数据交付给正确的套接字的工作。
序号:占 4 字节。TCP 是面向字节流的,在一个 TCP 连接中传送的字节流中的每一个字节都按顺序编
号,首部中的序号字段值指的是本报文段所发送的数据的第一个字节的序号。序号使用 mod232 计算,
每增加到 231-1 后下一个序号就又回到 0。
确认号:占 4 字节,是期望收到对方下一个报文段的第一个数据字节的序号。如果确认号为 N,代表到
序号 N-1 为止的所有数据已经正确收到。序号有 32 位长,一般情况下可以保证当序号重复使用时,旧
序号的数据早已通过网络到达终点了。
数据偏移:占 4 字节,实际是TCP 报文段的首部长度,指出了 TCP 报文段的数据起始处到 TCP 报文段
的起始处的距离。由于首部中有长度不确定的选项字段,因此数据偏移字段是必要的。
标志字段:占 6 位。URG 是紧急标志,URG=1 时告诉系统此报文段中有紧急数据,应尽快传送,而不
按照原来的排队顺序传送,和紧急指针配合使用,紧急指针指出了本报文段中紧急数据的字节数和位
置。ACK 是确认标志,ACK=1 时表示成功接收了报文段。SYN 是同步标志,在建立连接时用来同步序
号,当 SYN=1 而 ACK=0 时,表示一个连接请求报文段,响应时 SYN 和 ACK 都为 1,因此 SYN=1 表示
一个连接请求或连接响应报文。FIN 是终止标志,用来释放一个连接,当 FIN=1 时表示报文段发送方的
数据已发送完毕,并要求释放连接。PSH 是推送标志,PSH=1 时接收方就不等待整个缓存填满了再向
上交付而是尽快交付数据。RST 是复位标志,当 RST=1 时表示 TCP 连接出现了严重错误,必须释放连
接再重新建立连接。
接收窗口:占 2 字节,指的是发送本报文段一方的接收窗口,告诉对方从本报文首部的确认号算起允许
对方发送的数据量。窗口值是用来限制发送方的发送窗口的,因为接收方的数据缓存空间是有限的。
检验和:占 2 字节,检验范围包括首部和数据两部分。在计算检验和时,要在 TCP 报文段的前面加上
12 字节的伪首部。

HTTP 报文格式

HTTP 报文有两种,分为请求报文和响应报文。

请求报文

HTTP 请求报文的第一行叫做请求行,其后继的行叫做首部行。请求行有三个字段,包括方法、URL 和
HTTP 版本。方法包括了 GET、POST、HEAD、PUT 和 DELETE 等。绝大部分的 HTTP 请求报文使用
GET 方法,当使用 GET 方法时,在 URL 字段中会带有请求对象的标识。
首部行指明了对象所在的主机,其实已经存在 TCP 连接了,但是还需要首部行提供主机信息,这时
Web 代理高速缓存所要求的。通过包含 Connection:close 的首部行,可以告诉服务器不要麻烦地使
用持续连接,它要求在发送完响应后就关闭连接。User-agent 可以用来指明用户代理,即向服务器发
送请求的浏览器类型,服务器可以有效地为不同类型的用户代理发送实际相同对象的不同版本。
在首部行之后有一个空行,后面跟着的是实体。使用 GET 方法时实体为空,而使用 POST 方法时才会
使用实体。当用户提交表单时,HTTP 客户通常使用 POST 方法,使用 POST 方法时用户仍可以向服务
器请求一个 Web 页面,但 Web 页面的特定内容依赖于用户在表单字段中输入的内容。如果使用 POST
方法,则实体中包含的就是用户在表单字段的输入值。表单不是必须使用 POST 方法,也可以使用
GET。
HEAD 方法类似于 GET,当服务器收到一个使用 HEAD 方法的请求时,将会用一个 HTTP 报文进行响
应,但是并不返回请求对象。通常开发者使用 HEAD 方法进行调试跟踪。PUT 方法常用于上传对象到
指定的 Web 服务器上指定的目录,DELETE 方法允许用户或应用程序删除 Web 服务器上的对象。
响应报文
响应报文包括状态行、首部行和实体。状态行有三个字段,协议版本、状态码和对应的状态信息。实体
是报文的主要部分,即所请求的对象本身。
服务器通过首部行来告诉浏览器一些信息。 Connection:close 可以告诉客户发送完报文后将关闭该
TCP 连接。Date 是首部行指示服务器发送响应报文的日期和时间,这个时间不是对象创建或修改的时
间,而是服务器从它的文件系统中检索到该对象,将该对象插入响应报文并发送的时间。Server 指明
了服务器的类型,类似于请求报文中的 User-agent 。

HTTP和HTTPS的区别

1、端口:HTTP的URL由“http://”开始且默认使用端口80,而HTTPS的URL由“https://”起始且默认使用端口443;

2、安全性和资源消耗:HTTP协议运行在TCP之上,所有传输的内容都是明文,客户端和服务器端都无法验证对方的身份。HTTPS是运行在SSL/TLS之上的HTTP协议,SSL/TLS运行在TCP之上,所有传输的内容都经过加密,加密采用对称加密,但对称加密的秘钥用服务器方的证书进行了非对称加密。所以说,HTTP安全性没有HTTPS高,但是HTTPS比HTTP耗费更多的服务器资源。

对称加密:密钥只有一个,加密解密为同一个密码,且加解密速度快,典型的对称加密算法有DES、AES等;
非对称加密:密钥成对出现(且根据公钥无法推知私钥,根据私钥也无法推知公钥),加密解密使用不同密钥(公钥加密需要私钥解密,私钥加密需要公钥解密),相对对称加密速度较慢,典型的非对称加密算法有RSA、DSA等;

HTTP的缺点:

1.HTTP报文使用明文的方式发送,内容可能会被窃听;2.HTTP协议中的请求和响应不会对通信方进行确认,有可能遭遇伪装;3.HTTP协议无法证明通信的报文完整性,接收到的内容可能已被篡改。

HTTPS=HTTP+加密+认证+完整性保护;

HTTPS的缺点:

1.除了和TCP连接,发送HTTP请求外,还必须和SSL进行通信,因此通信慢;2.SSL必须进行加密处理,在服务器和客户端都需要加密和解密的运算处理,因此更多的消耗硬件资源,导致负载增强;3.申请SSL证书需要费用。

CA证书

Linux

查看日志

通过cd进入服务器日志文件所存放的目录;

tail -f a.log 实时查看新追加的日志

还可以使用head,cat等;

可以使用grep进行过滤:grep match_pattern a.log

查找文件

根据文件名查找

find /root/ -name "*.txt" 在/root/目录下以文件名搜索以.txt结尾的文件;

根据文件时间查找文件

find /root/ -mtime -1 在/root/目录下查找一天内更改过的文件-atime,-amin:用户最近一次访问时间-mtime,-mmin:文件最后一次修改时间-ctime,-cmin:文件数据元(例如权限等)最后一次修改时间

根据文件内容查找文件

find /root/ | xargs grep "error":在root目录下查找内容包含error的文件。

spring

IOC

IOC即控制反转,简单来说是把原来代码里需要实现的对象创建、依赖反转给容器来帮忙,需要创建一个容器并且需要一种描述让容器知道要创建的对象间的关系,在spring中管理对象及其依赖关系是通过spring的IOC容器实现的。

IOC的实现方式是依赖注入。依赖注入指对象被动的接受依赖类而不是自己主动去找,对象不是从容器中查找它依赖的类,而是在容器实例化对象时主动将它依赖的类注入给它。

Bean的配置

spring IOC容器的目的是管理bean,这些bean将根据配置文件中的bean定义进行创建,而bean定义在容器内部由BeanDefinition对象表示,该定义主要包含以下信息:

  • 全限定类名:用于定义bean的实现类;
  • Bean行为定义:定义了Bean在容器中的行为;包括作用域(单例、原型创建)、是否惰性初始化及生命周期等;
  • Bean创建方式定义:说明是通过构造器还是工厂方法创建Bean;
  • Bean之间关系定义:即对其他bean的引用,也就是依赖关系定义,这些引用bean也可以称之为同事bean或依赖bean,也就是依赖注入;

Bean的命名

每个bean可以有一个或多个id(或称之为标识符或名字),在这里我们把第一个id称为“标识符”,其余id叫做“别名”;这些id在ioc容器中必须唯一。

实例化Bean

spring IOC容器需要根据Bean定义里的配置元数据使用反射机制来创建Bean。

AOP

AOP即面向切面编程,简单的说就是将代码中重复的部分抽取出来。在需要执行的时候使用动态代理技术,在不修改源码的基础上对方法进行增强。

spring根据类是否实现接口来判断动态代理的方式,如果实现接口会使用JDK的动态代理,核心是InvocationHandler接口和Proxy类,如果没有实现接口会使用CGLib动态代理,CGLib是在运行时动态生成某个类的子类,如果某个类被标记为final,不能使用CGLib。

JDK动态代理主要通过重组字节码实现,首先获得被代理对象的引用和所有接口,生成新的类必须实现代理类的所有接口,动态生成代码后编译新生成的.class文件并重新加载到JVM运行。JDK代理直接写Class字节码,CGLib是采用ASM框架写字节码,生成代理类的效率低。但是CGLib调用方法的效率高,因为JDK使用反射调用方法,CGLib使用FastClass机制为代理类和被代理类各生成一个类,这个类会为代理类或被代理类的方法生成一个index,这个index可以作为参数直接定位到要调用的方法。

常用场景包括权限认证、自动缓存、错误处理、日志、调试和事务等。

JDK和CGLib动态代理

jdk动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用invokeHandler来处理;

CGLib动态代理是利用asm开源包,将代理对象类的类文件加载进来,通过修改其字节码生成子类来处理;

如果目标对象实现了接口,默认情况采用JDK动态代理实现AOP;如果目标对象实现了接口,可以强制使用CGLib实现AOP;如果目标对象没有实现接口,必须采用CGLib库,spring会自动在两种方式之间转换;

JDK动态代理

优点:

1.不依赖第三方jar包,使用方便;

2.随着jdk的升级,jdk动态代理的性能在稳步提升;

缺点:

1.只能代理实现了接口的类;

2.执行速度较慢;

使用场景:如果你的程序需要频繁、反复的创建代理对象,则JDK动态代理在性能上更占优;

CGLib动态代理

优点:

1.由于是动态生成字节码实现代理,因此代理对象的执行速度较快,约为JDK动态代理的1.5-2倍;

2.可以代理没有实现接口的对象;

缺点:

1.不能代理final类;

2.动态生成字节码虽然执行较快,但是生成速度较慢,CGLib创建代理对象的速度比JDK慢10-15倍

使用场景:不需要频繁创建代理对象的应用,如spring中默认的单例bean,只需要在容器启动时生成一次代理对象。

SpringBoot

是spring的扩展,消除了设置spring应用程序所需的XML配置,可以更快更高效的开发生态系统。

特征:

1.创建独立的spring应用;

2.嵌入Tomcat,Jetty;

3.提供starters简化构建配置;

4.尽可能的自动配置spring应用;

5.完全没有代码生成和XML配置要求。

SpringBoot自动装配原理

bean生命周期

1.实例化Bean

2.设置bean的属性

3.检查Aware接口并设置相关依赖

4.检查BeanPostProcessor接口并进行前置处理

5.检查bean在spring配置文件中配置的init-method属性并自动调用其配置的初始化方法

6.检查BeanPostProcessor接口并进行后置处理

7.当Bean不再需要时会经过清理阶段,如果bean实现了DisposableBean这个接口,会调用那个其实现的destroy方法

8.最后,如果这个bean的spring配置中配置了destroy-method属性,会自动调用其配置的初始化方法

循环依赖

spring中循环依赖有三种情况:

1.构造器注入形成的循环依赖。也就是beanB需要在beanA的构造函数中完成初始化,beanA也需要在beanB的构造函数中完成初始化,这种情况的结果就是两个bean都不能完成初始化,循环依赖难以解决。

2.setter注入构成的循环依赖。beanA需要在beanB的setter方法中完成初始化,beanB也需要在beanA的setter方法中完成初始化,spring设计的机制主要就是解决这种循环依赖。

3.prototype作用域bean的循环依赖。这种依赖同样无法解决,因为spring不会缓存prototype作用域的bean,而spring中循环依赖的解决正是通过缓存来实现的。

第二种情况中解决方案:

1.beanA进行初始化,并将自己进行初始化的状态记录下来,并提前向外暴露一个单例工程方法,从而使其他bean能引用到该bean;2.beanA中有beanB的依赖,于是开始初始化beanB;3.初始化beanB的过程中又发现beanB依赖了beanA,于是又进行beanA的初始化,这时发现beanA已经在初始化了,程序发现了存在的循环依赖,然后通过步骤一种暴露的单例工程方法拿到beanA的引用,从而beanB拿到了beanA的引用,完成注入,完成了初始化,如此beanB的引用也就可以被beanA拿到,从而beanA也就完成了初始化。

beanFactory和fatoryBean区别

BeanFactory是一个Bean工厂,使用简单工厂模式,是spring ioc容器顶级接口,可以理解为含有Bean集合的工厂类,作用是管理Bean,包括实例化、定位、配置对象及建立对象间的依赖。BeanFactory实例化后并不会自动实例化Bean,只有当Bean被使用时才实例化与装配依赖关系,属于延迟加载,适合多例模式。

FactoryBean是一个工厂Bean,使用了工厂方法模式,作用是生产其他Bean实例,可以通过实现该接口,提供一个工厂方法来自定义实例化Bean的逻辑。FactoryBean接口由BeanFactory中配置的对象实现,这些对象本身就是用于创建对象的工厂,如果一个bean实现了这个接口,那么他就是创建对象的工厂bean,而不是bean实例本身。

beanFactory和applicationContext

BeanFactory提供了IOC容器最基本功能,而ApplicationContext接口扩展了BeanFactory,还提供了与spring aop集成、国际化处理、事件传播及提供不同层次的Context实现。

springMVC

SpringMVC执行流程

1.用户发送请求到前端控制器DispatcherServlet

2.DispatcherServlet收到请求调用HandlerMapping处理器映射器

3.处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器一并返回给前端控制器

4.前端控制器通过处理器适配器(HandlerAdapter)调用处理器

5.执行处理器

6.处理器执行完成返回ModelAndView

7.处理器适配器将处理器执行结果ModelAndView返回给前端控制器

8.前端控制器将ModelAndView传给视图解析器(ViewResolver)

9.视图解析器解析后返回具体视图

10.前端控制器对视图进行渲染

11.前端控制器响应用户。

注解原理

注解本质上是一个继承了Annotation的特殊接口,其具体实现类是java运行时生成的动态代理类。我们通过反射获取注解时,返回的是java运行时生成的动态代理对象。通过代理对象调用自定义注解的方法,会最终调用AnnotationInvocationHandler的invoke方法。该方法会从memberValues这个map中索引出对应的值,而memberMap的来源是java常量池。

Mybatis

什么是Mybatis

Mybatis是一个半ORM,它内部封装了JDBC,开发时只需要关注SQL语句本身,不需要在花费精力去处理加载驱动,创建连接,创建statement等繁杂的过程。程序员直接编写原生态sql,可以严格控制sql执行性能,灵活度高。

Mybatis可以使用XML或注解来配置和映射原生信息,将POJO映射成数据库中的记录,避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。

Mybatis框架优点

  • 基于SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写在XML里,解除SQL与程序代码的耦合,便于统一管理。提供XML标签,支持编写动态SQL语句,并可重用;
  • 与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接。
  • 很好的与各种数据库兼容(因为Mybatis使用JDBC来连接数据库,所以JDBC支持的数据库,Mybatis都支持)
  • 能够与spring很好的集成

Mybatis的缺点

  • SQL语句编写工作量较大,尤其当字段多,关联表多时,编写SQL语句的功底有一定要求;
  • SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。

Mybatis和Hibernate有哪些不同

  • Mybatis与Hibernate不同,它不是一个完全的ORM框架,因为MyBatis需要程序员自己编写SQL语句;
  • MySQL直接编写原生态SQL,可以严格控制SQL执行性能,灵活度高,非常适合关系数据模型要求不高的软件开发,因为这类软件需求变化频繁,一旦需求变化要求迅速输出成功;
  • Hibernate对象关系映射能力强,数据库无关性好,对于关系模型要求高的软件,如果用Hibernate开发可以节省很多代码,提高效率。

为什么说Mybatis是半自动ORM映射工具

Hibernate属于全自动ORM映射工具,使用Hibernate查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。而Mybatis在查询关联对象或关联集合对象时,需要手动编写SQL来完成,所以称之为半自动ORM映射工具。

JDBC编程有哪些不足之处,Mybatis是如何解决的

  • 数据库连接的创建、释放频繁造成系统资源浪费从而影响了性能,如果使用数据库连接池就可以解决这个问题,当然JDBC同样能够使用数据源;(解决:在SQLMapConfig.xml中配置数据库连接池,使用数据库连接池管理数据库连接)
  • SQL语句在写代码中不容易维护,事件需求中SQL变化的可能性很大,SQL变动需要改变JAVA代码。(解决:将SQL语句配置在mapper.xml文件中与java代码分离)
  • 向SQL语句传递参数麻烦,因为SQL语句的where条件不一定,可能多也可能少,占位符需要和参数一一对应。(解决:Mybatis自动将java对象映射到sql语句)
  • 对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo对象解析比较方便啊。(解决:Mybatis自动将执行结果映射到java对象)

Mybatis编程步骤

  • 创建SQLSessionFactory
  • 通过SQLSessionFactory创建SQLSession
  • 通过SQLSession执行数据库操作
  • 调用session.commit提交事务
  • 调用session.close关闭会话

MyBatis与hibernate有哪些不同

  • MyBatis支持定制化SQL、存储过程以及高级映射的一种持久层框架。MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。MyBatis不是一种完全的ORM框架,需要程序员自己编写部分SQL语句,

#{}和$()的区别是什么

前者是预编译处理,后者是字符串替换。

Mybatis在处理#{}时会将SQL中的#{}替换为?,调用preparedStatement的set方法来赋值;

Mybatis在处理时会将SQL中的{}时会将SQL中的时会将SQL中的{}替换为变量的值。

使用#{}可以有效的防止SQL注入,提高系统安全性。

设计模式

工厂模式

简单工厂模式

凡是出现了大量不同种类的产品要创建,并且具有共同的接口时,可以通过工厂方法模式进行创建。

public interface Fruit {    public void produce();}public class Apple implements Fruit {    @Override    public void produce() {        System.out.println("生产苹果");    }}public class Banana implements Fruit {    @Override    public void produce() {        System.out.println("生产香蕉");    }}public class Factory {    public static Fruit getFruit(String s) throws Exception {        if(s.equals("Apple")){            return new Apple();        }else if(s.equals("Banana")){            return new Banana();        }else{            throw new Exception();        }    }}public class TestFactory {    public static void main(String[] args) {        Fruit fruit = null;        try {            fruit = Factory.getFruit("Banana");        } catch (Exception e) {            e.printStackTrace();        }        fruit.produce();    }}

抽象工厂模式

创建多个工厂类,不像上面一样如果增加产品则要去修改唯一的工厂类。

public interface Fruit {    public void produce();}public class Apple implements Fruit {    @Override    public void produce() {        System.out.println("生产苹果");    }}public class Banana implements Fruit {    @Override    public void produce() {        System.out.println("生产香蕉");    }}public interface Factory {   public Fruit produceFruit();}public class AppleFactory implements Factory {    @Override    public Fruit produceFruit() {        return new Apple();    }}public class BananaFactory implements Factory {    @Override    public Fruit produceFruit() {        return new Banana();    }}public class TestFactory {    public static void main(String[] args) {        Factory factory = new AppleFactory();        Fruit fruit = factory.produceFruit();        fruit.produce();    }}

单例模式

1.某些类创建比较频繁,对于一些大型对象,这是一笔很大的系统开销;

2.省去了new操作符,降低了系统内存的使用频率,减轻GC压力;

3.某些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了。

public class Singleton {    private static Singleton instance = new Singleton();    private Singleton(){    }    public static Singleton getInstance(){        return instance;    }}
public class Singleton {    private static Singleton instance;    private Singleton() {    }    public static synchronized Singleton getInstance(){        if(instance == null){            instance = new Singleton();        }        return instance;    }}
public class Singleton {    private static Singleton instance;    private Singleton() {    }    public static Singleton getInstance(){        if(instance == null){            synchronized(Singleton.class){              if(instance == null){                 instance = new Singleton();                }            }        }        return instance;    }}

原型模式

对一个原型对象进行复制、克隆产生类似新对象:将一个对象作为原型,对其进行复制、克隆,产生一个和元对象类似的新对象;

核心:核心的原型类Prototype,需要实现cloneable接口,重写Object的clone方法;

作用:使用原型模式创建对象比直接new一个对象在性能上好很多,因为Object类的clone方法是一个本地方法,它直接操作内存中的二进制流,特别是复制大对象时,性能的差别非常明显;

适配器模式

将某个类的接口转换成客户端期望的另一个接口表示,目的是消除由于接口不匹配所造成的类的兼容性问题。

装饰模式

给对象动态增加新功能,需持有对象实例:装饰模式就是给一个对象增加一些新的功能,而且是动态的,要求装饰对象和被装饰对象实现同一个接口,装饰对象持有被装饰对象的实例;

代理模式

持有被代理类的实例,进行操作前后控制:采用一个代理类调用原有的方法,且对产生的结果进行控制;

外观模式

集合所有操作到一个类:外观模式是为了解决类与类之间的依赖关系的,向spring一样,可以将类与类之间的关系配置到配置文件中,而外观模式就是将它们的关联放在一个Facade类中,降低了类与类之间的耦合度。

模板方法模式

抽象方法作为骨架,具体逻辑让子类实现:定义一个操作中算法的框架,而将一些步骤延迟到子类中,使得子类可以不改变算法的结构即可重定义该算法中的某些特定步骤,完成公共动作和特殊动作的分离。

观察者模式

当一个对象变化时,其他依赖该对象的对象都会收到通知。对象之间是一种一对多的关系,类似于邮件订阅,当你订阅,当后续有更新时会及时通知你。

六大原则

  • 开闭原则:对扩展开放,对修改关闭;
  • 里氏替换原则:任何基类可以出现的地方,子类一定可以出现;
  • 依赖倒转原则:只对接口编程,依赖于抽象而不依赖于具体;
  • 接口隔离原则:使用多个隔离的接口比使用单个接口要好;
  • 迪米特原则:也叫最少知道原则,一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立;
  • 合成复用原则:原则是尽量使用合成/聚合的方式,而不是使用继承。

netty

dubbo

kafka

为什么使用kafka,消息队列

缓冲和削峰:上游数据时有突发流量,下游可能扛不住。kafka在中间可以起到一个缓冲的作用,把消息暂时存在队列中,下游服务就可以按照自己的节奏进行慢慢处理。

解耦和扩展性:项目开始的时候并不能确定具体需求。消息队列可以作为一个接口层,解耦重要的业务流程,只需要遵守约定,针对数据编程即可获取扩展能力。

冗余:可以采用一对多的方式,一个生产者发布消息可以被多个订阅topic的服务消费到,供多个毫无关联的业务使用。

健壮性:消息队列可以堆积请求,所以消费端业务即使短时间死掉,也不会影响主要业务的正常进行。

异步通信:很多时候用户不想也不需要立即处理消息,消息队列提供了异步处理机制,允许用户把一个消息放入队列,但并不立即处理它。想向队列放入多少消息就放入多少,然后在需要的时候再去处理他们。

Kafka是什么,主要应用场景是什么?

Kafka是一个分布式流式处理平台。

流平台具有三个功能:

1 消息队列:发布和订阅消息流,这个功能类似于消息队列,这也是Kafka被归类为消息队列的原因;

2 容错的持久方式存储记录消息流:Kafka会把消息持久化到磁盘,有效避免了消息丢失的风险;

3 流式处理平台:在消息发布的时候进行处理,Kafka提供了一个完整的流式处理类库。

Kafka主要有两大应用场景:

1 消息队列:建立实时流数据管道,以可靠的在系统或应用程序之间获取数据;

2 数据处理:构建实时的流数据处理程序来转换或处理数据流。

什么是Producer,Consumer,Broker,Topic,Partition?

Kafka将生产者发布的消息发送到Topic中,需要这些消息的消费者可以订阅这些Topic,如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kQUX4xUE-1628683151280)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20201211103018636.png)]

1、Producer(生产者):产生消息的一方;

2、Consumer(消费者):消费消息的一方;

3、Broker(代理):可以看做是一个独立的Kafka实例。多个Kafka Broker组成一个Kafka Cluster。

每个Broker中又包含Topic以及Partition这两个重要的概念:

Topic(主题):Producer将消息发送到特定的主题,Consumer通过订阅特定的Topic(主题)来消费消息;

Partition(分区):Partition属于Topic的一部分。一个Topic可以有多个Partition,并且同一Topic下的Partition可以分布在不同的Broker上,这也就表明一个Topic可以横跨多个Broker。

Kafka多副本机制了解吗,带来了什么好处?

Kafka为分区引入了多副本机制。分区中的多个副本之间有一个叫做leader,其他副本称为follower。我们发送的消息会被发送到leader副本,然后follower副本才能从leader副本中拉取消息进行同步。

生产者和消费者只与leader副本交互。你可以理解为其他副本只是leader副本的拷贝,它们的存在只是为了保证消息存储的安全性。当leader副本发生故障时会从follower中选举出一个leader,但是follower中如果有和leader同步达不到要求的参加不了leader的竞选。

Kafka的多分区和多副本机制有什么好处

1 Kafka通过给特定Topic指定多个Partition,而各个Partition可以分布在不同的Broker上,这样便能提供比较好的并发能力(负载均衡)。

2 Partition可以指定对应的Replica(副本)数,这也极大的提高了消息存储的安全性,提高了容错能力,不过也相应增加了所需要的存储空间。

Kafka的设计是什么样的?

Kafka将消息以topic为单位

创建主题

kafka-topics.bat --create --bootstrap-server localhost:9092 --replication-factor 1 --partitions 1 --topic test

发送消息

kafka-console-producer.bat --broker-list localhost:9092 --topic test

例子:

package com.nowcoder.community;import org.apache.kafka.clients.consumer.ConsumerRecord;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.kafka.annotation.KafkaListener;import org.springframework.kafka.core.KafkaTemplate;import org.springframework.stereotype.Component;import org.springframework.test.context.ContextConfiguration;import org.springframework.test.context.junit4.SpringRunner;@RunWith(SpringRunner.class)@SpringBootTest@ContextConfiguration(classes = CommunityApplication.class)public class KafkaTests {    @Autowired    private KafkaProducer kafkaProducer;    @Test    public void testKafka() {        kafkaProducer.sendMessage("test", "你好");        kafkaProducer.sendMessage("test", "在吗");        try {            Thread.sleep(1000 * 10);        } catch (InterruptedException e) {            e.printStackTrace();        }    }}@Componentclass KafkaProducer {    @Autowired    private KafkaTemplate kafkaTemplate;    public void sendMessage(String topic, String content) {        kafkaTemplate.send(topic, content);    }}@Componentclass KafkaConsumer {    @KafkaListener(topics = {"test"})    public void handleMessage(ConsumerRecord record) {        System.out.println(record.value());    }}

Kafka中的ISA、AR代表什么?ISR的伸缩又指什么?

ISR:In-Sync Replicas,副本同步队列

AR:Assigned Replicas,所有副本

ISA是由Leader维护,follower从leader同步数据有一些延迟(包括延迟时间和延迟条数两个维度),任意一个超过阈值都会把follower剔除出ISA,存入OSR(Outof-Sync Replicas)列表,新加入的follower也会先存入OSR,AR=ISR+OSR。

Kafka中的broker是干什么的?

broker是消息的代理,producers往broker里指定的topic中写消息,consumers从broker里面拉取指定topic的消息,然后进行业务处理,broker在中间起到一个代理保存消息的中转站。

Kafka中的zookeeper起到什么作用,可以不用zookeeper吗?

zookeeper是一个分布式的协调组件,早期版本的Kafka用zk做meta信息存储,consumers的消费状态,group的管理以及offset的值。考虑到zk本身的一些因素以及整个架构较大概率存在单点问题,新版本逐渐弱化了zookeeper的作用,新的consumer使用了Kafka内部的group coordination协议,也减少了对zookeeper的依赖,但是broker依然依赖于zookeeper,zookeeper在Kafka中还用来选举controller和检测broker是否存活等。

kafka follower如何与leader同步数据?

Kafka的复制机制既不是完全的同步复制,也不是简单的异步复制。完全同步复制要求All Alive Followers都复制完,这条消息才会被认为commit,这种复制方式极大的影响了吞吐率。而异步复制方式下,Follower异步的从Leader复制数据,数据只要被Leader写入log就被认为已经commit,如果Leader挂掉会丢失数据,Kafka使用ISA的方式很好的均衡了确保数据不丢失以及吞吐率。Follower可以批量的从Leader复制数据,而且Leader充分利用磁盘顺序读以及send file(zero copy)机制,这样极大的提高复制性能,内部批量写磁盘,大幅减少了Follower与Leader的消息量差。

什么情况下Broker会从ISR中剔除去?

Leader会维护一个与其基本保持同步的副本队列,该列表称为ISR,每个Partition都会有一个ISR,而且是由Leader动态维护,如果一个Follower比一个Leader落后太多,或者超过一定时间未发起数据复制请求,则Leader将其从ISR中移除。

Kafka为什么那么快?

Cache,FileSystemCache PageCache

顺序写,由于现代的操作系统提供了预读和写技术,磁盘的顺序写大多数情况下比随机写内存还要快。

zero copy,零拷贝技术减少拷贝次数。

Batching of Messages,批量处理,合并小的请求,然后以流的方式进行交互,直顶网络上限;

拉模式,使用拉模式进行消息的获取消费,与消费端处理能力相符。

Kafka producers如何优化打入速度?

增加线程

提高batch size

增加更多produer实例

增加Partition数

设置acks=-1时,如果延时增大,可以增大num.replica.fetchers(Follower同步数据的线程数)来调节

跨数据中心的传输,增加socket缓冲区设置以及OS tcp缓冲区设置。

Kafka producer打数据,ack为0,-1,1的时候代表什么,设置-1的时候,什么情况下Leader会认为一条消息commit了

1:默认,数据发送到Kafka后,经过leader成功接收消息的确认,就算是发送成功了,在这种情况下,如果leader宕机了,则会丢失数据;

0:生产者将数据发送出去就不管了,不去等待任何返回,这种情况下数据传输效率最高,但是数据可靠性却是最低的。

-1:producer需要等待ISR中的所有follower都确认接收到数据后才算一次发送完成,可靠性最高。当ISR中所有副本都向leader发送ack时,leader才commit,这时候producer才能认为一个请求中的消息都commit了。

Kafka unclean配置代表什么,会对spark streaming消费有什么影响?

unclean.leader.election.enable为true的话,意味着非ISR集合的broker也可以参与选举,这样有可能会丢失数据,spark streaming在消费过程中拿到的end offset会突然变小,导致spark streaming job挂掉。如果unclean.leader.election.enable参数设置为true,就有可能发生数据丢失和数据不一致的情况,Kafka的可靠性就会降低,而如果unclean.leader.election.enable参数设置为false,Kafka的可用性就会降低。

如果leader crash时,ISR为空怎么办?

Kafka在broker端提供了一个配置参数:unclean.election.enable,这个参数有两个值:

true:默认,允许不同步副本成为leader,由于不同步副本的消息较为滞后,此时成为leader,可能会出现消息不一致的情况。

false:不允许不同步副本成为leader,此时如果发生ISR列表为空,则会一直等待旧leader恢复,降低了可用性。

Kafka的message格式是什么?

一个Kafka的message由一个固定长度的header和一个变长的消息体body组成。header部分由一个字节的magic(文件格式)和四个字节的CRC(用于判断body消息体是否正常)构成。

当magic为1的时候,会在magic和CRC32之间多一个字节的数据:attributes(保存一些相关属性,比如是否压缩、压缩格式等等);如果magic为0,就不存在attribute属性,body是由N个字节构成的一个消息体,包含了具体的key/value消息。

Kafka中consumer group是什么概念?

同样是逻辑上的概念,是Kafka实现单播和广播两种消息模型的手段。同一个Topic的数据,会广播给不同的group,同一个group中的worker,只有一个worker能拿到这个数据。换句话说,对于同一个topic,每个group都可以拿到同样的所有数据,但是数据进入group后只能被其中一个worker消费。group内的worker可以使用多线程或多进程来实现,也可以将进程分散在多台机器上,worker的数量通常不超过partition的数量,且二者最好保持整数倍关系,因为Kafka在设计时假定一个partition只能被一个worker消费(一个group内)。

Kafka中的消息是否丢失和重复消费?

可以从两方面分析:消息发送和消息消费

消息发送:Kafka消息发送有两种方式,同步和异步,默认是同步方式,可通过producer.type属性进行配置,Kafka通过配置request.required.acks属性来确认消息的生产:0:表示不进行消息接收是否成功的确认1:表示当leader接收成功时确认-1:表示leader和follower都接收成功时确认综上所述,有6种消息生产的情况,下面分情况分析消息丢失的场景:1.acks=0,不和kafka集群进行消息接收确认,则当网络异常、缓冲区满了等情况时,消息可能丢失;2.acks=1,同步模式下,只有leader确认成功后但挂掉了,副本没有同步,数据可能丢失;消息消费:Kafka消息消费有两个consumer接口,Low-level API和High-level API;low-level:消费者自己维护offset等值,可以实现对Kafka的完全控制;hign-level:封装了对partition和offset的管理,使用简单;如果使用高级接口,可能存在的一个问题是当消息消费者从集群中把消息取出来,并提交了新的消息offset之后,还没来得及消费就挂掉了,那么下次再消费时之前没消费成功的消息就诡异的消失了。解决办法:针对消息丢失,同步模式下,确认机制设为-1,即让消息写入leader和follower之后再确认消息发送成功,异步模式下,为防止缓冲区满,可以在配置文件设置不限制阻塞超时时间,当缓冲区满时让生产者一直处于阻塞状态;针对消息消费,将消息的唯一标识保存在外部介质中,每次消费时判断是否处理过即可。

为什么Kafka不支持读写分离?

在Kafka中,生产者写入消息、消费者读取消息的操作都是与leader副本进行交互的,从而实现的是一种主写主读的生产消费模型;

Kafka并不支持主写从读,因为主写从读有2个明显的缺点:

1.数据一致性问题:数据从主节点转到从节点必然会有一个延时的时间窗口,这个时间窗口会导致主从节点之间的数据不一致。某一时刻,在主节点和从节点中A数据的值都为X,之后将主节点中A的值修改为Y,那么在这个变更通知到从节点之前,应用读取从节点中的A数据的值并不为最新的Y,由此便产生了数据不一致的问题;

2.延时问题:类似Redis这种组件,数据从写入主节点到同步至从节点中的过程需要经历网络-主节点内存-网络-从节点内存这几个阶段,整个过程会耗费一定的时间。而在Kafka中,主从同步会比Redis更加耗时,他需要经历网络-主节点内存-主节点磁盘-网络-从节点内存-从节点磁盘这几个阶段。对于延时敏感的应用而言,主写从读的功能并不太适用。

Kafka中怎么体现消息顺序性

Kafka中每个partition中的消息在写入时都是有序的,消费时,每个partition只能被一个group中的一个消费者消费,保证了消费时也是有序的。

整个topic不保证有序,如果为了保证topic整个有序,需要将partition调整为1。

消费者提交消费位移时提交的是当前消费到的最新消息的offset还是offset+1

offset+1

Kafka如何实现延迟队列

Kafka并没有使用JDK自带的Timer或DelayQueue来实现延迟的功能,而是基于时间轮自定义了一个用于实现延迟功能的定时器(System Timer)。JDK的Timer和DelayQueue插入和删除操作的平均时间复杂度是O(nlog(n)),并不能满足Kafka的高性能要求,而基于时间轮可以将插入和删除操作的时间复杂度都降为O(1),时间轮的应用并非Kafka独有,其应用场景还有很多。

底层使用数组实现,数组中每个元素可以存放一个TimerTaskList对象,TimerTaskList是一个环形双向链表,在其中的链表项TimerTaskEntry封装了真正的定时任务TimerTask。

Kafka中到底是怎么推进时间的呢?Kafka中的定时器借助了JDK中的DelayQueue来协助推进时间轮。具体做法是对于每个使用到的TimerTaskList都会加入到DelayQueue中。Kafka中的TimingWheel专门用来执行插入和删除TimerTaskEntry的操作,而DelayQueue专门负责时间推进的任务。再试想一下,DelayQueue中的第一个超时任务列表的expiration为200ms,第二个超时任务为840ms,这里获取DelayQueue的队头只需要O(1)的时间复杂度。如果采用每秒定时推进,那么获取到第一个超时的任务列表时执行的200次推进中有199次属于“空推进”,而获取到第二个超时任务时有需要执行639次“空推进”,这样会无故空耗机器的性能资源,这里采用DelayQueue来辅助以少量空间换时间,从而做到了“精准推进”。Kafka中的定时器真可谓是“知人善用”,用TimingWheel做最擅长的任务添加和删除操作,而用DelayQueue做最擅长的时间推进工作,相辅相成.

zookeeper

java后端开发面经(一)相关推荐

  1. java 获取文件大小_阿里Java后端开发面经,面试官都替我感到绝望

    点关注,不迷路:持续更新Java相关技术及资讯!!! 内容源于群友投稿!记录一次阿里Java后端开发面经,分享给大家,感谢支持! 前言 秋招面试的第一家公司,也是第一次面试,真的超级紧张,从自我介绍到 ...

  2. 校招Java后端开发面经专栏——序

    目录 前言 本专栏将包含的内容 作者以往的免费Java基础专栏 本专栏内容索引 一.基础知识 二.实战面经 三.算法 四.其他经验 后记 前言 最近从各方面了解到的消息都显示:毕业生在逐年增多,各个企 ...

  3. 阿里Java后端开发面经,面试官都替我感到绝望

    前言 秋招面试的第一家公司,也是第一次面试,真的超级紧张,从自我介绍到项目介绍再到问题回答,面试表现真的是灾难级~ 由于我笔试做的还行,一面的时候准备的都是项目相关的问题,Java基础和框架相关的就没 ...

  4. 各大公司Java后端开发面试题总结

    ThreadLocal(线程变量副本) Synchronized实现内存共享,ThreadLocal为每个线程维护一个本地变量. 采用空间换时间,它用于线程间的数据隔离,为每一个使用该变量的线程提供一 ...

  5. 入职脉脉是一种什么体验?附上我的脉脉Java后端开发面经,本人已于上周成功入职!

    前言 总体来说面试过程非常舒服,面试官很和蔼,会引导你回答一些问题,深入浅出. 一面 数组和链表的区别 栈和堆的区别 你知道的树的种类 有了解过字典树吗 介绍一下红黑树 介绍一下B+树 讲一下贪心和动 ...

  6. 牛客网Java后端开发面经汇总

    感谢牛客网! 文章目录 Java基础知识 计算机网络知识 MySQL.数据库 常用数据结构 操作系统 Spring 设计模式 分布式 Redis Mybatis 消息队列 场景题 手撕代码 Linux ...

  7. JAVA后端开发面试题

    转载自 https://blog.csdn.net/sihai12345/article/details/79465620 本文来自百度网络的一篇文章,由于没有答案,现在整理了一些比较好的回答和好的博 ...

  8. “面试不败计划”:各大公司Java后端开发面试题总结

    文章有不当之处,欢迎指正,如果喜欢微信阅读,你也可以关注我的微信公众号:好好学java,获取优质学习资源. 1.ThreadLocal(线程变量副本) Synchronized实现内存共享,Threa ...

  9. 面试题:各大公司Java后端开发面试题总结 已看1 背1 有用 链接有必要看看

    ThreadLocal(线程变量副本)       --整理 Synchronized实现内存共享,ThreadLocal为每个线程维护一个本地变量. 采用空间换时间,它用于线程间的数据隔离,为每一个 ...

最新文章

  1. PostgreSQL client's startup packet different between logical and normal stream replication
  2. Install 802.1x In Fedora
  3. 为什么全网 都在说 iOS 开发不行了 ?
  4. Java黑皮书课后题第8章:8.28(严格相同的数组)如果两个二维数组m1和m2相应的元素相等的话,则认为它们是严格地相同的。编写一个方法,如果m1和m2是严格相同的话,返回true
  5. ClickHouse 副本协同原理:ReplicatedMergeTree引擎
  6. 格雷码问题:输出当输入为n时的格雷码
  7. sql数据库监控语句
  8. C语言根据日期(年,月,日)判断星期几(使用基姆拉尔森计算公式)
  9. Python升级至2.7方法
  10. mysql 转成树_Mysql树型结构2种方式及相互转换
  11. vba两个集合的差集_Python中的集合是如何使用的?
  12. css实现图片虚化_什么? CSS 阴影竟然还有这种骚操作 ?
  13. android编程好难,自学Android编程入门难吗
  14. H264 帧格式 封装 传输
  15. lua菜鸟教程_lua经典学习教程.pdf
  16. 怎样在表格中选出同一类_excel表格怎么将相同内容筛选出来
  17. antd form 表单数据校验·记
  18. Linux系统异常夯死或宕机分析
  19. LeetCode-分数排名
  20. grep 命令的使用

热门文章

  1. 什么是 Scrum 中的 Timeboxing? 1
  2. 笔记本屏幕变暗/调高亮度闪烁修复方法
  3. 【JMeter】压测工具的使用
  4. 番茄助手 Visual Assist X 下载安装及使用
  5. 3.1 YOLO系列理论(YOLOV1、YOLOV2、YOLOV3)
  6. 输入三角形的3条边长(均为正整数),如果不能构成一个三角形,则输出“not a triangle”;如果能够构成一个直角三角形,则输出“yes”;如果不能构成直角三角形,则输出“no”。
  7. 第八篇order订单专题(2)订单通知及属性
  8. 基于tensorflow的手写数字识别
  9. mysql 占用cpu高_mysqlCPU占用过高解决方案
  10. win10系统nfs服务器搭建