大纲
此文档只展示简历中涉及到的知识点!!(后期延伸)
- Java 基础扎实,阅读过集合框架的源码,如 HashMap,掌握反射、IO、Lambda 表达式等。
- 掌握 Java 并发编程,熟悉线程池、并发容器以及锁的机制,如 sychronized、ReentrantLock、AQS等。
- 了解 JVM,包括内存布局、类加载机制、垃圾回收算法、垃圾回收器的工作原理。
- 掌握 MySQL,包括索引、事务、日志和锁,熟悉 MVCC 的实现原理。
- 熟悉 Redis 的数据类型,能解决缓存穿透、缓存雪崩和缓存击穿等高并发场景下的常见问题,了解分布式锁和缓
存一致性策略。 - 熟悉 Spring、SpringBoot、SpringSecurity 等开发框架,深入理解 IOC 和 AOP、了解 Bean 的生命周期和循环
依赖处理,熟悉 SpringBoot 启动流程和自动装配原理。 - 熟悉 RabbitMQ 的使用,有处理消息丢失、重复消费和消息顺序性问题,以及延迟队列的实现的实战经验。
- 了解 SpringCloud 微服务技术和常用组件,如 Nacos、Seata、GateWay、OpenFeign、Sentinel。
- 熟悉计算机网络和操作系统知识,包括 TCP/IP、UDP、HTTP、HTTPS、DNS 协议、页面置换、进程间通信等。
- 熟悉 Linux 常用命令,如 chmod、nohup 等,了解 Docker 的常用操作,有部署项目的经验。
知识点
1.集合
Java 基础扎实,阅读过集合框架的源码,如 HashMap,掌握反射、IO、Lambda 表达式等。
有哪些常见的集合?底层结构是什么?有什么区别?
基础
ArrayList和LinkedList有什么区别?ArrayList扩容机制?
ArrayList怎么序列化的知道吗?为什么用transient修饰数组?
快速失败(fail-fast)和安全失败(fail-safe)了解吗?
有哪几种实现ArrayList线程安全的方法?
HashMap相关(重点)
底层数据结构?jdk1.7 和 1.8区别?
数据结构 初始化table和扩容 节点名 节点插入方式 Hash算法 Null的处理
https://blog.csdn.net/weixin_44141495/article/details/108402128
HashMap的put流程知道吗?HashMap怎么查找元素的呢?
HashMap的哈希/扰动函数是怎么设计的?为什么哈希/扰动函数能降hash碰撞?
为什么HashMap的容量是2的倍数呢?
你还知道哪些哈希函数的构造方法呢?解决哈希冲突有哪些方法呢?
你对红黑树了解多少?为什么不用二叉树/平衡树呢?
红黑树怎么保持平衡的知道吗?
为什么HashMap链表转红黑树的阈值为8呢?
扩容在什么时候呢?为什么扩容因子是0.75?
有什么办法能解决HashMap线程不安全的问题呢?
能具体说一下ConcurrentHashmap的实现吗?
jdk1.7:Segment(ReentrantLock) + HashEntry数组 + 单链表
jdk1.8:Node(CAS + synchronized) + 单链表 / 红黑树
Java7 中 ConcurrentHashMap
使用的分段锁,也就是每一个 Segment 上同时只有一个线程可以操作,每一个 Segment
都是一个类似 HashMap
数组的结构,它可以扩容,它的冲突会转化为链表。但是 Segment
的个数一但初始化就不能改变。
Java8 中的 ConcurrentHashMap
使用的 Synchronized
锁加 CAS 的机制。结构也由 Java7 中的 Segment
数组 + HashEntry
数组 + 链表 进化成了 Node 数组 + 链表 / 红黑树,Node 是类似于一个 HashEntry 的结构。它的冲突再达到一定大小时会转化成红黑树,在冲突小于一定数量时又退回链表。
理解:在 ConcurrentHashMap
中,大部分操作都是通过 CAS 操作来保证原子性的,因为 CAS 操作是一种非常高效的并发控制方式。但是在一些特定的情况下,如扩容或者将链表转换为红黑树时,需要对表头进行修改操作,这时就需要使用 synchronized
来保证对表头修改的原子性和可见性。这种方式在保证了并发安全的前提下,尽可能地减少了对锁的使用,从而提高了并发性能。
有些同学可能对 Synchronized
的性能存在疑问,其实 Synchronized
锁自从引入锁升级策略后,性能不再是问题,有兴趣的同学可以自己了解下 Synchronized
的锁升级。
能具体说一下ConcurrentHashmap的put和get的流程吗(1.7和1.8)?
**jdk1.7: ** 分段锁
put流程
整个流程和HashMap非常类似,只不过是先定位到具体的Segment,然后通过ReentrantLock去操作而已,后面的流程,就和HashMap基本上是一样的。
计算hash,定位到segment,segment如果是空就先初始化
使用ReentrantLock加锁,如果获取锁失败则尝试自旋,自旋超过次数就阻塞获取,保证一定获取锁成功
遍历HashEntry,就是和HashMap一样,数组中key和hash一样就直接替换,不存在就再插入链表,链表同样操作
get流程
get也很简单,key通过hash定位到segment,再遍历链表定位到具体的元素上,需要注意的是value是volatile的,所以get是不需要加锁的。
jdk1.8: CAS + synchronized
put流程
- 首先计算hash,遍历node数组,如果node是空的话,就通过CAS+自旋的方式初始化
- 如果当前数组位置是空则直接通过CAS自旋写入数据
- 写入数据同样判断链表、红黑树,链表写入和HashMap的方式一样,key hash一样就覆盖,反之就尾插法,链表长度超过8就转换成红黑树
get查询
get很简单,和HashMap基本相同,通过key计算位置,table该位置key相同就返回,如果是红黑树按照红黑树获取,否则就遍历链表获取。
为什么Hashtable和ConcurrentHashMap 是不允许键或值为 null 的,HashMap 的键值则都可以为 null?
https://blog.csdn.net/cy973071263/article/details/126354336
HashMap和HashTable的区别?
底层数据结构 线程安全 初始化数组大小及扩容倍数 是否可以为null
https://blog.csdn.net/clearLB/article/details/107903603
HashMap和concurrentHashMap的区别?
1.7 1.8 区别
https://blog.csdn.net/qq_42949615/article/details/124109063?spm=1001.2014.3001.5502
ConcurrentHashMap 和 Hashtable 的区别?
数据结构 初始化和扩容倍数 线程安全的实现方式 (重要!!)
https://javaguide.cn/java/collection/java-collection-questions-02.html#concurrenthashmap-%E5%92%8C-hashtable-%E7%9A%84%E5%8C%BA%E5%88%AB
CopyOnWriteArrayList了解多少?
写时复制:一种优化策略。读写分离,空间换时间:CopyOnWriteArrayList容器允许并发读,读操作是无锁的,性能较高。至于写操作,比如向容器中添加一个元素,则首先将当前容器复制一份,然后在新副本上执行写操作,结束之后再将原容器的引用指向新容器。通过 **锁(ReentrantLock) + 数组拷贝 + volatile ** 保证线程安全。
优点:并发度性能高,适合读多写少的并发环境。
缺点:内存占用大, **其存在数据一致性问题:**CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。如果希望写入的的数据,马上能读到,不要使用CopyOnWrite容器。
讲讲 LinkedHashMap 怎么实现有序的?
讲讲 TreeMap 怎么实现有序的?
Queue下有哪些子类?各自数据结构?
https://blog.csdn.net/weixin_43895362/article/details/135962789
你能自己设计实现一个HashMap吗?(kuaishou)
2.JUC
掌握 Java 并发编程,熟悉线程池、并发容器以及锁的机制,如 sychronized、ReentrantLock、AQS等。
- 掌握 Java 并发编程,熟悉线程池、并发容器以及锁的机制,如 sychronized、ReentrantLock、AQS等。
2.1线程池机制
并发专题汇总:https://blog.csdn.net/qq_59366033/article/details/132391455
线程池概念:
线程池就是采用池化思想(类似连接池、常量池、对象池等)管理线程的工具,不需要每次接收到一个请求就创建一个线程去处理,用的时候直接从线程池取,用完放回去,这样可以重复使用,大大节省了创建线程的资源开销。
最常用的包括ThreadPoolExecutor、ScheduledThreadPoolExecutor、ForkJoinPool等。ThreadPoolExecutor是最通用的线程池实现。
创建线程池的方法有哪些?
newFixedThreadPool newSingleThreadExecutor newCachedThreadPool newScheduledThreadPool等。
ThreadPoolExecutor的核心参数是什么?工作原理是什么?拒绝策略有哪些?
核心线程数(corePoolSize):池中保持的最小线程数。
最大线程数(maximumPoolSize):池中最大允许的线程数。
线程空闲时间(keepAliveTime):当线程数大于核心线程数时,多余的线程在空闲指定时间后被销毁。
线程存活时间单位(TimeUnit unit)
阻塞队列(BlockingQueue):等待执行的任务队列(5个)。
线程工厂(ThreadFactory threadFactory):用于创建线程,execute()。
拒绝策略(RejectedExecutionHandler):当队列满并且线程数达到最大线程数时,用于处理新任务的策略。
工作原理
如果线程池中的线程数少于核心线程数,创建新线程执行任务。
如果线程池中的线程数达到核心线程数,将任务放入阻塞队列。
如果队列已满,但线程数未达到最大线程数,创建新线程执行任务。
如果队列已满且线程数已达到最大线程数,根据拒绝策略处理任务。
拒绝策略:
AbortPolicy:终止策略,线程池会抛出异常并终止执行此任务;
CallerRunsPolciy:把任务交给调用者的(main)线程来执行;
DiscardPolicy:忽略当前最新任务(最新的任务);
DiscardOldestPolicy:忽略任务队列中最早加入的任务,接收新任务(加入任务队列)
如何确定核心线程数?(难)
① 高并发、任务执行时间短 -->( CPU核数+1 ),减少线程上下文的切换
② 并发不高、任务执行时间长
IO密集型的任务 --> (CPU核数 * 2 + 1)
计算密集型任务 --> ( CPU核数+1 )
③ 并发高、业务执行时间长,解决这种类型任务的关键不在于线程池而在于整体架构的设计,看看这些业务里面某些数据是否能做缓存是第一步,增加服务器
是第二步,至于线程池的设置,设置参考(2)
为什么不建议用Executors创建线程池,而是使用ThreadPoolExecutor?
允许的请求队列的长度为Integer.MAX_VALUE,可能会堆积大量的请求,导致OOM。
线程池使用场景CountDownLatch、Future(你们项目哪里用到了多线程)
谈谈你对ThreadLocal的理解?底层原理实现?内存泄露问题知道吗?
如何终止线程池?
shutdown:“温柔”的关闭线程池。不接受新任务,但是在关闭前会将之前提交的任务处理完毕。
shutdownNow:“粗暴”的关闭线程池,也就是直接关闭线程池,通过 Thread#interrupt() 方法终止所有线程,不会等待之前提交的任务执行完毕。但是会返回队列中未处理的任务。
如何判断线程池中的任务是否执行完成?
1、使用 isTerminated 方法判断
2、使用 getCompletedTaskCount方法判断
3、使用CountDownLatch判断
4、使用 CyclicBarrier判断
…
2.2 并发容器
Java中的并发容器主要包括以下几类:
Map 类型:
ConcurrentHashMap:线程安全的HashMap实现,通过锁分段技术和CAS算法保证线程安全和运行效率。在JDK 1.7中采用分段锁来减少锁的竞争,而在JDK 1.8中则直接使用Node数组+链表/红黑树的数据结构来实现,并发控制使用synchronized和CAS操作。
ConcurrentSkipListMap:基于跳表的并发Map实现,使用跳表的数据结构进行快速查找。
跳表原理:链表加多级索引的结构 https://blog.csdn.net/2401_83977530/article/details/137216610
List 类型:
- CopyOnWriteArrayList:线程安全的List实现,适用于读多写少的场景。当新增和删除元素时,会创建一个新的数组,在新的数组中增加或者排除指定对象,最后用新增数组替换原来的数组。
Queue 类型:
- ConcurrentLinkedQueue:基于链表实现的并发队列,使用乐观锁(CAS)保证线程安全。
- ConcurrentLinkedDeque:基于双向链表实现的并发队列,可以分别对头尾进行操作,因此除了先进先出(FIFO)外,也可以先进后出(FILO)。
Set 类型:
- CopyOnWriteArraySet:基于CopyOnWriteArrayList实现的并发Set,每次add都要遍历整个集合才能知道是否存在,不存在时需要插入(加锁)。
这些并发容器通过不同的机制保证线程安全,如锁分段技术、CAS算法等,以提高并发访问的性能和效率。
2.3锁机制
Java中的锁:
对锁的态度:悲观锁 乐观锁 (ReentrantLock是悲观锁:https://blog.csdn.net/m0_38045306/article/details/119812773)
对数据的操作方式:ReadWriteLock / 共享锁 互斥锁
可重入锁:synchronized ReentrantLock
可重试/自旋锁:SpinLock (CAS + 循环) 常见的如ReentrantLock (但是sync不是自旋锁!!!)
是否公平:公平锁 不公平锁
锁的状态:无锁 , 偏向锁 , 轻量级锁 , 重量级锁
AQS
分段锁
死锁
死锁是指两个或两个以上的线程在执行过程中,因抢夺资源而造成的一种互相等待的现象。
排查死锁方法:
- 纯命令
-
- jps -l
- jstack 进程编号
- 图形化
-
- jconsole
预防死锁的方法:
- 按顺序获取锁:确保所有线程获取锁的顺序是一致的,这样可以避免循环等待的情况发生。
- 使用超时等待:在获取锁的时候使用超时等待机制,如果超过一定时间还未获取到锁,可以放弃或重新尝试。
- 避免嵌套锁:尽量避免在持有一个锁的情况下去获取另一个锁,这可能导致死锁。
- 使用锁的粒度尽量小:锁的粒度越小,发生死锁的可能性越小。
- 使用工具检测死锁:可以使用一些工具来检测和排查死锁,如上述的
jstack
和jconsole
工具。
注意:ReentrantLock是悲观锁,底层是基于AQS,虽然用到了CAS乐观锁,但这里的CAS只是为了判断线程是否获取到了锁,保证获取锁和释放锁操作的原子性,而不是ReentrantLock用的CAS判断共享值有没有被改变。。
CAS(Compare-And-Swap)在 ReentrantLock 中的作用主要是确保锁状态的原子性更新,以保证线程安全。即保证获取锁的时候锁状态更新的线程安全问题,防止并发时多个线程都获取锁成功。释放锁也需要提高CAS保证释放操作的原子性,防止出现死锁的情况。
2.4 synchronized 和 ReentrantLock 的区别?
1.实现方式:synchronized是Java语言内置的关键字,基于JVM实现,锁的获取和释放是交给jvm自动处理的;ReentrantLock是基于Java的Lock接口实现,需要手动编写代码,并使用lock()方法获取锁,使用完毕后需要显式调用unlock()方法释放锁。。
2.是否是自旋锁:都是可重入锁,但是synchronized不是自旋锁,获取锁失败就会阻塞等待,等待过程不能被中断;ReentrantLock是自旋锁,利用了CAS+循环,获取锁失败就会不断的重试获取锁。
3.锁的公平性:synchronized是非公平锁;ReentrantLock既可以是公平锁也可以是非公平锁,默认情况下是非公平锁。
4.条件变量支持。synchronized没有直接对应的条件变量功能;ReentrantLock可以通过Condition对象实现线程间的等待和唤醒操作。
5.性能表现:在低并发情况下,synchronized的性能通常优于ReentrantLock;在高并发情况下,ReentrantLock的性能可能更佳,因为它提供了更多的控制选项和更灵活的锁策略。
synchronized底层原理及锁升级
基本原理:
锁升级过程:
Java中的synchronized有偏向锁、轻量级锁、重量级锁三种形式,分别对应了锁只被一个线程持有、不同线程交替持有锁、多线程竞争锁三种情况。
重量级锁:底层使用的Monitor实现,里面涉及到了用户态和内核态的切换、进程的上下文切换,成本较高,性能比较低。
轻量级锁:线程加锁的时间是错开的(也就是没有竞争),可以使用轻量级锁来优化。轻量级修改了对象头的锁标志,相对重量级锁性能提升很多。每次修改都是CAS操作,保证原子性
偏向锁:一段很长的时间内都只被一个线程使用锁,可以使用了偏向锁,在第一次获得锁时,会有一个CAS操作,之后该线程再获取锁,只需要判断mark word中是否是自己的线程id即可,而不是开销相对较大的CAS命令,W一旦锁发生了竞争,都会升级为重量级锁
ReentrantLock的使用方式和底层原理?
ReentrantLock是一个可重入锁:,调用 lock 方 法获取了锁之后,再次调用lock,是不会再阻塞,内部直接增加重入次数 就行了,标识这个线程已经重复获取一把锁而不需要等待锁的释放。ReentrantLock是属于juc报下的类,属于api层面的锁,跟synchronized一样,都是悲观锁。通过lock()用来获取锁,unlock()释放锁。它的底层实现原理主要利用CAS+AQS队列来实现。它支持公平锁和非公平锁,两者的实现类似构造方法接受一个可选的公平参数(默认非公平锁),当设置为true时,表示公平锁,否则为非公平锁。公平锁的效率往往没有非公平锁的效率高。
ReentrantLock 底层使用了CAS,为什么它不是乐观锁而是悲观锁?
synchronized和Lock有什么区别 ?
JMM 和 volatile
你知道什么是Java内存模型JMM吗?
JMM和volatile他们两个之间的关系?
JMM没有那些特征或者它的三大特征是什么?
为什么要有JMM,它为什么出现?作用和功能是什么?
happens-before先行并发原则你有了解过吗?
volatile凭什么可以保证可见性和有序性?
内存屏障Memory Barrier
内存屏障(重点)
2.3 AQS 和 CAS
AQS: 全名 AbstractQueuedSynchronizer,意为抽象队列同步器,JUC(java.util.concurrent 包)下面的 Lock 和其他一些并发工具类都是基于它来实现的。AQS 维护了一个 volatile 的 state 和一个 CLH(FIFO)双向队列 ReenrantLock就是基于-它实现的
https://blog.itpub.net/70024924/viewspace-3008324/
CAS的全称是: Compare And Swap(比较再交换);它体现的一种乐观锁的思想,在无锁状态下保证线程操作数据的原子性。
线程使用场景问题
如果控制某一个方法允许并发访问线程的数量?
如何保证Java程序在多线程的情况下执行安全呢?
你在项目中哪里用了多线程?
3.JVM
了解 JVM,包括内存布局、类加载机制、垃圾回收算法、垃圾回收器的工作原理。
1.1 JVM内存模型
1.7
方法区:存放类对象(加载好的类),所有线程共享方法区
堆区:存放new出来的对象,所有线程共享堆区
栈区:存放方法之间的调用关系,每个线程对应有一个栈区
程序计数器:存放下一个要执行的指令的地址,每个线程对应有一个程序计数器
其中:
线程私有的:程序计数器、栈区
线程是共享的:堆、方法区
1.8
1.2 类加载机制
https://blog.csdn.net/m0_67683346/article/details/128163144
类加载:.java编译为.class后,类加载器会将.class文件加载到jvm中进行解析,然后在内存中构造一个类Class对象并初始化(反射)。
类加载过程:类从加载到虚拟机中开始,直到卸载为止,它的整个生命周期包括了:1.加载 2.连接(验证、准备(初始化static)、解析(初始化字符串常量)) 3.初始化、4.使用和卸载。
1.3 类加载器分类和双亲委派机制
分类(4种):启动 扩展 应用 自定义类加载器。
类加载器的体系并不是“继承”体系,而是委派体系,类加载器首先会到自己的parent
中查找类或者资源,如果找不到才会到自己本地查找。类加载器的委托行为动机是为了避免相同的类被加载多次。
双亲委派模型?
类加载器首先会到自己的parent
中查找类或者资源,如果找不到才会到自己本地查找。
好处:1.避免相同的类被加载多次。2.为了安全,保证类库API
不会被修改。
如何打破双亲委派?
两个典型的方法:
- 自定义类加载器,重写loadClass方法
- 使用线程上下文类加载器
1.4 垃圾回收机制
GC:程序员只申请内存资源后,不需要手动释放资源,释放内存的工作JVM的GC完成。
GC主要针对堆区来进行内存回收(比如没有引用指向该对象的情况,如何判断是否是垃圾:1.引用计数器 2.可达性分析)。
引用计数器:可能产生循环引用。
可达性分析算法:
Java使用的是可达性分析算法来判断对象是否可以被回收。可达性分析将对象分为两类:垃圾回收的根对象(GC Root)和普通对象,对象与对象之间存在引用关系。可达性分析算法指的是如果从某个到GC Root对象是可达的,对象就不可被回收。
哪些对象被称之为GC Root对象呢?
虚拟机栈中的本地变量引用、方法区中的类静态属性引用、方法区中的常量引用、本地方法栈中 JNI 引用的对象。
栈区的变量,释放时机确定(出了作用域生命周期就结束),不必回收;程序计数器是固定内存空间,不必回收;方法区中的静态属性,在类加载之后一般是不会卸载的,所以也不需要进行处理。
1.5 垃圾回收算法
1.标记清除 2.复制算法 3.标记整理 4.分代回收
1.6 垃圾回收器
1 你知道哪几种垃圾收集器,各自的优缺点是啥,重点讲下cms和G1,包括原理,流程,优缺点?
1)首先简单介绍下 有以下这些垃圾回收器
Serial收集器: 单线程的收集器,收集垃圾时,必须stop the world,使用复制算法。
ParNew收集器: Serial收集器的多线程版本,也需要stop the world,复制算法。
Parallel Scavenge收集器: 新生代收集器,复制算法的收集器,并发的多线程收集器,目标是达到一个可控的吞吐量。如果虚拟机总共运行100分钟,其中垃圾花掉1分钟,吞吐量就是99%。
Serial Old收集器: 是Serial收集器的老年代版本,单线程收集器,使用标记整理算法。
Parallel Old收集器: 是Parallel Scavenge收集器的老年代版本,使用多线程,标记-整理算法。
CMS(Concurrent Mark Sweep) 收集器: 是一种以获得最短回收停顿时间为目标的收集器,标记清除算法,运作过程:初始标记,并发标记,重新标记,并发清除,收集结束会产生大量空间碎片。
G1收集器: 标记整理算法实现,运作流程主要包括以下:初始标记,并发标记,最终标记,筛选标记。不会产生空间碎片,可以精确地控制停顿。(重点)
2)CMS收集器和G1收集器的区别:
CMS收集器是老年代的收集器,可以配合新生代的Serial和ParNew收集器一起使用;
G1收集器收集范围是老年代和新生代,不需要结合其他收集器使用;
CMS收集器以最小的停顿时间为目标的收集器;
G1收集器可预测垃圾回收的停顿时间
CMS收集器是使用“标记-清除”算法进行的垃圾回收,容易产生内存碎片
G1收集器使用的是“标记-整理”算法,进行了空间整合,降低了内存空间碎片。
2详细介绍一下 CMS 垃圾回收器?
首先CMS垃圾收集器是一种
1 老年代的垃圾收集器,
2 是以牺牲吞吐量为代价来获取最短回收停顿时间的垃圾回收器,
3 多线程并发的标记-清除算法,所以在gc的时候会产生大量的内存碎片,当剩余内存不能满足程序运行要求时,系统将会出现 Concurrent Mode Failure,临时 CMS 会采用 Serial Old 回收器进行
垃圾清除,此时的性能将会被降低。
CMS 工作机制相比其他的垃圾收集器来说更复杂。
整个过程分为以下 4 个阶段:
初始标记
只是标记一下 GC Roots 能直接关联的对象,速度很快,仍然需要暂停所有的工作线程。
并发标记
进行 GC Roots 跟踪的过程,和用户线程一起工作,不需要暂停工作线程。
重新标记
为了修正在并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,仍然需要暂停所有的工作线程。
并发清理
清除 GC Roots 不可达对象,和用户线程一起工作,不需要暂停工作线程。由于耗时最长的并发标记和并发清除过程中,垃圾收集线程可以和用户现在一起并发工作, 所以总体上来看CMS 收集器的内存回收和用户线程是一起并发地执行。
3 详细介绍一下 G1 垃圾回收器?
首先G1垃圾收集器相比于CMS垃圾收集器,有两个突出的优点:
1 基于标记整理算法,不产生内存碎片
2 可以按照按照用户预期的停顿时间进行执行,也就是说在不牺牲吞吐量的情况下,实现低停顿垃圾回收
G1垃圾回收器的实现原理:G1将Java堆划分为多个大小相等的独立区域(Region),一般Region大小等于堆大小除以2048,比如堆大小为4096M,则Region大小为2M,当然也可以用参数"-XX:G1HeapRegionSize"手动指定Region大小
G1垃圾收集器的特点:
1 使用了标记-整理算法,无内存碎片产生
2 无分代概念但是有内存分区策略,即G1它将新生代和老年代的这种物理空间划分取消了,但是G1 采取了内存分区策略,将堆内存划分为大小固定的几个独立区域。
而具体的分区 可以分为 新生代区域,老年代区域,大对象区域
G1垃圾收集器对于对象什么时候会转移到老年代跟之前讲过的原则一样,唯一不同的是对大对象的处理,G1有专门分配大对象的Region叫Humongous区,而不是让大对象直接进入老年代的Region中。在G1中,大对象的判定规则就是一个大对象超过了一个Region大小的50%,比如按照上面算的,每个Region是2M,只要一个大对象超过了1M,就会被放入Humongous中,
而且一个大对象如果太大,可能会横跨多个Region来存放。
Full GC的时候除了收集年轻代和老年代之外,也会将Humongous区一并回收
G1收集器一次GC的过程:(主要指Mixed GC)
初始标记:暂停所有的其他线程,并记录下gc roots直接能引用的对象,速度很快 ;
并发标记:同CMS的并发标记
最终标记:同CMS的重新标记
筛选回收(Cleanup,STW):筛选回收阶段 1 首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿STW时间(可以用JVM参数 -XX:MaxGCPauseMillis指定)来制定回收计划
比如说老年代此时有1000个Region都满了,但是因为根据预期停顿时间,本次垃圾回收可能只能停顿200毫秒,那么通过之前回收成本计算得知,可能回收其中800个Region刚好需要200ms,那么就只会回收800个Region(Collection Set,要回收的集合),说白了就是尽量把GC导致的停顿时间控制在我们指定的范围内。
这个阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分Region,时间是用户可控制的,而且停顿用户线程将大幅提高收集效率。
4.MySQL
掌握 MySQL,包括索引、事务、日志和锁,熟悉 MVCC 的实现原理。
1.两种引擎的对比
1、由于锁粒度的不同,InnoDB比MyISAM支持更高的并发;
2、InnoDB为行级锁,MyISAM为表级锁,所以InnoDB相对于MyISAM来说,更容易发生死锁,锁冲突的概率更大,而且上锁的开销也更大,因为需要为每一行加锁;
3、在备份容灾上,InnoDB支持在线热备,有很成熟的在线热备解决方案;
4、查询性能上,MyISAM的查询效率高于InnoDB,因为InnoDB在
查询过程中,是需要维护数据缓存,而且查询过程是先定位到行所在的数据块,然后在从数据块中定位到要查找的行;而MyISAM可以直接定位到数据所在的内存地址,可以直接找到数据;
5、SELECT COUNT(*)语句,如果行数在千万级别以上,MyISAM可以快速查出,而InnoDB查询的特别慢,因为MyISAM将行数单独存储了,而InnoDB需要逐行去统计行数;所以如果使用InnoDB,而且需要查询行数,则需要对行数进行特殊处理,如:离线查询并缓存;
6、MyISAM的表结构文件包括:.frm(表结构定义),.MYI(索引),.MYD(数据);而InnoDB的表数据文件为:.ibd和.frm(表结构定义);
2.MySQL的索引是如何实现的?
在MySQL中,索引底层数据结构主要使用B+Tree和Hash。InnDB引擎的索引底层是B+树,B+Tree是一种多叉平衡搜索树,其特点是高度较低,可大幅降低磁盘I/O次数,提高搜索效率。B+Tree的非叶子节点均为目录项记录页,用于存储下属主键及对应数据页的页码,叶子节点之间使用双向链表连接,数据页内记录之间使用单向链表连接。叶子结点为第0层,从下往上层数变大。每个数据页有对应的页目录,由此可使用二分查找快速定位。实际工程中,B+树高度一般不会超过4层,因为高度为4层时可存储的数据量已经非常巨大。
3.为什么选择B+树不选这Hash和B树?
Hash:
1.不适合范围查找
2.没有顺序,不支持order by 排序
3.联合索引,hash结构是无法通过单独的某一个索引来进行查询的。
4.索引重复值多时,如性别,年龄 ,效率就会很低。
注:对于等值查询hash结构是一个不错的选择。redis存储的核心就是hash表。
B+树相比于B树:
io磁盘代价小。
查询效率更稳定。
4.索引的分类
- 从
功能逻辑
上说,索引主要有 4 种,分别是普通索引、唯一索引、主键索引、全文索引。 - 按
照物理实现方式
,索引可以分为 2 种:聚簇索引和非聚簇索引。 - 按照
作用字段个数
进行划分,分成单列索引和联合索引。 - 空间索引
锁分类
5.Redis
熟悉 Redis 的数据类型,能解决缓存穿透、缓存雪崩和缓存击穿等高并发场景下的常见问题,了解分布式锁和缓
存一致性策略。
Redis 五种基本数据结构及使用场景
字符串(String):
缓存数据:将经常访问的数据缓存在 Redis 中,以加快访问速度。
计数器:记录某个事件发生的次数,例如网站的访问次数、文章的点赞次数等。
分布式锁:使用字符串的 SETNX 命令来实现分布式锁。
哈希(Hash):
存储对象属性:将对象的属性存储在哈希中,方便对属性进行读写操作。
缓存对象:将对象序列化后存储在哈希中,以便快速获取和更新对象。
记录用户信息:存储用户的详细信息,如用户名、年龄、性别等。
列表(List):
消息队列:用于实现消息队列,将生产者产生的消息存储在列表中,消费者从列表中获取消息进行处理。
最新动态:存储用户的最新动态或消息,如微博的用户动态、新闻网站的最新消息等。
实时排行榜:用于存储用户的分数或权重,并根据分数进行排序,实现实时排行榜功能。
集合(Set):
好友关系:存储用户的好友关系,利用集合的交集、并集、差集等操作来实现好友关系的管理。
标签管理:将对象关联的标签存储在集合中,方便进行标签的添加、删除和检索。
唯一值集合:用于存储唯一值,如去重、统计等场景。
有序集合(Sorted Set):
排行榜:存储用户的分数,并根据分数进行排序,实现排行榜功能。
实时热门数据:存储数据的热度值,并根据热度值进行排序,用于实时热门数据的展示。
计划任务:存储定时任务的执行时间,并根据时间戳进行排序,用于实现计划任务的调度。
1.Redis为什么快?
1.基于内存实现。
2.单线程模型避免上下文切换,I/O多路复用。
Redis的网络IO和键值对读写是由一个线程来完成的,因此避免了CPU不必要的上下文切换和竞争锁的消耗。Redis单线程同时监控多个客户端连接的 I/O 状态,不需要为每个连接新建一个线程(复用),这大大减少了资源的开销。
3.高效的数据结构:SDS 哈希 跳跃表 压缩列表ziplist
如压缩表,对短数据进行压缩存储,再如,跳表,使用有序的数据结构加快读取的速度。
2.缓存穿透、缓存雪崩和缓存击穿
缓存穿透产生的原因是什么?
- 用户请求的数据在缓存中和数据库中都不存在,不断发起这样的请求,给数据库带来巨大压力
缓存穿透的解决方案有哪些?
- 缓存null值 优点:实现简单,维护方便。 缺点:额外的内存消耗,可能造成短期的不一致(id=5查询不存在,将(5,null)更新到缓存,这个时候我们刚好添加了一个id=5的数据到数据库。这个时候去查询缓存是null,但是数据库已经存在者新增的数据了。第一次穿透的key设null值存到redis缓存,再次访问该值的redis缓存为null则直接返回前端该数据不存在。
- 布隆过滤 优点:内存占用较少,没有多余key 缺点:实现复杂,存在误判可能(布隆过滤器走的是哈希思想,可能存在哈希冲突)
- 增强id的复杂度,避免被猜测id规律
- 做好数据的基础格式校验
- 加强用户权限校验
- 做好热点参数的限流
缓存雪崩
是什么?(重要!!)
缓存雪崩是指在同一时段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力。
解决方案?(重要!!)
- 给不同的Key的TTL添加随机值
- 利用Redis集群提高服务的可用性
- 给缓存业务添加降级限流策略???
- 给业务添加多级缓存
缓存击穿
缓存击穿问题也叫热点Key问题,就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击。
常见的解决方案有两种:(重要!!!)
- 互斥锁
- 逻辑过期
两者的区别是什么?(重要!!)
按图分别介绍,然后解释各自优缺点。
解决方案一、使用锁来解决:
因为锁能实现互斥性。假设线程过来,只能一个人一个人的来访问数据库,从而避免对于数据库访问压力过大,但这也会影响查询的性能,因为此时会让查询的性能从并行变成了串行,我们可以采用tryLock方法 + double check来解决这样的问题。
假设现在线程1过来访问,他查询缓存没有命中,但是此时他获得到了锁的资源,那么线程1就会一个人去执行逻辑,假设现在线程2过来,线程2在执行过程中,并没有获得到锁,那么线程2就可以进行到休眠,直到线程1把锁释放后,线程2获得到锁,然后再来执行逻辑,此时就能够从缓存中拿到数据了。
解决方案二、逻辑过期方案
方案分析:我们之所以会出现这个缓存击穿问题,主要原因是在于我们对key设置了过期时间,假设我们不设置过期时间,其实就不会有缓存击穿的问题,但是不设置过期时间,这样数据不就一直占用我们内存了吗,我们可以采用逻辑过期方案。
我们把过期时间设置在 redis的value中,注意:这个过期时间并不会直接作用于redis,而是我们后续通过逻辑去处理。假设线程1去查询缓存,然后从value中判断出来当前的数据已经过期了,此时线程1去获得互斥锁,那么其他线程会进行阻塞,获得了锁的线程他会开启一个线程去进行以前的重构数据的逻辑,直到新开的线程完成这个逻辑后,才释放锁, 而线程1直接进行返回,假设现在线程3过来访问,由于线程线程2持有着锁,所以线程3无法获得锁,线程3也直接返回数据,只有等到新开的线程2把重建数据构建完后,其他线程才能走返回正确的数据。
这种方案巧妙在于,异步的构建缓存,缺点在于在构建完缓存之前,返回的都是脏数据。
3.分布式锁
秒杀模块·
秒杀优化-异步秒杀思路
我们来回顾一下下单流程
当用户发起请求,此时会请求nginx,nginx会访问到tomcat,而tomcat中的程序,会进行串行操作,分成如下几个步骤
1、查询优惠卷
2、判断秒杀库存是否足够
3、查询订单
4、校验是否是一人一单(悲观锁解决)
5、扣减库存(乐观锁解决)
6、创建订单
在这六步操作中,又有很多操作是要去操作数据库的,而且还是一个线程串行执行, 这样就会导致我们的程序执行的很慢,所以我们需要异步程序执行,那么如何加速呢?
优化方案:我们将耗时比较短的逻辑判断放入到redis中,比如是否库存足够,比如是否一人一单,这样的操作,只要这种逻辑可以完成,就意味着我们是一定可以下单完成的,我们只需要进行快速的逻辑判断,根本就不用等下单逻辑走完,我们直接给用户返回成功, 再在后台开一个线程,后台线程慢慢的去执行queue里边的消息,这样程序不就超级快了吗?而且也不用担心线程池消耗殆尽的问题,因为这里我们的程序中并没有手动使用任何线程池,当然这里边有两个难点
第一个难点是我们怎么在redis中去快速校验一人一单,还有库存判断
第二个难点是由于我们校验和tomct下单是两个线程,那么我们如何知道到底哪个单他最后是否成功,或者是下单完成,为了完成这件事我们在redis操作完之后,我们会将一些信息返回给前端,同时也会把这些信息丢到异步queue中去,后续操作中,可以通过这个id来查询我们tomcat中的下单逻辑是否完成了。
思路:!!!
新增秒杀优惠券的同时,将优惠券信息保存到Redis中
基于Lua脚本,判断秒杀库存、一人一单,决定用户是否抢购成功
如果抢购成功,将优惠券id和用户id封装后存入阻塞队列
开启线程任务,不断从阻塞队列中获取信息,实现异步下单功能
4.缓存与数据库的一致性
6.框架
熟悉 Spring、SpringBoot、SpringSecurity 等开发框架,深入理解 IOC 和 AOP、了解 Bean 的生命周期和循环
依赖处理,熟悉 SpringBoot 启动流程和自动装配原理。
3.1 Spring的IOC和AOP
IOC
将创建管理对象的工作交给容器来做。在容器初始化(或在某个时间节点)通过反射机制创建好对象,在使用时直接从容器中获取。
1.Tomcat启动Spring容器;
2.初始化Spring容器,加载application.xml配置文件;
3.获取扫描包下所有class文件;
4.解析class中的注解信息;
5.通过反射实例化相应bean对象,以<beanId,bean>的形式保存集合,存储在IOC容器中。
6.通过ApplicationContext的getBean方法获取Bean。
DI
常见的依赖注入:@Autowired @Resource @Value。
@Autowired 和 @Resource区别?
第一,Autowired默认是根据type类型进行依赖注入的,而Resource既支持type和支持name,默认是name进行匹配。其次,Autowired是spring自带的依赖注入方式,而Resource是JSR 250规范中的依赖注入方式,只是Spring对其提供了支持。最后,使用Autowired注入依赖,当Spring Ioc容器中有多个类型相同的bean时,这个时候启动就会报错,解决方法是结合@Primary注解或者@Qualifier注解实现name匹配注入。
AOP
AOP:主要用于非业务代码,将功能相同的部分抽离出一个公共的模块(Aspect切面),减少重复代码的使用,提高代码复用性,降低了和业务代码的一个耦合度。
用于:记录日志 缓存 Spring内置事务
记录日志:
1.@Aspect @Component 2.@Around joinPoint 3.例如记录每个请求接口的相关消息… @PointCut(自定义注解路径)切点表达式 @Log
3.Spring事务的实现:编程式事务 声明式事务
编程式事务:对业务代码具有侵入性,需要手动在业务代码中去开启 提交 回滚事务,一般不使用。
声明式事务:使用@Transaction’注解给目标方法添加事务,原理和记录日志一样的也是使用AOP,去切面类里面写一个@Round环绕通知的方法,在目标方法执行前开启事务,执行结束提交事务,出现异常回滚事务。
补充:
3.1spring事务失效的场景有哪些?
1.异常捕获处理:转钱例子!!!A -100 (中间异常被捕获catch) B +100,导致A减了100 B没有加100,事务事务失效,无法保证事务原子性。事务是通过只有捕获到目标抛出的异常才进行回滚的,如果使用catch将异常捕获了,就无法回滚了,解决方法就是如果要使用catch捕获异常,可以在catch里面将对应的异常throw new RuntimeException抛出去,让事务捕获到,然后回滚。
2.抛出检查异常:如在中间读取文件 抛出FileNotFoundException异常会事务失效,原因是Spring事务默认只会滚非检查时/运行时异常。解决方法:给@Transaction注解配置rollbackFor属性,@Transaction(rollbackFor = Exception.class) 表示Spring事务只要检测出异常就进行回滚。
3.非public方法:
3.2 Bean的生命周期和循环依赖
Spring的Bean的生命周期
Spring循环依赖
循环依赖:Bean A中注入B,B中注入了A。可能导致以下情况:
0.creatingSet[AService]
1.实例化:—>AService的普通对象---->WangLeiMap<beanName,AService的普通对象>(提前将ABean实例创建好放到一个Map当中,这样在创建BService的时候就可以将A的Bean实例注入到BService当中,解决了循环依赖)。
2.填充BService—>单例池—>创建BService
BService的bean生命周期
2.1 实例化—》BService的普通对象
2.2 填充AService—>单例池—>创建AService(产生循环依赖)
2.3 填充其他属性
2.4 其他步骤(包括AOP)
2.5 加入到线程池
3.填充其他属性
4.其他步骤(包括AOP)
5.加入到线程池
以上方式存在问题:使用AOP时应该将A的代理对象赋值给BService,但实例化时存的是A的普通对象。
解决方法:一开始实例化就将代理对象存到Map中。
1.实例化:—>AService的普通对象—>循环依赖—>AOP—>AService的代理对象WangLeiMap<beanName,AService的普通对象的代理对象>。
以上方式存在问题:2.2注入到BService的A的实例已经提前实现了AOP,4就不需要给A进行AOP了,需要检测之前是否已经AOP了,也就是判断是否存在循环依赖在2.2阶段,防止重复给A进行AOP。
修改j2.2 :填充AService—>单例池—>creatingSet—>判断循环依赖—>earlySingtonObjects—>AOP—>创建AService代理对象—>earlySingtonObjects
最终流程:
Spring三级缓存(重要!!!)
第一级缓存 单例池 singletonObjects:用来保存完整的经过bean的生命周期之后bean的实例。
第二级缓存 earlySingtonObjects:用来保存那些出现了循环依赖的bean后,需要提前给其他bean用的那些单例bean。
第三级缓存 singletonFactories:用来打破循环
3.3 Spring中的设计模式
总结:4Bean 2AOP MVC Spring监听器 模板 bean->BeanDefination的策略模式
3.4 SpringBoot启动流程和自动装配原理
启动流程
1.首先从main找到run()方法,在执行run()方法之前new一个SpringApplication对象
2.进入run()方法,创建应用监听器SpringApplicationRunListeners开始监听
3.然后加载SpringBoot配置环境(ConfigurableEnvironment),然后把配置环境(Environment)加入监听对象中
4.然后加载应用上下文(ConfigurableApplicationContext),当做run方法的返回对象
5.最后创建Spring容器,refreshContext(context),实现starter自动化配置和bean的实例化等工作。
自动装配原理
3.5 SpringBoot-Spring-SpringMVC常用注解
7.消息队列
熟悉 RabbitMQ 的使用,有处理消息丢失、重复消费和消息顺序性问题,以及延迟队列的实现的实战经验。
1.RabbitMQ-如何保证消息不丢失?(重要)
主要从三个层面考虑:
第一个是开启生产者确认机制,确保生产者的消息能到达队列,如果报错可以先记录到日志中,再去修复数据。
第二个是开启持久化功能,确保消息未消费前在队列中不会丢失,其中的交换机、队列、和消息都要做持久化。
第三个是开启消费者确认机制为auto,由spring确认消息处理成功后完成ack,当然也需要设置一定的重试次数,我们当时设置了3次,如果重试3次还没有收到消息,就将失败后的消息投递到异常交换机,交由人工处理。
2.RabbitMQ消息的重复消费问题如何解决的?(重要)
消费者是设置了自动确认机制,当服务还没来得及给MQ确认的时候,服务宕机了,导致服务重启之后,又消费了一次消息。这样就重复消费了
因为我们当时处理的支付(订单|业务唯一标识),它有一个业务的唯一标识,我们再处理消息时,先到数据库查询一下,这个数据是否存在,如果不存在,说明没有处理过,这个时候就可以正常处理这个消息了。如果已经存在这个数据了,就说明消息重复消费了,我们就不需要再消费了。
其实这个就是典型的幂等的问题,比如,redis分布式锁、数据库的锁都是可以的解决这个问题的。
3.其他的解决方案吗?
4.RabbitMQ中死信交换机 ? (RabbitMQ延迟队列有了解过嘛)(重要)
延迟队列就是用到了死信交换机和TTL(消息存活时间)实现的。
如果消息超时未消费就会变成死信,在RabbitMQ中如果消息成为死信,队列可以绑定一个死信交换机,在死信交换机上可以绑定其他队列,在我们发消息的时候可以按照需求指定TTL的时间,这样就实现了延迟队列的功能了。
我记得RabbitMQ还有一种方式可以实现延迟队列,在RabbitMQ中安装一个死信插件,这样更方便一些,我们只需要在声明交互机的时候,指定这个就是死信交换机,然后在发送消息的时候直接指定超时时间就行了,相对于死信交换机+TTL要省略了一些步骤。
5.如果有100万消息堆积在MQ , 如何解决 ?
我在实际的开发中,没遇到过这种情况,不过,如果发生了堆积的问题,解决方案也所有很多的
第一:提高消费者的消费能力 ,可以使用多线程消费任务
第二:增加更多消费者,提高消费速度
使用工作队列模式, 设置多个消费者消费消费同一个队列中的消息
第三:扩大队列容积,提高堆积上限
可以使用RabbitMQ惰性队列,惰性队列的好处主要是
①接收到消息后直接存入磁盘而非内存
②消费者要消费消息时才会从磁盘中读取并加载到内存③支持数百万条的消息存储
6.RabbitMQ的高可用机制有了解过嘛?
我们当时项目在生产环境下,使用的集群,当时搭建是镜像模式集群,使用了3台机器。镜像队列结构是一主多从,所有操作都是主节点完成,然后同步给镜像节点,如果主节点宕机后,镜像节点会替代成新的主节点,不过在主从同步完成前,主节点就已经宕机,可能出现数据丢失。
7.那出现丢数据怎么解决呢?
我们可以采用仲裁队列,与镜像队列一样,都是主从模式,支持主从数据同步,主从同步基于Raft协议,强一致。并且使用起来也非常简单,不需要额外的配置,在声明队列的时候只要指定
这个是仲裁队列即可。
8.RabbitMQ,实现消息的异步解耦?
8.微服务
了解 SpringCloud 微服务技术和常用组件,如 Nacos、Seata、GateWay、OpenFeign、Sentinel。
9.计网和操作系统
熟悉计算机网络和操作系统知识,包括 TCP/IP、UDP、HTTP、HTTPS、DNS 协议、页面置换、进程间通信等。
常见的页面置换算法:
- 先进先出(FIFO)算法:将最早进入内存的页面替换出去。
- 最近最少使用(LRU)算法:将最近最少被访问的页面替换出去。
- 最不常用(LFU)算法:将最不经常被访问的页面替换出去。
- 时钟置换算法:通过一个指针按照顺时针方向遍历页面,当需要替换页面时,替换指针指向的页面,并将该页面的访问位清零。
原文链接:https://blog.csdn.net/weixin_72256328/article/details/138327484
进程间通信方式
0.计算机网络
1.键入网址
2.TCP/IP OSI模型 关系 各个层常见的协议
3.DNS解析流程(重点!!!)
4.TCP 三次握手 四次挥手(重点!!!)
5.TCP如何保证可靠性?(重点!!!)
5.TCP和UDP的区别?(重点!!!)
6.HTTP状态码? Get Post方法区别?
7.HTTP和HTTPS区别?(重点!!!)
8.HTTPS加密过程?(重点!!!)
1.键入网址
2.各层常见协议
应用层:HTTP DNS WebSocket SSH FTP Telnet SMTP POP3/IMAP RTP
传输层:TCP UDP
网络层:IP ARP ICMP NAT OSPF RIP BGP
3.DNS解析流程(重点!!!)
www.server.com.
浏览器缓存 操作系统缓存 hosts文件 本地dns服务器 根域dns服务器 顶级dns服务器 权威dns服务器
4.TCP 三次握手 四次挥手(重点!!!)
一:TCP三次握手 (Three-Way Handshake)过程
SYN:客户端发送一个SYN(同步序列编号)报文到服务器,并进入SYN_SENT状态,等待服务器确认。
SYN-ACK:服务器收到SYN报文后,发送一个SYN+ACK(确认)报文给客户端,并进入SYN_RCVD状态。
ACK:客户端收到服务器的SYN+ACK报文后,会发送一个ACK报文给服务器,然后双方进入ESTABLISHED(已建立连接)状态,完成三次握手,开始数据传输。
目的
TCP三次握手的主要目的是建立一个可靠的会话连接,确保双方通信端口的正确性,以及同步双方的序列号和确认号,为数据传输做好准备。
二:TCP四次挥手 (Four-Way Handshake)过程
FIN:当通信的一方完成所有数据传输后,会发送一个FIN(结束)报文来关闭连接。
ACK:另一方收到FIN报文后,会发送一个ACK报文作为应答,并将接收到的序列号加1。
FIN:在发送了ACK报文后,该方也可以发送一个FIN报文请求关闭连接。
ACK:最初发送FIN报文的一方收到对方的FIN报文后,发送一个ACK报文作为回应,然后等待足够时间以确保对方收到这个ACK报文。
目的
四次挥手的目的是允许双方均能清楚地关闭已建立的TCP连接。由于TCP是全双工的,因此每个方向必须单独进行关闭。
5.TCP如何保证可靠性?(重点!!!)
首先是一个简略版的回答:
1.建立连接
2.序号机制
3.数据校验
4.超时重传
5.流量控制
6.拥塞控制
下面是详细版回答:
建立连接:通过三次握手建立连接,保证连接实体真实存在
序号机制:保证数据是按序、完整到达
合理分片:tcp会按最大传输单元(MTU)合理分片,接收方会缓存未按序到达的数据,重新
排序后交给应用层。
数据校验:TCP报文头有校验和,用于校验报文是否损坏
超时重传:如果发送一直收不到应答,可能是发送数据丢失,也可能是应答丢失,发送方再等待一段时间之后都会进行重传。
流量控制:当接收方来不及处理发送方的数据,能通过滑动窗口,提示发送方降低发送的速率,防止包丢失。
拥塞控制:网络层拥堵造成的拥塞,包括慢启动,拥塞避免,快速重传三种机制
6.TCP和UDP的区别?(重点!!!)
**区别:**连接 服务对象 可靠性 传输方式 分片不同 首部开销
1. 连接
- TCP 是面向连接的传输层协议,传输数据前先要建立连接。
- UDP 是不需要连接,即刻传输数据。
2. 服务对象
- TCP 是一对一的两点服务,即一条连接只有两个端点。
- UDP 支持一对一、一对多、多对多的交互通信
3. 可靠性
- TCP 是可靠交付数据的,数据可以无差错、不丢失、不重复、按序到达。
- UDP 是尽最大努力交付,不保证可靠交付数据。但是我们可以基于 UDP 传输协议实现一个可靠的传输协议,比如 QUIC 协议,具体可以参见这篇文章:如何基于 UDP 协议实现可靠传输?(opens new window)
4. 拥塞控制、流量控制
- TCP 有拥塞控制和流量控制机制,保证数据传输的安全性。
- UDP 则没有,即使网络非常拥堵了,也不会影响 UDP 的发送速率。
5. 首部开销
- TCP 首部长度较长,会有一定的开销,首部在没有使用「选项」字段时是
20
个字节,如果使用了「选项」字段则会变长的。 - UDP 首部只有 8 个字节,并且是固定不变的,开销较小。
6. 传输方式
- TCP 是流式传输,没有边界,但保证顺序和可靠。
- UDP 是一个包一个包的发送,是有边界的,但可能会丢包和乱序。
7. 分片不同
- TCP 的数据大小如果大于 MSS 大小,则会在传输层进行分片,目标主机收到后,也同样在传输层组装 TCP 数据包,如果中途丢失了一个分片,只需要传输丢失的这个分片。
- UDP 的数据大小如果大于 MTU 大小,则会在 IP 层进行分片,目标主机收到后,在 IP 层组装完数据,接着再传给传输层。
TCP 和 UDP 应用场景:
由于 TCP 是面向连接,能保证数据的可靠性交付,因此经常用于:
FTP
文件传输;- HTTP / HTTPS;
由于 UDP 面向无连接,它可以随时发送数据,再加上 UDP 本身的处理既简单又高效,因此经常用于:
- 包总量较少的通信,如
DNS
、SNMP
等; - 视频、音频等多媒体通信;
- 广播通信;
7.HTTP状态码? Get Post方法区别?
GET和POST的区别? 参数位置 数据长度限制 安全性 幂等性和缓存
前提是GET和POST都要满足RFC规范。
虽然GET和POST都是使用HTTP协议传输数据,但它们在一些方面有明显的区别:
- 参数位置
GET请求的参数附加在URL的末尾,形式为:http://example.com/path?param1=value1¶m2=value2
POST请求的参数包含在请求的正文中,不会显示在URL中。
- 数据长度限制
GET请求的参数附加在URL后面,受限于URL的长度限制,一般由浏览器限制,不同浏览器可能有不同的限制,例如IE限制为2083字节。
POST请求的参数在请求的正文中,没有明确的长度限制,一般受到服务器端的配置限制。
- 安全性
GET请求的参数会显示在URL中,因此对于敏感信息不宜使用GET请求,因为敏感信息可能会被浏览器保存、历史记录或被其他人看到。
POST请求的参数在请求的正文中,相对来说更加安全,但仍然不是绝对安全,因为HTTP是明文传输的,只有使用HTTPS才能加密传 输数据。
缓存与历史记录
GET请求会被浏览器主动缓存,而POST请求不会被缓存,除非手动设置。因为GET请求的参数附加在URL后面,浏览器会将URL作为缓存的标识。幂等性
GET请求是幂等的,多次执行相同的GET请求,服务器的状态不会发生变化。
POST请求不是幂等的,多次执行相同的POST请求可能会产生不同的结果,因为可能会导致服务器状态的变化。
GET和POST的使用场景?
GET请求主要用于获取数据,因为它是幂等的、可缓存的。常见的使用场景包括:
搜索引擎的搜索请求
获取网页内容
获取资源文件等
POST请求主要用于向服务器提交数据,因为它可能会导致状态变化或产生副作用。常见的使用场景包括:
提交表单数据
上传文件
发表评论等
在实际应用中,要根据具体的需求选择GET或POST方法,合理地使用它们可以提升应用的性能和安全性。
8.HTTP和HTTPS区别?(重点!!!)
- HTTP 是超文本传输协议,信息是明文传输,存在安全风险的问题。HTTPS 则解决 HTTP 不安全的缺陷,在 TCP 和 HTTP 网络层之间加入了 SSL/TLS 安全协议,使得报文能够加密传输。
- HTTP 连接建立相对简单, TCP 三次握手之后便可进行 HTTP 的报文传输。而 HTTPS 在 TCP 三次握手之后,还需进行 SSL/TLS 的握手过程,才可进入加密报文传输。
- 两者的默认端口不一样,HTTP 默认端口号是 80,HTTPS 默认端口号是 443。
- HTTPS 协议需要向 CA(证书权威机构)申请数字证书,来保证服务器的身份是可信的。
9.HTTPS加密过程?(重点!!!)
HTTP 由于是明文传输,所以安全上存在以下三个风险:
窃听风险,比如通信链路上可以获取通信内容,用户号容易没。
解决方法:混合加密。非对称加密(建立连接前使用1次) + 对称加密(建立连接后一直用)
篡改风险,比如强制植入垃圾广告,视觉污染,用户眼容易瞎。
解决方法:摘要算法(哈希函数) + 数字签名(给hash值加密,防止被替换。私钥签名,公钥验证)
冒充风险,比如冒充淘宝网站,用户钱容易没。
解决方法:服务器注册公钥到CA;CA用私钥对服务器公钥进行签名,并颁发数字证书;服务器将证书和公钥传给客户端;客户端用CA的公钥验证服务器的证书;合法,就用服务器公钥加密数据然后发送给服务器;服务器私钥解密,接收客户端发送的信息;
10.工具使用
熟悉 Linux 常用命令,如 chmod、nohup 等,了解 Docker 的常用操作,有部署项目的经验。
Linux
查看进程命令有哪些?(重要)
ps top htop pstree
ps aux # 显示所有进程的详细信息
ps -ef # 以标准格式显示所有进程
top # 实时的系统资源使用情况,包括 CPU、内存使用率和进程信息
htop # 图形化的进程监控工具
pstree # 以树状结构显示进程,清晰展示进程的父子关系。
文件和目录操作
ls
:列出目录内容。cd
:更改当前目录。pwd
:显示当前工作目录。cp
:复制文件或目录。mv
:移动或重命名文件或目录。rm
:删除文件或目录。mkdir
:创建新目录。rmdir
:删除空目录。touch
:创建空文件或更新文件的时间戳。find
:查找文件和目录。ln
:创建硬链接或软链接。
文件查看和编辑
cat
:连接并显示文件内容。more
:逐页查看文件内容。less
:逐页查看文件内容,功能比more
强大。head
:查看文件开头部分。tail
:查看文件结尾部分。vim
:强大的文本编辑器。
用户和组
useradd
:添加新用户。usermod
:修改用户信息。userdel
:删除用户。groupadd
:添加新组。groupmod
:修改组信息。groupdel
:删除组。passwd
:修改用户密码。
文件权限和所有权(重要)
chmod
:改变文件权限。chown
:改变文件所有者。chgrp
:改变文件所属组。
压缩和解压
tar
:压缩和解压.tar
文件。 tar -zcvf 压缩 tar -zxvf 解压zip
:压缩文件。unzip
:解压.zip
文件。gzip
:压缩文件。gunzip
:解压.gz
文件。
Docker
Docker作用
Docker可以帮助我们下载应用镜像,创建并运行镜像的容器,从而快速部署应用。
优点:
1.不需要运行额外的OS(省内存)
2.部署方便快速。
3.资源相互隔离。
docker常用命令
查看容器详细信息:
docker inspect [容器名称]
查看日志:
docker logs[容器名称]
实时动态的监控容器的CPU 内存资源:(类似top)
docker stats[容器名称]
Docker底层原理
NameSpace
Cgroups