- Sql的查询过程你有了解吗?给我说一下update的具体经历了什么过程?
- 查询缓存,如果这是条查询语句的话,这里是update不会走这步
- 语法分析,看这条update语句是否有错误
- 语义分析,这条update语句是做什么的
- 优化,因为update不仅是修改数据,还需要通过查询来确定修改的哪一行数据,因此这个检索的过程还有优化的点,比如确定走的哪个索引啊这样的优化
- 生成执行计划,确定这条语句具体怎么执行
- 锁定数据
- 执行修改
- 提交事务或者回滚
- 项目怎么做限流?
- 两种方式:
- 漏桶算法
- 通过Nginx作为代理服务器来实现,在Nginx配置文件中配置每秒放行的请求数即可
- 令牌桶算法
- 通过SpringCloud中在网关中通过的bucket4j包来实现,具体地呢,通过配置类设置令牌桶的容量,也就是桶中令牌的数量,以及令牌的生成速率
- 再定义一个网关的过滤器,也就是一个自定义的GatewayFilter,在这个过滤器里每次请求来需要去消费令牌桶中的令牌,成功则放行,失败则返回一个429状态码
- 讲这个过滤器放到对应的路由的filters中去
- 令牌桶的好处:因为令牌桶可以存储令牌,令牌桶可以在一段时间无请求后桶中堆积令牌,在之后可应对突发大流量
- 漏桶算法
- 两种方式:
- 网络层的协议了解吗?
- IP协议:用于在网络中唯一标识设备
- ARP协议:地址解析协议,用于将IP地址转换为对应的物理地址
- OSPF协议:最短寻道优先协议,保证网络传输总是能以最短的路径传输,保证效率
- NAT协议:网络地址转换协议,确保能够从一个子网转换到另外一个子网。
- TCP协议了解吗?
- TCP协议是传输层的协议,其最大的特点就是建立连接要经过三次握手,断开连接要经过4次挥手,还有一系列的机制来保证传输的可靠性
- 3次握手:
- 第一次握手:客户端向服务端发送一个syn包,并进入SYN_SEND状态
- 第二次握手:服务端接收到syn包,向客户端发送一个ack包,同时发送一个syn包,服务端进入SYN_RECV状态
- 第三次握手:客户端接收到ack和syn包,并发送一个ack包给服务器端,客户端与服务端都进入ESTABLISHED状态
- 4次挥手:
- 第一次挥手:客户端发送一个FIN包给服务端,客户端进入FIN_WAIT1状态
- 第二次挥手:服务端接收到FIN包,发送一个ACK包给客户端,但是此时服务端可能还有数据发送任务,不立即发送FIN包,此时客户端进入FIN_WAIT2状态,服务端进入CLOSE_WAIT状态
- 第三次挥手:服务端完成数据发送任务,发送FIN包给客户端,服务端进入LAST_ACK状态
- 第四次挥手:客户端接收到FIN包,发送一个ACK包给服务端,客户端进入TIME_WAIT状态,在经过两个msl后,双端进入CLOSED状态
- TCP传输的可靠性保证:
- 通过数据块进行传输,被称为报文段
- TCP确保数据和首部的校验和,校验和不通过则数据包被丢弃
- 对失序的包进行重排序和去重
- 重传机制:TCP每次发送一个数据,都需要对端发送一个ACK来进行确认,在一段时间后没有收到ack,会进行重传
- 流量控制:通过流量窗口,也就是一个滑动窗口来控制接收的流量数量
- 拥塞控制:通过一个拥塞窗口,也就是一个滑动窗口来控制在网络拥塞时的发送包数量,减少丢包率
- 慢开始
- 拥塞避免
- 快恢复和快重传
- DNS是基于什么协议的?
- DNS是基于UDP协议的
- 了解多线程吗?多线程怎么创建的?
- 多线程主要是为了提高程序执行效率,让多个线程执行多个任务,提高系统可用性。
- 线程创建:
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
- 利用线程池
- 利用CompletableFuture.runAscn()或者CompletableFuture.supplyAscn(),前者接收一个Runnable接口,后者接收一个Supplier接口
- 线程池了解吗?
- 线程池是为了在多线程环境下避免频繁线程创建和销毁带来的性能损耗而产生的
- 最佳实践是自己创建ThreadPoolExecutor对象来创建线程池,而避免使用jdk内置的一些线程池
- 当要执行一项任务,可以利用ThreadPoolExecutor.execute()方法,传入一个Runnable接口,可以传入一个实现Runnable的类对象,也可以利用lamda表达式
- 当要关闭线程池时,可以利用shutdown()方法,会等当前线程执行完任务,且在这期间不接收新任务,执行完毕则关闭线程池,也可以利用shutdownNow()方法,会立即关闭线程池
- 核心参数:
- 核心线程数
- 最大线程数
- 临时线程存活时间
- 临时线程存活时间单位
- 线程工厂
- 阻塞队列
- 拒绝策略:
- AbortPolicy:抛弃新任务,并抛出异常
- DiscardPolicy:抛弃任务,不做操作
- DiscardOldestPolicy:抛弃阻塞队列里最老的任务
- CallerRunPolicy:调用主线程来执行这个任务
- 什么情况下会产生线程安全问题?
- 当多个线程对同一共享资源产生争抢时会产生线程安全问题
- 线程安全是怎么解决的?
- 线程安全主要要保证3个方面:
- 共享资源的可见性
- 可见性通过对共享资源用volatile关键字修饰来保证
- 有序性
- 有序性通过对共享资源用volatile关键字修饰,底层采用内存屏障,禁用指令重排序,确保有序性
- 原子性
- 通过互斥锁来实现,包括乐观锁和悲观锁
- 乐观锁如CAS,版本号
- 悲观锁如synchronized和reentrantlock
- 共享资源的可见性
- 线程安全主要要保证3个方面:
- ReentrantLock是什么锁?是重入锁吗?
- ReentrantLock底层是基于AQS实现的,AQS底层依赖一个CLH队列和一个状态变量state,CLH队列是一个虚拟队列,无实体,这个队列用于存放那些未能获取锁并被阻塞的线程,有几个字段,包括线程id,waitStatus是否是处于waiting状态,前驱结点指针和后继节点指针
- ReentrantLock是可重入的,初始未上锁时,state为0,其有一个内部类Sync(),里面记录了获得锁的线程,每次线程尝试获取锁时会判断要获取锁的线程是否是记录的线程,如果是,则让state+1,释放锁时则让state-1,直到state重新为0锁才真正被释放
- Sync()有两个子类,分别是FairSync()和UnFairSync(),分别对应公平锁和非公平锁,可以在创建ReentrantLock时根据参数设置为true或者false来设置公平锁或者非公平锁
- 了解CAS吗?
- CAS依赖操作系统底层来确保一系列操作为原子操作
- 一系列操作主要包括:
- 从共享内存读取数据到工作内存
- 对比预期值与共享变量是否相等
- 相等则将修改值写入到共享变量,同步到共享内存
- 不相等则返回失败
- java中依赖CAS的一系列类,包括AtomicInteger,AtomicLong,AtomicReference等
- CocurrentHashMap是怎么使用CAS的?
- ConcurrentHashMap在将元素插入到数组对应位置时,如果数组对应位置为空,则CAS写入该位置
- 线程之间怎么通信的?
- 对共享资源的通信可以通过互斥锁来通信
- 还可以通过共享内存来通信
- 通过磁盘中的文件来通信
- CountDownLatch了解吗?
- CountDownLatch底层基于AQS,确保要执行的多个任务执行完毕后,主线程再继续执行
- 内部依赖AQS的state状态变量,在添加任务之后,state设置为任务数量,每当一个任务完成后,state-1,当state=0时去唤醒主线程
- 使用的话,当利用线程池执行任务时,每当一个任务执行完成,则调用CountDownLatch.countDown()方法来使state-1,而主线程在这些任务执行前调用CountDownLatch.await()进入等待,等state=0时,主线程被唤醒继续执行
- 数据结构了解吗?队列和栈?
- 队列是一种先入先出的数据结构
- 栈是一种后入先出的数据结构
- 内存补齐有听说过吗?
- 因为现代计算机往往是通过一定的单位来存放数据的,例如32位计算机和64位计算机中,字节往往是基本单位,因此数据存储往往要以这个单位来存储,例如java中对象的存储,就包括了两个部分,一部分是对象头,包含了对象的信息,而另一部分则是用于补齐内存,使其位数是8的倍数
- Linux了解吗?
- 了解一些指令:
- 基本的文件操作:
- cd
- pwd
- ls
- ll:包括文件权限的文件信息
- mkdir
- mv:用于重命名和剪切
- tar -zcvf:打包
- tar -zxvf:解压
- 系统操作:
- htop:查询cpu使用情况
- ps -ef:查询进程使用信息
- grep:搜索对应信息
- free:查看内存使用情况
- uptime:查看系统运行了多久
- vmstat:查看虚拟内存状态
- df:查看对应文件系统磁盘使用状况
- du:查看对应文件磁盘使用状况
- 基本的文件操作:
- 了解一些指令:
- 使用过哪些消息队列?
- rabbitmq:
- 组件:
- exchange:交换机用于将消息通过一定规则(topic)路由到对应绑定在这个交换机的消息队列中去
- queue:消息队列实体,客户端可以监听消息队列从队列中取数据
- 防止消息丢失的方式:
- 客户端到交换机:confirm机制,也就是如果客户端到交换机消息发生了丢失,那么客户端会触发一个回调函数,来重发消息
- 队列本身:持久化队列,通过集群来保证高可用
- 普通集群,从节点的队列中保存着主节点的元数据,从节点可以根据元数据去找到主节点,并获得对应的消息
- 镜像集群:从节点中会有着主节点中的全量数据,每次主节点的消息变更都会同步到从节点。
- 队列到消费者端:
- basicAck机制,当消息发送到消费者,消费者成功消费消息后会发送一个ack来告知队列消息已经成功消费
- 死信队列,消费失败的消息会放入到死信队列中,可以从死信队列中取出对应的消息
- 消息补偿机制:消息重新发送
- 组件:
- kafka:由于采用了异步的思想,kafka能够做多做到处理千万级别的数据
- 组件:
- topic:消费者通过绑定topic来消费消息
- partition:一个topic可以有多个partition,每次消费者从一个partition上去取走消息
- Broker:可以看成是一个kafka实例,同一个topic的不同partition可以分布在多个Broker上,提高并发性能
- 防止消息丢失的机制:
- 客户端到kafka:通过一个回调函数来确认是否发送到了kafka,如果没发送到则触发,可以在回调函数里触发重发消息或者记录日志
- kafka本身:kafka所有消息都是持久化到磁盘的,在集群环境下,我们可以设置ack=?来设置消息被同步到几个节点才算是客户端消息发送成功
- 消费者弄丢数据:可以手动提交消费完成,防止消费丢失,如果消费者弄丢,那么将触发kafka重发
- 防止消息重复消费的机制:
- 这种事情往往很难权衡,重复消费和消费丢失其实是互斥的问题,解决了消息丢失就可能带来重复消费的问题,因此我们可以保证手动提交,因为重复消费一般是消费的超时时间到达,导致消息漂移rebalance,让另外一个消费者重复消费,因此可以延长消费的超时时间,但是不能盲目增加,如果太长可能会导致消息堆积,降低kafka的性能。
- 怎么保证kafka消息的顺序性?
- 可以在发送消息的时候指定key/partition,内核都是将消息发送到同一partition上,保证消费的顺序性。
- 组件:
- rabbitmq:
20240731面经背诵
最新推荐文章于 2024-09-16 17:24:25 发布