JavaWeb方向-面试汇总

本文深入探讨了JVM垃圾回收算法及其应用场景,介绍了常见的垃圾回收器,并解析了判断对象回收的方法。此外,还讲解了数据库优化技巧,包括SQL调优、索引管理和事务隔离级别的选择。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

JVM

1. 垃圾回收算法
  • 标记-清除:标记已死对象,GC时直接清除。CMS回收器使用。
      特点: 简单、速度快,但会留有内存空间碎片,空间碎片会导致后面的GC频率增加。
      适合场景:只有小部分对象需要进行回收的,因为回收对象太多,其清除的时间就会越长。关注引用停顿时间,因为收集速度快,用户线程停顿的时间较短。

  • 标记-复制:将内存分为两块区域,标记已死对象,GC时将存活对象复制到另一块区域,清除已死对象。
      特点:收集速度快,可以避免空间碎片,但是有空间浪费,存活对象较多的情况下会非常耗时,而且需要担保机制。 适合场景: 只有少量对象存活的,因为需要把存活对象复制到新的内存区域,所需要复制 的对象越多那么其复制的时间就会越长,比较适合新生代的垃圾回收。

  • 标记-整理:标记已死对象,GC时将存活对象复制到内存的另一端,将这一端的已死对象清除。
      特点:相对于标记复制法不会浪费内存空间,相对标记清除法则可以避免空间碎片, 但是速度比其他两个算法慢。
      适合场景: 内存吃紧,又要避免空间碎片的场景。

2. 常见的垃圾回收器
  • G1(使用标记-复制算法)
  • CMS(使用标记-清除算法)
3. 判断哪些对象需要被回收
  • 引用计数法
      给对象添加一个引用记数器,被引用一次就+1,引用失效就-1.无法解决循环引用的问题
  • 可达性分析算法
      通过GC root为起点,从起点开始搜索,搜索走过的路叫引用链。当一个对象到GC root没有任何引用链时,则证明对象不可用。

可作为GC root的对象:
  虚拟机栈中引用的对象
  方法区中类静态属性引用的对象
  方法区中常量引用的对象
  本地方法栈中引用的对象

4. JVM调优命令
  • jps主要用来输出JVM中运行的进程状态信息。
  • jstat命令可以用于持续观察虚拟机内存中各个分区的使用率以及GC的统计数据
  • jmap可以用来查看堆内存的使用详情。
  • jstack可以用来查看Java进程内的线程堆栈信息。 jstack是个非常好用的工具,结合应用日志可以迅速定位到问题线程。
  • 设置JVM内存大小

mysql

事务隔离机制
隔离级别可造成简介
读未提交(RU)脏读、不可重复读、幻读事务A可以读取到事务B未commit前对记录做的修改
读提交(RC)不可重复读、幻读事务A只可以读取到其它事务commit后的记录修改
可重复读(RR)幻读(可通过加next-key lock锁解决)事务A第二次读某条记录与第一次返回同样的值,即使这条记录被其它事务commit修改过
序列化(SERIALIZABLE)最高隔离级别,所有事务串行
几个log
  • redo log
    特点:宕机恢复,提高写入效率,顺序IO写入速度快
    redo保证事务持久性。mysql在对数据库数据做更改(insert/update/delete)时,为了避免频繁写入磁盘,使用的机制是先将更新写入内存,再写入redo log,在空闲时间或者redo log满时再刷盘持久化。如果数据在未刷盘期间mysql挂掉,重启后也可以通过redo log恢复,做到crash-safe。
  • undo log
    特点:事务回滚,MVCC
    undo log保证事务的原子性。其原理为记录一条可恢复的sql。比如一条sql为insert,与其对应的undo log的sql为delete。事务在回滚时需要使用到undo log。
  • binlog
    特点:恢复、同步
    归档日志。binlog为事务成功提交后写的日志。主要用于mysql主从同步,主库发生了更新,写入binlog,从节点过来获取binlog来执行,从而达到同步效果。如果想要恢复某时间段的数据,只需要采用全量备份+重放binlog来完成。

redo log 和binlog的两阶段提交
事务执行中,mysql会写undo log 和redo log,同时事务此时是prepare状态
事务语句全部执行完,mysql写binlog日志,写完后事务可commit

redo log和change buffer的区别
redo log缓解了mysql随机写磁盘的压力
change buffer缓解了mysql随机读磁盘的压力

高可用集群方案
  1. MySQL Replication(主从复制)
    一主多从,主库提供写服务,从库提供读服务。
    master通过binlog同步复制给从库实现数据一致性
    无故障转移和负载均衡
    主从同步流程

  2. MySQL Fabirc
    在MySQL Replication基础上加入了故障检测、故障转移、数据分片功能,依旧是一主多从。
    主节点挂掉后,会从从节点选一个提供写服务。

  3. MySQL Cluster
    只适用于NBD引擎。多主多从架构,可用性可达五个九,其分片数据也在不同的Data Nodes中备份。

以上三个并称为官方三兄弟


第三方插件提供的集群方案

  1. MMM(Master Replication Manager for MySQL)
    双主多从,一个主节点提供写服务,另一个提供部分读服务。在故障发生时,切换主节点。有一个monitor提供检测服务
  2. MHA(Master High Availability)
    多主多从
    直接上线
  • 全局锁
  • 表锁
  • 行锁
  • 间隙锁
  • next-key lock
1. sql在什么情况下不走索引
  • like %xxx
  • 查询条件使用函数
  • 查询条件使用表达式
  • 隐式类型转换(如字段类型为int 查询时使用 = ‘1’)
2. explain中展示的字段
  • select_type:本次查询的每个select子句的类型(SIMPLE,PRIMARY,UNION)
  • possible_keys:可能使用的索引
  • Key:实际用到的索引
  • rows:估算出结果集行数
  • extra:表示查询的详细信息(using_index,using filesort无法利用索引进行order by)
3. 表中以a,b,c建立索引 什么情况下不会走索引?

    以b开头以及以c开头的查询条件不走索引

4. 关于索引的名词
  • 覆盖索引
    如果查询列在索引文件中可以找到,那么称此次查询覆盖索引
  • 索引下推
    筛选条件可以在索引文件里直接判断,不需要回表判断,称为索引下推。是mysql5.6之后官方做的优化。
5. sql调优问题
  1. 偶尔慢
    mysql在刷新"脏页"。脏页为记录在redo log中还没有来的及写磁盘的数据。本来mysql会在空闲时间去刷脏页,但是可能一直没有空闲时间,直到把redo log写满。mysql此时不得不暂停一些手里的事去刷新脏页,这时处理请求就会变慢

    被某些事务锁住。可以使用show processlist查看所有连接的状态

  2. 经常慢
    查看表上是否有索引
    查看explain执行计划是否走了索引(mysql可能会选错索引)
    查看是否有回表情况 是否可做优化


dubbo

1. 为什么用dubbo
  • 服务越来越多时,管理配置服务url变的困难。F5的硬件负载单点压力很大
  • 服务之间的依赖关系复杂,甚至分不清哪个应用应在哪个应用前启动
  • 服务调用量上来后,什么时候该加机器?服务的管理方面
2. dubbo负载均衡策略
  • 随机,按权重设置随机概率(默认)
  • 轮询,按权重设置轮询比率
  • 最少活跃调用
  • hash调用
3. 集群容错
  • failover 失败自动切换,重度其它服务器(默认)
  • Failfast 快速失败,只发起一次,失败立即报错
    - Failsafe 失败安全,出现异常时,直接忽略
    - Failback 失败自动恢复,后台记录失败请求,定时重发
    - Forking 并行调用多个服务器,只要一个成功即返回
    - Broadcast 广播调用所有提供者
4. dubbo支持的协议
  • dubbo(默认) 采用单一长连接和NIO异步通讯,适用于小数据量大并发的服务调用。采用hession序列化
    - rmi 采用阻塞式短连接和JDK标准序列化方式
    - hessian 用于集成Hessian服务,他的底层采用http通讯
    - http 短连接。采用json序列化
    - webservice 基于webService的远程调用协议
    - thrift
    - memcached 基于 memcached实现的 RPC 协议
    - redis
    - rest( 就是 RestFull)

MQ

1. 顺序消息

业务需要保证顺序消息时:通过自定义函数实现消息选择分区策略。保证顺序的消息写入到同一分区里,分里自动保证消息的顺序。

2. 重复消息

生产者:自身代码保证消息不发送重复,如果消息发送失败,则重试,直到成功为止。
消费者:处理完后提交offset,有可能消费完未提交offset时宕机,这时需要保证接口幂等性。

3. 消息丢失

生产者:消息发送类型分为三种:

  1. 消息写入到网络里就不管了
  2. 同步发送,等消息发送完回来结果继续执行
  3. 异步发送,发送完可通过消息回调函数做操作
    这里我们一般使用异步发送,并且在回调函数里加入重试机制

acks=0/1/all
0 代表生产者写入消息就不管了
1 代表至少有一个broker接受到消息就算发送成功
all 代表所有broker都同步完消息才返回成功

broker:需要根据实际情况通过参数控制几个副本写入成功才算成功
消费者:在业务处理结束后再提交offset

Kafka
Kafka为什么那么快?
  1. 顺序写:顺序写比随机写快就快在省去了磁盘寻道和旋转的时间。因Kafka的分区保证有序,所以很很合适顺序写。
  2. 零拷贝:在linux系统中,为了安全考虑,应用程序要操作操作系统底层都要经过内核态。一次正常的读写请求需要经过四次copy,才可完成。而零拷贝则是直接在内核态操作读写并写入到socket中,提高读写效率。
  3. page cache:Kafka接受生产者的消息时,消息先写入page cache。在消费者来取消息时,如果page cache里有,则直接与page cache取即可。这样加快了消息传播效率。并且Kafka会在适当时机把page cache里的消息一并写入到磁盘中,类似于mysql的redo log,缓解了频繁写磁盘的压力。
  4. 批量与压缩:kafka支持多种消息压缩算法。生产者发送压缩的消息,消息者取走消息并解压缩,这样的好处是节省了网络带宽和磁盘空间。同时生产者发送与消费者消费都是支持批量发送/读取的。
Kafka启动-通信流程

流程

Kafka发送消息选择分区策略
  1. 随机
  2. 轮询
  3. 自定义
Kafka的重平衡

kafka最好是消费者与partition是一一对应的关系。如果消费者多,则多余的消费者空闲;如果partition多,则会有一个消费者消费多个partition的情况。
在消费者、partition有变动的情况,都会触发kafka的rebanlance。

Kafka分区分配策略

前提:partition比消费者多的情况

  1. 最新加入的最先分配(Range):如果消费者A最先加入,则消费者A多分配partition
  2. 轮询分配(RoundRobin)
  3. Sticky粘性分配:即在最小变动下完成rebanlance。如果partitionA上次与消费者A连接,则这次尽量保证不变动。
Kafka副本同步原理

AR:kafka集群中所有副本的统称
ISR:kafka集群中所有flowse的统称,是一个动态的集合

kafka通过两个变量来维持副本间的同步
HW:高水位,代表已经同步完的offset
LEO:下一条待写入的消息offset

Kafka消息是推是拉?


消费者与Broker采用长轮询方式拉取数据。
长轮询指消费者去broker拉消息,此时broker没有未消费消息或不够本次拉取数量,则消费者阻塞等待;等到broker有了消息之后唤醒消费者线程再拉取返回。避免没有消息时候多次频繁请求拉取。

Kafka为什么后来抛弃了zk
  1. zk集群会增加kafka集群的复杂度
  2. 因为之前offset等信息都保存在zk中,zk并不适合这各频繁增改的操作。

基础

1. HashMap

    1.8之后底层采用数组+链表/红黑树。链表转红黑树的机时是链表长度>8,红黑树转回链表的时机是红黑树的节点数量<6。这么做的原因是避免来回转换。
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);这是HashMap的hash算法


算法

  • 二叉树的前中后遍历
    假设List是现有的排序好的二叉树。

前序:

public static void qianxu(TreeNode treeNode){
   	reuslt.add(treeNode);
   	if (treeNode.left != null) {
   		qianxu(treeNode.left);
   	}
   	if (treeNode.right != null) {
   		qianxu(treeNode.right);
   	}
   }

中序:


   public static void zhongxu(TreeNode treeNode) {
   	if (treeNode.left != null) {
   		zhongxu(treeNode.left);
   	}
   	reuslt.add(treeNode);
   	if (treeNode.right != null) {
   		zhongxu(treeNode.right);
   	}
   }

后序:

public static void houxu(TreeNode treeNode) {
	if (treeNode.left != null) {
		zhongxu(treeNode.left);
	}
	if (treeNode.right != null) {
		zhongxu(treeNode.right);
	}
	reuslt.add(treeNode);
}   

明白区别了么?不明白就背。耗子尾汁


多线程

1.线程同步方法
  • Synchronized同步方法
        通过Java的对象头来做线程同步。对象头包含mark work(标记字段) 和klass(类型指针)
        标记字段存储对象的hashcode,锁标志位和分代年龄。
        klass存储对象指向的类对象
  • Synchronized同步代码块
         Monitor可以理解为一个同步工具或一种同步机制,通常被描述为一个对象。每一个Java对象就有一把看不见的锁,称为内部锁或者Monitor锁。
        Monitor是线程私有的数据结构,每一个线程都有一个可用monitor record列表,同时还有一个全局的可用列表。每一个被锁住的对象都会和一个monitor关联,同时monitor中有一个Owner字段存放拥有该锁的线程的唯一标识,表示该锁被这个线程占用。
  • ThreadLocal
        为每个线程的成员变量创建一个副本,多线程环境下各个线程的变量值不会共享。
  • 可重入锁ReenreantLock
  • volidate
        保证变量的可见性,即某一线程在修改其值时其它线程是可以正常读取的,但是不保证变量的原子性。一般用于一写多读的变量这种情况
  • 原子类AtomicInteger
2. 锁机制

在这里插入图片描述

  • 悲观锁&乐观锁
        悲观锁:认为每次线程使用数据时都会被其它线程修改,它使用JDK提供的锁机制加锁(synclized和lock)。
        适用场景:写多读少
        乐观锁:认为每次线程使用数据都没有其它线程修改,使用CAS(Compare And Swap)实现(在更新数据时先判断是否有其它线程已修改数据,如果没有则直接修改。如果有则拿到新数据再对数据修改,接着循环判断其是否被修改)。乐观锁性能较悲观锁要好一些。JAVA的原子类就是通过CAS实现。
        适用场景:写少读多
  • 自旋锁&适应性自旋锁
        自旋锁是在某线程没有抢到锁时自行执行一段循环等锁的现象。如果循环结束锁已被其它线程释放,则该线程尝试抢锁,抢不到继续自旋。这么做的好处是减少CPU调度阻塞线程的开销,提升性能。
        适应性自旋锁则是更优化了自旋锁。适应性的锁会根据该同步方法上一次相同锁对象的自旋情况来判断是否进行自旋。如果上个锁对象很快获得了锁且现在还在同步方法中执行,则会进行自旋。如果该同步方法的自旋很少成功,则会直接阻塞等待CPU调度。

举个例子加深理解:某办事大厅,老百姓在排队办事,排了很多人。
    非自旋锁:非自旋锁是个呆头鹅。一看前面排了这么多人啦,我先排队在这睡会,等轮到我了叫我啊。
    自旋锁:自旋锁是个小机灵鬼。他不排队,先在旁边坐下迷糊一会,醒来了看看现在的情况是不是有个人刚办完事而后面的还没来的及补上,如果是则插队办事,不是则继续迷糊,如此循环。
    适应性自旋锁:先问问小机灵鬼哥们他们迷糊了多久?迷糊了几次?根据情况来判断是睡还是迷糊。

  • 无锁&偏向锁&轻量锁&重量锁
    synchronized的四种锁状态
        无锁:没有线程进入同步方法内。
        偏向锁:当只有一个线程对同步方法访问时使用偏向锁。偏向锁的标志位记录了访问的threadID,在该线程获取或释放锁时不需要再通过CAS原子操作来加锁或释放锁,而是直接判断threadID是否相等。减少了CAS的原子操作开销。
        轻量锁:轻量锁是通过自旋来等待持锁线程访问结束释放锁,自旋结束后尝试抢锁。
        重量锁:重量锁是完全互斥的。某个锁正在被一个线程使用,其它线程阻塞等待锁释放后被唤醒。
        锁升级过程: 只能升级,不能降级 无锁 ->偏向锁     当有线程第一次访问同步方法时,锁状态从无锁变成偏向锁,记录锁对象的偏向线程ID。 偏向锁 -> 轻量锁     当有其它线程访问同步方法时,锁升级为轻量锁。轻量锁通过线程自旋等待锁释放。 轻量锁 -> 重量锁     当被锁住的对象自旋时间过长或者等待线程变多,此时会升级为重量锁。等待线程会被阻塞等待唤醒。.

  • 公平锁&非公平锁
    场景:当有一个同步方法锁被占用,而队列中还有其它线程在等待。
    公平锁
        排到队尾,阻塞等待唤醒。 优点:线程有序执行,线程不会被饿死 缺点:整体吞吐量低,唤醒线程需要开销。
    非公平锁
        看看此时是不是持锁线程刚释放锁而队列中第一个线程又没来的及获取锁,如果是这种情况,则直接插队尝试获取锁。否则将排队阻塞。 优点:吞吐量高,减少了一部分唤醒线程的开销 缺点:有可能等待时间长的线程会饿死。

  • 可重入锁&不可重入锁
    可重入锁
        线程在持有锁的时候,访问同对象锁的加锁方法会直接进入方法,不需要先释放锁再获取锁。synchronized和ReetrantLock都是可重入锁。 优点:一定程度避免死锁。
    不可重入锁:
        线程在持有锁时,访问同对象锁的其它加锁方法会先释放锁,再尝试获取锁。NonReentrantLock是不可重入锁的实现。
    处理流程:
        ReettrantLock和NonReentrantLock都继承于父类AQS,AQS中有个状态status来记录锁的重入次数。
        可重入锁获取锁时判断status==0。如果为0则status =1 并进入方法执行。在同步方法中再次进入该锁对象其它同步方法时会判断该锁对象持有的线程是否是当前线程?如果是则status ++并进入方法。
        释放时status-1,当status-1 = 0时,则代表可重入锁的锁已经释放完成。

关于锁,附上一篇特别好的博客,此处贴上链接

3. 线程池
ThreadPoolExecutor的参数
  • corePoolSize:线程池的核心线程数量
  • maximumPoolSize:线程池的最大线程数量
  • keepAliveTime:当空闲线程超过多长时间后销毁
  • TimeUnit :空闲线程超时销毁的时间单位
  • BlockingQueue:存储任务的阻塞队列
  • RejectedExecutionHandler:当线程池数量超过最大线程数量及阻塞队列已满时的拒绝策略
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }
Executors提供的四种常见线程池
  • Executors.newSingleThreadExecutor();
        只有一个线程的线程池。其使用无界的阻塞队列存储待执行任务。可保证任务有序性,FIFO
        适用场景:任务需要有序处理时
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
  • Executors.newCachedThreadPool()
        可缓存的线程池。无核心线程,线程池中线程上限为Integer.MAX。在接到任务时先判断线程池中是否有之前执行过该任务的空闲线程,如果有则复用,没有则创建一个新的线程。当空闲线程空闲时间超过60S时,则销毁该线程。
        适用场景:大量执行时间短的任务。
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>(),
                                      threadFactory);
    }
  • Executors.newFixedThreadPool(n)
        固定数量的线程池,n为核心线程、最大线程的数量。使用无界阻塞队列存储待执行任务。
        适用场景:根据业务需求或系统资源来动态定义线程池的大小。
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
  • Executors.newScheduledThreadPool(3)
        支持定时调度的线程池,n为核心线程数量,可设置该线程池中的任务延迟s秒后每隔m秒执行一次。
        适用场景:有定时任务需要的线程
public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }
线程池的执行流程

    每当有任务进来,按这个顺序塞任务。核心线程 -> 阻塞队列 -> 非核心线程 ->拒绝,盗张图给你看
在这里插入图片描述

线程池为什么使用阻塞队列

    阻塞队列与其它队列的区别是阻塞队列为空时,线程过来取数据会阻塞在那里,当阻塞队列中有数据时,会自动唤醒阻塞的线程。当阻塞队列已满时,添加数据的线程会等待阻塞队列可用。


分布式

1. 分布式锁

概念:分布式环境下某集群服务中某个接口保证线程同步机制,同一时刻只有一个线程执行此方法。

  • Redis实现
        使用Redis的setnx命令,该命令只有在key不存在时才会添加成功,key可取业务名+业务数据id。在setnx返回1的时候代表抢占到该锁,为0时代表未抢到锁,等待锁。在释放锁时使用del方法删除set的key
  • ZooKeeper实现
        使用zk的临时顺序节点的特性。设置一个永久父节点。有其它线程进入加锁方法执行时在该父节点下创建临时顺序节点,后续进来的线程需有序的创建节点。方法执行前需判断该线程是否持有锁,即该线程创建的Znode为顺序节点中最小的节点。如果是则代表其抢到锁,不是该线程则在zk上监听比自己的节点号小1的节点的删除事件,等待锁。在方法执行完删除掉自己创建的节点,以此做到释放锁。下一个线程会通过监听事件来得到消息拿到锁执行方法,以此类推。
  • 数据库实现
        数据库层面创建一张锁表,加锁时往表中插入一条记录,如果有记录则代表有锁。
2. 分布式事务

CAP:C一致性 A可用性 P分区容错性

  • 两阶段提交

      基于数据库层面引用协调者来协调分布式事务。协调询问各参与者事务是否执行成功,如果都成功,则通知所有参与者提交事务。如果有一个失败,则通知所有参与者回滚事务。
      特点:基本保持数据一致性,牺牲了可用性。

    • 参与者在等待状态是阻塞的。
    • 协调者的单点问题。
    • 如果在发送了部分commit情况下协调者宕机,则会影响其它参与者的事务回滚,导致数据不一致。
    • 任意一个节点失败则整个事务失败
  • TTC(补偿事务)
      TTC是为事务的成功以及失败各准备一套方案。TTC为try\confirm\cancel。在某方法调用成功我们会做怎样的逻辑,失败后又会做怎样的逻辑。比较针对业务,开发成本高(需要实现不同的方法)

  • 本地消息表(异步)
      在本地数据库维护一张消息表,在这边方法执行成功后往消息表中插入数据,插入成功后推送到MQ中,MQ确认收到后消息表中删除这条数据。分布式的其方法消费这条消息,来做它对应的操作

  • 事务消息
      kafka和RocketMq实现事务消息。原理为系统A在RocketMQ中开启事务消息机制,发送一个"半消息"(含义为此消息未确认提交前消费者不可见),然后执行本地事务,事务执行成功后给MQ发送确认提交,之后消费者会收到该事务消息,再执行自己本地的事务,成功后提交消费确认。在系统A这边执行完任务因某原因不能确认提交的时候,RocketMq也提供一个事务反查功能,调用系统A实现的事务反查方法来查询事务结果。


spring

1. 事务传播机制

事务传播是指spring在处理这种某事务方法调用其它事务方法时关于事务的选择机制。

  • PROPAGATION_REQUIRED:默认。如果有则使用当前,没有则创建一个新事务执行。
  • PROPAGATION_REQUES_NEW:每次都新建一个事务执行,如果有外层事务,则先挂起。
  • PROPAGATION_SUPPORT:如果外层有事务,则加入事务。没有则以非事务执行。
  • PROPAGATION_NOT_SUPPORT:如果外层有事务,则挂起事务,并以非事务执行此方法内容。
  • PROPAGATION_NEVER:不支持外层事务,如果外层有事务则抛出异常。
  • PROPAGATION_MANDATORY:与never相反,如果外层没事务则抛异常。

Redis

1. 持久化
  • RDB
      默认Redis持久化策略。是Redis里数据的全量备份。Redis可配置每隔多长时间来做一次RDB持久化。
      优点:性能好、文件小
      缺点:通常在恢复时都会丢失一部分数据
      适用场景:可以忍受丢失一段时间的数据
  • AOF
      类似于MySql的binlog。在Redis每有一个改变数据集的命令(set,del)执行后,会在AOF文件中追加此命令。AOF文件会随着时间推移而逐渐变大。
      优点:使用AOF备份可以保证Redis在宕机后数据不丢失(不绝对,可能Redis在刚执行完命令后就宕机了)
      缺点:性能较RDB差,文件越来越大
      适用场景:对Redis的数据恢复要求较高,不想丢失数据
    AOF重写:在AOF文件很大时,Redis会做一个重写。就是删掉而一些重复的set key只保留最后一个set和将set key,del key的命令删掉,以减少AOF文件占用的存储空间
2. 内存淘汰机制

    在添加数据时,Redis如果检测到存储数据很多要超出内存限制,则会根据内存淘汰配置的机制去淘汰掉一些key。

  • 在设置了过期时间的key中随机淘汰
  • 在设置了过期时间的key中淘汰最近最少使用的key(LRU)
  • 在设置了过期时间的key中淘汰即将过期的key
  • set key时报错
  • 全量数据集中随机淘汰key
  • 全量数据集中淘汰最近最少使用的key
  • 全量数据集中淘汰即将过期的key
3. 删除策略
  • 主动删除
        在访问某key时发现该key已经过期,则触发该key主动删除
  • 被动删除
      有些key可能很久都不会访问到,这样导致大量不常访问的已过期key还占用着空间,所以就有了被动删除。
      即Redis会每隔10秒随机抽取一些设置了过期时间的key判断是否已过期。
      如果过期则删除。
      如果本次删除的key数量超过了抽取数据的25%,则重复第一步
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值