JAVA 面试准备

这里写自定义目录标题

一、JAVA基础

1.ArrayList 和LinkedList、CopyOnWriteArrayList

ArrayList:  元素必须连续存储,当需要在ArrayList的中间位置插入或者删除元素时,需要将待插入或者删除的节点后的所有元素进行移动,
其修改代价较高,但是随机查询速度快。


LinkedList:  进行随机访问时,需要从链表的头部一直遍历到该节点为止,因此随机访问速度很慢,但是插入效率快。

2.HashMap 和 Concurrenthashmap

3.Concurrenthashmap

4.Stream

5.synchronized

6.线程池

一、 corePoolSize 线程池核心线程大小

corePoolSize 是线程池中的一个最小的线程数量,即使这些线程处理空闲状态,他们也不会被销毁,除非设置了allowCoreThreadTimeOut。

二、maximumPoolSize 线程池最大线程数量

线程池能够容纳同时执行的最大线程数,此值大于等于1。一个任务被提交到线程池以后,首先会找有没有空闲并且存活线程,如果有则直接将任务交给这个空闲线程来执行,如果没有则会放到工作队列中,直到工作队列满了,才会创建一个新线程,然后从工作队列的头部取出一个任务交由新线程来处理,而将刚提交的任务放入工作队列尾部。线程池不会无限制的去创建新线程,它会有一个最大线程数量的限制,这个数量即由maximunPoolSize指定。工作队列满,且线程数等于最大线程数,此时再提交任务则会调用拒绝策略。

三、keepAliveTime 多余的空闲线程存活时间

当线程空闲时间达到keepAliveTime值时,多余的线程会被销毁直到只剩下corePoolSize个线程为止。默认情况下:只有当线程池中的线程数大于corePoolSize时keepAliveTime才会起作用,直到线程中的线程数不大于corepoolSIze,

四、unit 空闲线程存活时间单位

keepAliveTime的计量单位

五、workQueue 工作队列

任务被提交给线程池时,会先进入工作队列,任务调度时再从工作队列中取出。
常用工作队列有以下几种

1. ArrayBlockingQueue(数组的有界阻塞队列)

ArrayBlockingQueue 在创建时必须设置大小,按FIFO排序(先进先出)。新任务进来后,会放到该队列的队尾,有界的数组可以防止资源耗尽问题。当线程池中线程数量达到corePoolSize后,再有新任务进来,则会将任务放入该队列的队尾,等待被调度。如果队列已经是满的,则创建一个新线程,如果线程数量已经达到maxPoolSize,则会执行拒绝策略。

2. LinkedBlockingQueue(链表的无界阻塞队列)

按 FIFO 排序任务,可以设置容量(有界队列),不设置容量则默认使用 Integer.Max_VALUE 作为容量 (无界队列)。该队列的吞吐量高于 ArrayBlockingQueue。由于该队列的近似无界性,当线程池中线程数量达到corePoolSize后,再有新任务进来,会一直存入该队列,而不会去创建新线程直到maxPoolSize,因此使用该工作队列时,参数maxPoolSize其实是不起作用的。有两个快捷创建线程池的工厂方法 Executors.newSingleThreadExecutor、Executors.newFixedThreadPool,使用了这个队列,并且都没有设置容量(无界队列)。

3.SynchronousQueue(一个不缓存任务的阻塞队列)

生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略。
其 吞 吐 量 通 常 高 于LinkedBlockingQueue。 快捷工厂方法 Executors.newCachedThreadPool 所创建的线程池使用此队列。与前面的队列相比,这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。

4. PriorityBlockingQueue(具有优先级的无界阻塞队列)

优先级通过参数Comparator实现。

5. DelayQueue(这是一个无界阻塞延迟队列)

底层基于 PriorityBlockingQueue 实现的,队列中每个元素都有过期时间,当从队列获取元素(元素出队)时,只有已经过期的元素才会出队,而队列头部的元素是过期最快的元素。快捷工厂方法 Executors.newScheduledThreadPool 所创建的线程池使用此队列。

Java 中的阻塞队列(BlockingQueue)与普通队列相比,有一个重要的特点:在阻塞队列为空时,会阻塞当前线程的元素获取操作。具体来说,在一个线程从一个空的阻塞队列中取元素时,线程会被阻塞,直到阻塞队列中有了元素;当队列中有元素后,被阻塞的线程会自动被唤醒(唤醒过程不需要用户程序干预)。

六、threadFactory 线程工厂

创建一个线程工厂用来创建线程,可以用来设定线程名、是否为daemon线程等等

七、handler 拒绝策略【四大拒绝策略】

AbortPolicy:丢弃任务并抛出 RejectedExecutionException 异常。(默认这种)
DiscardPolicy:丢弃任务,但是不抛出异常
DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程) 。也就是当任务被拒绝添加时,会抛弃任务队列中最旧的任务也就是最先加入队列的,再把这个新任务从队尾添加进去,等待执行。
CallerRunsPolicy:谁调用,谁处理。由调用线程(即提交任务给线程池的线程)处理该任务,如果线程池已经被shutdown则直接丢弃

7.CompletableFuture

8.Fork/join

9.数组与链表的区别

10.单例模式

1.饿汉模式

顾名思义,很饿,没见过吃的,一开始就要吃。其实就在在启动的时候就创建对象,这种方式比较的消耗内存,不太推荐在内存消耗较大的对象上面使用,好处是一开始就创建对象,没有多线程下的安全问题

public class Hungry {
   //私有构造方法,防止自行实例化对象
   private Hungry() {
   }
   
   //静态属性,在一开始就创建是实例对象
   private final static Hungry HUNGRY = new Hungry();

   //通过这个方法拿是实例对象
   public static Hungry getInstance() {
       return HUNGRY;
   }

}
2.懒汉模式

顾名思义,很懒,不火烧眉毛不动手。这种模式只会在你使用对象的时候才创建对象,这种方式避免一开始就使用过多的内存,推荐在内存消耗较大的对象上面使用。

public class LazyMan {
   //volatile->禁止指令重排,避免创建实例对象时重排指令空指针异常
   private volatile static LazyMan lazyMan;

   // 双重检测锁模式的 懒汉式单例  DCL懒汉式(double check lock)
   public static LazyMan getInstance() {
       if (lazyMan == null) {
           synchronized (LazyMan.class) {//对LazyMan上锁,同一时间只能有一个线程能够进入创建对象
               if (lazyMan == null) {
                   lazyMan = new LazyMan(); // 不是一个原子性操作
               }
           }
       }
       return lazyMan;
   }
10.1、 为啥使用synchronized?

答:在多个线程进入getInstance()方法时,如果不加入synchronized锁住LazyMan.class,可能会有对个线程成功实例化对象,这就会违背单例模式的初衷,当加上以后,因为锁住的是class,所以只会有一个线程能够拿到锁来实例化对象,保证单例.

10.2、 又为啥使用volatile?

答:如果看过编译原理,其实里面有一个对编译器的介绍中,编译器会根据需要将代码的执行顺序进行优化,类比这里,我们在实例化一个对象的时候,可能是这样的顺序:为1.lazyMan分配内存空间->2.初始化lazyMan->3.为lazyMan指向第一步中分配的内存空间。但是在编译器的好心下,我们的执行顺序变成了1->3->2,那么这就出现问题了,我们的其他的线程拿对象会到哪里去拿?回到第一步分配的内存空间去拿!假如现在实例对象的线程正好按着1->3->2的顺序刚刚执行完了3,我们另一个线程过来了,因为2还没有执行,内存地址对应的内存空间中还没有东西,那么我们拿的就是个空,但是这个时候这个地址确实是被分配了的。这个时候就会出现空指针异常,也就是因为实例化对象的时候不满足原子性。

10.3、 那又又为啥用两个if (lazyMan == null)

答:好问题!第一个外层的好解释,在我们成功实例化对象之后,我们还需要进入加锁的哪一步吗?其实是不需要的,我们就可以直接返回对象去用就好了。那么第二个内层的又是用来干嘛的?在我们未创建实例对象的时候,我们假设有一千个线程来创建这个实例对象,我们一号线程拿到了锁,二号线程撞到了门上,没拿到锁,于是它在门口等,此时注意二号线程已经经过了第一个if (lazyMan == null),然后线程一执行完了实例,然后返回了这个实例对象LazyMan2@7f1d78ac,这个时候线程二啪的一下拿到了线程一扔下的锁,很快啊!然后又因为没有第二层的if (lazyMan == null),他又实例化了一个对象LazyMan2@688ee48d,然后线程二开心的返回了。此时我们的实例对象已经是线程二的LazyMan2@688ee48d,这个时候第三个线程来了,它在第一层的if (lazyMan == null)被告知对象已经实例化了,拿走去用吧,后来的都是像线程三一样被安排的明明白白。从此以后就只有一个对象LazyMan2@688ee48d,但是这个是正常的吗。我们也违反了单例模式的原则。所以需要加上第二层的if (lazyMan == null)。

11.单例模式中,双重检查锁是干什么用的

二次判空原因
第一次判断是为了验证是否创建对象,判断为了避免不必要的同步
第二次判断是为了避免重复创建单例,因为可能会存在多个线程通过了第一次判断在等待锁,来创建新的实例对象。
判断是为了在null的情况下创建实例代码会检查两次单例类是否有已存在的实例,一次加锁一次不加锁,一次确保不会有多个实例被创建。
单例模式中用volatile和synchronized来满足双重检查锁机制

12.volatile [禁止指令重排]

读volatile:每当子线程某一语句要用到volatile变量时,都会从主线程重新拷贝一份,这样就保证子线程的会跟主线程的一致。
写volatile: 每当子线程某一语句要写volatile变量时,都会在读完后同步到主线程去,这样就保证主线程的变量及时更新。

13.接口和类的区别

14.项目中反射是如何应用的

15.为什么重写hashcode一定要重写equals?


二、Redis

数据类型

数据类型数据结构
String字符串: key,value
List快速列表 quicklist【 双向列表+ziplist(压缩列表)】
Hashziplist(压缩列表)+ hashtable
Setintse【存储纯数字的情况下才会用到】,hashtable
ZSet有序集合:底层也是 Set 集合,只不过对于每个 value添加score,为了便于 Redis 对集合内元素排序, 【ziplist(压缩列表)+ skiplist【跳跃表】】
Geo地理空间
HyperLogLog基数统计: 是去重复统计功能的基数估计算法
bitmap位图:是由 0 和 1 状态表现的二进制位的 bit 数组
bitfield位域
Stream

1、Redis 的回收策略(淘汰策略)?

  1. volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
  2. volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
  3. volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
  4. allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
  5. allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
  6. no-enviction(驱逐):禁止驱逐数据

2、Redis 过期键的删除策略?

  • 定时删除:在设置键的过期时间的同时,创建一个定时器 timer。让定时器在键的过期时间来临时,立即执行对键的删除操作。
  • 惰性删除:放任键过期不管,但是每次从键空间中获取键时,都检查取得的键是否过期,如果过期的话,就删除该键;如果没有过期,就返回该键。
  • 定期删除:每隔一段时间程序就对数据库进行一次检查,删除里面的过期键。至于要删除多少过期键,以及要检查多少个数据库,则由算法决定。

3、Redis 的持久化机制是什么?各自的优缺点?

RDB(Redis DataBase)持久化方式:是指用数据集快照的方式半持久化模式记录 Redis 数据库的所有键值对,在某个时间点将数据写入一个临时文件,持久化结束后,用这个临时文件替换上次持久化的文件,达到数据恢复。

优点

只有一个文件 dump.rdb,方便持久化。
容灾性好,一个文件可以保存到安全的磁盘。
性能最大化,fork 子进程来完成写操作,让主进程继续处理命令,所以是 IO 最大化。
使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 Redis的高性能。相对于数据集大时,比 AOF 的启动效率更高。

缺点

数据安全性低。
RDB 是间隔一段时间进行持久化,如果持久化之间 Redis 发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候

AOF(Append-only file)持久化方式:是指所有的命令行记录以 Redis 命令请求协议的格式完全持久化存储保存为 aof 文件。

优点

数据安全,aof 持久化可以配置 appendfsync 属性,有 always,每进行一次命令操作就记录到 aof 文件中一次。
通过 append 模式写文件,即使中途服务器宕机,可以通过 redis-check-aof 工具解决数据一致性问题。
AOF 机制的 rewrite 模式。AOF 文件没被 rewrite 之前(文件过大时会对命令进行合并重写),可以删除其中的某些命令(比如误操作的 flushall)

缺点

AOF 文件比 RDB 文件大,且恢复速度慢。
数据集大的时候,比 RDB 启动效率低。

redis的持久化机制
        redis是存储在内存中的,当断电时会丢失数据,使用redis持久化机制可以将redis在内存中的数据持久化 到磁盘中,防止丢失

redis持久化机制分为rdb和aof

rdb机制: redis内存中的数据每隔一段时间就会将整个内存中的数据生成一个镜像文件保存到本地磁盘中,如果 redis重启则可以直接把保存的镜像文件加载到内存中去

        例如: save 900 1 :在900秒内有一个key发生了改变,就执行rdb持久化机制 save 300 10 :在300秒内有10个key发生了改变,就执行rdb持久化机制

  特点:rdb持久化文件速度较快,而且存储的是二进制文件,传输方便

        rdb是每隔一段时间持久化一次,如果修改了数据,但是修改的数据没有达到持久化的条件,此时 会导致数据的丢失,无法保证绝对的安全 rdb持久化机制数据恢复的速度快,但是生成镜像文件的速度慢

注意:如果不配置的话,redis磨人的持久化机制是rdb.

aof机制: aof持久化机制是当我们在redis中执行的所有的增删改查命令都会写入到一个aof文件中,redis重启 的时候,就会执行该aof文件中所有的命令,来完成数据的恢复

        特点:aof写入文件的性能相对于rdb较差,恢复数据慢 aof文件较大,不便于传输 aof的数据安全性比rbd高,但是也不能做到100%数据不丢失

        AOF持久化时机 appendfsync always:每执行一个写操作,立即持久化到AOF文件中,性能比较低。每执行一个增删改命令 都立即写入aof文件效率低,但是数据的安全性最高 appendfsync everysec:每秒执行一次持久化数据丢失最多丢失1s的数据 appendfsync no:会根据你的操作系统不同,环境的不同,在一定时间内执行一次持久化。

        rdb默认持久化机制,aof 非默认持久化机制,redis官方推荐同时开启rdb和aof,同时开启时,数据 恢复优先使用aof持久化机制


4、说说 Redis 哈希槽的概念?

Redis 集群没有使用一致性 hash,而是引入了哈希槽的概念,Redis 集群有 16384 个哈希槽,每个 key 通过 CRC16 校验后对 16384 取模来决定放置哪个槽,集群的每个节点负责一部分 hash 槽。

5、Redis 集群的主从复制模型是怎样的?

为了使在部分节点失败或者大部分节点无法通信的情况下集群仍然可用,所以集群使用了主从复制模型,每个节点都会有 N-1 个复制品。

6、Redis 集群会有写操作丢失吗?为什么?

Redis 并不能保证数据的强一致性,这意味着在实际中集群在特定的条件下可能会丢失写操作。

7、Redis 集群之间是如何复制的?

异步复制。

8、Redis 集群最大节点个数是多少?

16384 个。

9、Redis 集群如何选择数据库?

Redis 集群目前无法做数据库选择,默认在 0 数据库。

10、 Redis击穿、穿透、雪崩

  • 缓存穿透

     缓存穿透:指在redis缓存中不存在数据,这个时候只能去访问持久层数据库,当用户很多时,缓存都没有命中就会照成很大压力
     解决方案 :
     (1)布隆过滤器(对可能查询的数据先用hash存储)
     (2)缓存空对象:在没有的数据中存一个空,而这些空的对象会设置一个有效期)
    
  • 缓存击穿

      缓存击穿:指在同一个时间内访问一个请求的请求数过多,而在这个时候缓存某个key失效了,这个时候就会冲向数据库照成缓存击穿
      解决方案:
      (1)设置缓存永远不过期
      (2)加互斥锁,使用分布式锁,保证每个key只有一个线程去查询后端服务,而其他线程为等待状态。这种模式将压力转到了分布式锁上
    
  • 缓存雪崩

    缓存雪崩:在某个时间段,缓存集体过期、redis宕机
    解决方案:给key的失效时间设置为随机时间,避免集体过期;双缓存;加互斥锁

Redis


三,RabbitMQ 【消息队列】

在这里插入图片描述

1. 为什么要使用rabbitmq

  1. 异步处理 - 相比于传统的串行、并行方式,提高了系统吞吐量。

  2. 应用解耦 - 系统间通过消息通信,不用关心其他系统的处理。

  3. 流量削锋 - 可以通过消息队列长度控制请求量;可以缓解短时间内的高并发请求。

  4. 消息通讯 - 消息队列一般都内置了高效的通信机制,因此也可以用在纯的消息通讯。比如实现点对点消息队列,或者聊天室等。

2. 如何确保消息正确地发送至RabbitMQ?如何确保消息接收方消费了消息?

2.1 发送方确认模式

  1. 将信道设置成confirm模式(发送方确认模式),则所有在信道上发布的消息都会被指派一个唯一的ID。
  2. 一旦消息被投递到目的队列后,或者消息被写入磁盘后(可持久化的消息),信道会发送一个确认给生产者(包含消息唯一 ID)。
  3. 如果 RabbitMQ发生内部错误从而导致消息丢失,会发送一条nack(notacknowledged,未确认)消息。发送方确认模式是异步的,生产者应用程序在等待确认的同时,可以继续发送消息。当确认消息到达生产者应用程序,生产者应用程序的回调方法就会被触发来处理确认消息。

2.2,接收方确认机制

  • 2.3,接收方消息确认机制

     消费者接收每一条消息后都必须进行确认(消息接收和消息确认是两个不同操作)。
     只有消费者确认了消息,RabbitMQ才能安全地把消息从队列中删除。
     这里并没有用到超时机制,RabbitMQ仅通过Consumer的连接中断来确认是否需要重新发送消息。
     也就是说,只要连接不中断,RabbitMQ给了Consumer足够长的时间来处理消息。保证数据的最终一致性;
    

下面罗列几种特殊情况

如果消费者接收到消息,在确认之前断开了连接或取消订阅,RabbitMQ会认为消息没有被分发,然后重新分发给下一个订阅的消费者。

(可能存在消息重复消费的隐患,需要去重)如果消费者接收到消息却没有确认消息,连接也未断开,则RabbitMQ认为该消费者繁忙,
将不会给该消费者分发更多的消息。

3、如何避免消息重复投递或重复消费?

在消息生产时,MQ内部针对每条生产者发送的消息生成一个inner-msg-id,作为去重的依据(消息投递失败并重传),避免重复的消息进入队列;

在消息消费时,要求消息体中必须要有一个 bizId(对于同一业务全局唯一,如支付ID、订单ID、帖子ID 等)作为去重的依据,避免同一条消息被重复消费。

4、消息基于什么传输?

由于TCP连接的创建和销毁开销较大,且并发数受系统资源限制,会造成性能瓶颈。RabbitMQ使用信道的方式来传输数据。
信道是建立在真实的TCP连接内的虚拟连接,且每条TCP连接上的信道数量没有限制

5、消息怎么路由?

消息提供方->路由->一至多个队列

消息发布到交换器时,消息将拥有一个路由键(routing key),在消息创建时设定。

通过队列路由键,可以把队列绑定到交换器上。

消息到达交换器后,RabbitMQ 会将消息的路由键与队列的路由键进行匹配(针对不同的交换器有不同的路由规则);

常用的交换器主要分为一下三种

  • fanout:如果交换器收到消息,将会广播到所有绑定的队列上

  • direct:如果路由键完全匹配,消息就被投递到相应的队列

  • topic:可以使来自不同源头的消息能够到达同一个队列。 使用topic交换器时,可以使用通配符

6、如何确保消息不丢失?

消息持久化,当然前提是队列必须持久化

RabbitMQ确保持久性消息能从服务器重启中恢复的方式是,将它们写入磁盘上的一个持久化日志文件,当发布一条持久性消息到持久交换器上时,Rabbit会在消息提交到日志文件后才发送响应。

一旦消费者从持久队列中消费了一条持久化消息,RabbitMQ会在持久化日志中把这条消息标记为等待垃圾收集。如果持久化消息在被消费之前RabbitMQ重启,那么Rabbit会自动重建交换器和队列(以及绑定),并重新发布持久化日志文件中的消息到合适的队列。

7、死信队列

  1. 死信队列的概念
    • 死信队列(Dead - Letter Queue,DLQ)是消息队列(Message Queue,MQ)中的一种特殊队列。当消息在正常的消息队列中无法被正确消费时,这些消息会被转移到死信队列中。例如,在RabbitMQ中,消息可能因为被消费者拒收(basic.reject或basic.nack)、消息过期或者队列达到最大长度等原因而成为死信,然后被发送到死信队列。
  2. 产生死信的原因
    • 消息被拒收
      • 消费者在处理消息时,可能由于业务逻辑判断或者消息格式不符合预期等原因,明确地拒绝接收消息。在RabbitMQ中,消费者可以通过调用basic.rejectbasic.nack方法来拒绝消息。例如,一个消息队列用于传递订单信息,消费者在验证订单格式时发现订单数据不完整,就可以拒绝该消息,使其进入死信队列。
    • 消息过期
      • 消息队列通常允许设置消息的过期时间(TTL - Time To Live)。如果消息在队列中停留的时间超过了设定的过期时间,就会成为死信。比如,在一个缓存更新消息队列中,为了保证数据的及时性,设置消息的TTL为5分钟,超过这个时间的消息就会被判定为过期,进而进入死信队列。
    • 队列达到最大长度
      • 当消息队列的长度达到了预先设定的最大值时,新进入的消息可能会导致旧消息成为死信。例如,一个用于处理日志消息的队列,为了避免占用过多内存,设置队列最大长度为1000条,当新的日志消息进入使得队列长度超过1000时,最早的消息可能会被挤出成为死信。
  3. 死信队列的作用和价值
    • 故障排查和数据恢复
      • 死信队列为开发人员提供了一个集中处理问题消息的地方。通过查看死信队列中的消息,可以快速定位消息无法正常消费的原因。例如,如果大量消息因为格式问题被拒收进入死信队列,开发人员可以分析这些消息的格式,修改消费者的业务逻辑或者消息生产者的消息格式,从而解决问题。而且,在某些情况下,如果消息的内容是重要的数据,还可以对死信队列中的消息进行数据恢复操作,重新将其发送到正常的消息队列中进行消费。
    • 保证系统稳定性
      • 它可以防止问题消息在正常的消息队列中不断堆积,从而影响整个系统的性能和稳定性。例如,在一个高并发的电商系统中,如果订单消息因为某些原因无法被正确处理而在消息队列中不断累积,会导致后续的订单处理流程受阻。通过将这些问题消息转移到死信队列,可以确保正常的订单消息队列能够高效地运行,维持系统的稳定性。
  4. 死信队列的使用方式和配置(以RabbitMQ为例)
    • 队列和交换器设置
      • 首先需要创建一个死信队列和一个普通的消息队列。同时,为普通消息队列配置死信交换器(Dead - Letter - Exchange,DLX)和死信路由键(Dead - Letter - Routing - Key)。当消息在普通队列中变成死信后,会根据DLX和DLR路由到死信队列中。
    • 消费者处理
      • 消费者可以正常地从普通消息队列中接收消息进行处理。当出现死信情况时,消息会自动地被路由到死信队列。开发人员可以通过编写专门的消费者程序来处理死信队列中的消息,这个消费者程序可以和处理正常消息的消费者程序分开,专门用于解决死信问题。

8、消息优先级【VIP插队】

  • rabbitmq 创建消息队列的时候 x-max-priority 属性 可配置

    1.优先级属性是 byte类型的,所以它的取值范围:[0,255];
    2.消息优先级为0或不设优先级的效果一样;
    3.消息优先级大于设定的优先级最大值时,效果同优先级的最大值(我们可以自行实验);
    

    rabbitmq
    消息消费结果

9、顺序消费

保证循序消费的方案【URL跳转】

四、MYSQL

  • 1.SQL执行流程

    1. 客户端
    2. 连接器
    3. 查询缓存
    4. 分析器
    5. 优化器
    6. 执行器
    7. 存储引擎
  • 2.索引

    索引类型数据结构
    普通索引普通索引是最基本的索引,它没有任何限制,值可以为空;仅加速查询。
    唯一索引唯一索引与普通索引类似,不同的就是:索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。简单来说:唯一索引是加速查询 + 列值唯一(可以有null)
    主键索引主键索引是一种特殊的唯一索引,一个表只能有一个主键,不允许有空值。简单来说:主键索引是加速查询 + 列值唯一(不可以有null)+ 表中只有一个。
    唯一索引组合索引指在多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。使用组合索引时遵循最左前缀集合。
  • 索引的优点
    1)创建索引可以大幅提高系统性能,帮助用户提高查询的速度;
    2)通过索引的唯一性,可以保证数据库表中的每一行数据的唯一性;
    3)可以加速表与表之间的链接;
    4)降低查询中分组和排序的时间。
    
      当然了,没有任何事情是完美的,索引也是如此,尽管索引好处非常多,但是其也有局限性合理性以及片面性。
    
  • 索引的缺点
      1)索引的存储需要占用磁盘空间;
      2)当数据的量非常巨大时,索引的创建和维护所耗费的时间也是相当大的;
      3)当每次执行CRU操作时,索引也需要动态维护,降低了数据的维护速度。
      
      表在正常使用的时候,增删索引,会导致锁表
      在当数据太少的时候,全盘搜索可能都比索引查找还快,就没有必要创建索引了,反而还会降低磁盘空间和性能。
    
  • 应该创建索引的列【分析索引-慢 SQL】
    1. 经常需要搜索的列上,可以加快搜索的速度
    2. 在作为主键的列上,强制该列的唯一性和组织表中数据的排列结构
    3. 经常用在连接(JOIN)的列上,这些列主要是一外键,可以加快连接的速度
    4. 经常需要根据范围(<,<=,=,>,>=,BETWEEN,IN)进行搜索的列上创建索引,因为索引已经排序,其指定的范围是连续的
    5. 经常需要排序(order by)的列上创建索引,因为索引已经排序,这样查询可以利用索引的排序,加快排序查询时间;
    6. 经常使用在WHERE子句中的列上面创建索引,加快条件的判断速度。
  • 不该创建索引的列
    1. 对于那些在查询中很少使用或者参考的列不应该创建索引。
      若列很少使用到,因此有索引或者无索引,并不能提高查询速度。相反,由于增加了索引,反而降低了系统的维护速度和增大了空间需求。
    2. 对于那些只有很少数据值或者重复值多的列也不应该增加索引。
      这些列的取值很少,例如人事表的性别列,在查询的结果中,结果集的数据行占了表中数据行的很大比例,即需要在表中搜索的数据行的比例很大。增加索引,并不能明显加快检索速度。
    3. 对于那些定义为text, image和bit数据类型的列不应该增加索引。
      这些列的数据量要么相当大,要么取值很少。
    4. 当该列修改性能要求远远高于检索性能时,不应该创建索引。(修改性能和检索性能是互相矛盾的)
  • 3.深分页处理方案

  • 4.redo log、binlog、innodb log

  • 5.慢日志

  • 6.sql语句优化

  • 7. show profile

    参考

      show profile命令可以显示多种性能分析数据,包括查询的执行时间、锁定等待时间、磁盘I/O等待时间、网络I/O等待时间等。
      这些数据可以帮助开发人员确定查询的瓶颈所在,以便进行优化。
    
  • 8.explain

      mysql> explain select * from student;
      +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------+
      | id | select_type | table   | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra |
      +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------+
      |  1 | SIMPLE      | student | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    2 |   100.00 | NULL  |
      +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------+
    
    1. id:选择标识符
    2. select_type:表示查询的类型。
    3. table:输出结果集的表
    4. partitions:匹配的分区
    5. type:表示表的连接类型
    6. possible_keys:表示查询时,可能使用的索引
    7. key:表示实际使用的索引
    8. key_len:索引字段的长度
    9. ref:列与索引的比较
    10. rows:扫描出的行数(估算的行数)
    11. filtered:按表条件过滤的行百分比
    12. Extra:执行情况的描述和说明
  • 9.information_schema

  • 10.两阶段提交

    10.1 update 语句执行的内部流程

    update user set name = "张三" where id = 2
    
      执行器先找引擎取 ID=2 这一行。ID 是主键,引擎直接用树搜索找到这一行。如果 ID=2 这一行所在的数据页本来就在内存中,就直接返回给执行器;否则,需要先从磁盘读入内存,然后再返回。
      执行器拿到引擎给的行数据,把这个值修改成张三
      引擎将这行新数据更新到内存中,同时将这个更新操作记录到 redo log 里面,此时 redo log 处于 prepare 状态。然后告知执行器执行完成了,随时可以提交事务。
      执行器生成这个操作的 binlog,并把 binlog 写入磁盘。
      执行器调用引擎的提交事务接口,引擎把刚刚写入的 redo log 改成提交(commit)状态,更新完成。
      两阶段提交就是先提交 redolog,然后写入 binlog,binlog 写入成功后再提交 redolog
    
  • 11.B-True

      关键字集合分布在整颗树中;
      任何一个关键字出现且只出现在一个结点中;
      搜索有可能在非叶子结点结束;
      其搜索性能等价于在关键字全集内做一次二分查找;
      自动层次控制;
    

在这里插入图片描述

  • 12.B+True

      所有关键字都出现在叶子结点的链表中(稠密索引),且链表中的关键字恰好是有序的;
      不可能在非叶子结点命中;
      非叶子结点相当于是叶子结点的索引(稀疏索引),叶子结点相当于是存储(关键字)数据的数据层;
      每一个叶子节点都包含指向下一个叶子节点的指针,从而方便叶子节点的范围遍历。
      更适合文件索引系统;
    

在这里插入图片描述

  • 13.MVCC流程图

在这里插入图片描述


五、MyBatils

1.#{}${}的区别是什么?
${}是字符串替换,#{}是预处理;

Mybatis在处理${}时,就是把${}直接替换成变量的值。
而Mybatis在处理#{}时,会对sql语句进行预处理,将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;

使用#{}可以有效的防止SQL注入,提高系统安全性。
2.Mybatis的一级二级缓存
1)一级缓存: 基于 PerpetualCacheHashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存。

(2)二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCacheHashMap 存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializable序列化接口(可用来保存对象的状态),可在它的映射文件中配置 ;

(3)对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear 掉并重新更新,如果开启了二级缓存,则只根据配置判断是否刷新。

六、Spring

可以参考

1. AOP

2.IOC

3.双亲委派


4.MVC执行流程


5. 如何把服务交给Spring管理

6.@Autowired 和 @Resource 的区别

  1. 来源不同
    • @Autowired
      • @Autowired是Spring框架提供的注解,用于自动装配Bean。它是Spring依赖注入(Dependency Injection,DI)机制的一部分。在Spring容器中,当一个Bean需要依赖另一个Bean时,可以使用@Autowired来让Spring自动完成依赖注入。例如,在一个Service层的类中需要依赖一个Repository层的类来进行数据库操作时,可以使用@Autowired来注入Repository类的实例。
    • @Resource
      • @Resource是Java标准的注解,来自于javax.annotation.Resource包。它是Java EE(现在是Jakarta EE)规范的一部分,并不是Spring特有的。不过,Spring也支持这个注解用于依赖注入。
  2. 装配规则不同
    • @Autowired
      • 默认按类型(byType)装配:当使用@Autowired注解时,Spring默认会根据属性的类型来查找并注入Bean。例如,如果有一个UserService类中有一个@Autowired注解的UserRepository属性,Spring会在容器中查找类型为UserRepository的Bean,并将其注入到UserServiceUserRepository属性中。
      • 如果存在多个相同类型的Bean会报错(可通过其他方式解决):如果容器中有多个相同类型的Bean,Spring不知道该注入哪一个,会抛出一个异常。不过,可以通过使用@Qualifier注解来指定Bean的名称来解决这个问题。例如,如果有UserRepositoryImpl1UserRepositoryImpl2两个实现了UserRepository接口的Bean,在UserService中注入时可以使用@Autowired @Qualifier("userRepositoryImpl1")来指定注入userRepositoryImpl1这个Bean。
    • @Resource
      • 默认按名称(byName)装配@Resource注解默认情况下会根据属性的名称来查找并注入Bean。它首先会在容器中查找名称与属性名相同的Bean。例如,如果有一个UserService类中有一个@Resource注解的userRepository属性,Spring会先查找名称为userRepository的Bean来进行注入。
      • 如果找不到同名Bean,则按类型装配:如果没有找到名称匹配的Bean,@Resource会按照类型来查找Bean进行注入。这一点和@Autowired的默认行为有些相似,但触发条件不同。例如,在一个简单的应用场景中,如果只有一个UserRepository类型的Bean,即使@Resource注解的属性名和Bean名称不一致,也会将这个唯一的UserRepository类型的Bean注入进去。
  3. 使用场景建议
    • @Autowired
      • 在纯Spring项目中,尤其是使用了大量Spring的高级特性(如AOP、Spring Boot自动配置等),@Autowired是一个很好的选择。因为它与Spring的其他机制紧密集成,能够方便地配合Spring的组件扫描、自动配置等功能。例如,在Spring Boot的Web应用中,自动配置的RestController中的服务层Bean注入可以很方便地使用@Autowired
    • @Resource
      • 在一些需要遵循Java标准规范或者需要在不同的容器环境(如从Spring容器迁移到其他符合Java EE规范的容器)中保持兼容性的项目中,@Resource更合适。因为它是Java标准的注解,具有更好的通用性。不过,在复杂的Spring应用中,可能需要更加注意它的名称和类型装配规则,以避免注入错误的Bean。

七、SpringCloud Albaba

简介
Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用微服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。

依托 Spring Cloud Alibaba,您只需要添加一些注解和少量配置,就可以将 Spring Cloud 应用接入阿里微服务解决方案,通过阿里中间件来迅速搭建分布式应用系统。

主要功能

  • 服务限流降级 :默认支持 WebServlet、WebFlux, OpenFeign、RestTemplate、Spring Cloud Gateway, Zuul, Dubbo 和 RocketMQ 限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级 Metrics 监控。
  • 服务注册与发现 :适配 Spring Cloud 服务注册与发现标准,默认集成了 Ribbon 的支持。
  • 分布式配置管理 :支持分布式系统中的外部化配置,配置更改时自动刷新。
  • 消息驱动能力 :基于 Spring Cloud Stream 为微服务应用构建消息驱动能力。
  • 分布式事务 :使用 @GlobalTransactional 注解, 高效并且对业务零侵入地解决分布式事务问题。
  • 阿里云对象存储 :阿里云提供的海量、安全、低成本、高可靠的云存储服务。支持在任何应用、任何时间、任何地点存储和访问任意类型的数据。
  • 分布式任务调度 :提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。同时提供分布式的任务执行模型,如网格任务。网格任务支持海量子任务均匀分配到所有 Worker(schedulerx-client)上执行。
  • 阿里云短信服务 :覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道。

组件

  • [Sentinel] :阿里巴巴源产品,把流量作为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。

  • [Nacos] :一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。

  • [RocketMQ] :一款开源的分布式消息系统,基于高可用分布式集群技术,提供低延时的、高可靠的消息发布与订阅服务。

  • [Dubbo] :Apache Dubbo™ 是一款高性能 Java RPC 框架。

  • [Seata] :阿里巴巴开源产品,一个易于使用的高性能微服务分布式事务解决方案。

  • [Alibaba Cloud OSS] : 阿里云对象存储服务(Object Storage Service,简称 OSS),是阿里云提供的海量、安全、低成本、高可靠的云存储服务。您可以在任何应用、任何时间、任何地点存储和访问任意类型的数据。

  • [Alibaba Cloud SchedulerX]: 阿里中间件团队开发的一款分布式任务调度产品,提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。

  • [Alibaba Cloud SMS] : 覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道。

组件

Nacos 服务发现、配置管理和服务管理平台

1.多人本地开发,如何共同使用测试环境的同一个Nacos进行调试?
2.Config 配置信息更改,代码中是如何感知到的?
3.Config 配置文件存储在哪里?
4.服务的注册发现是如何实现的?

Getway 网关

1.如何使用灰度发布
2.白名单
2.路径跳转

Sentinel 分布式服务架构的实时流量控制、熔断降级和系统负载

Seata 分布式事务

Admin 监控服务

SkyWalking 链路追踪

RocketMQ 消息队里

微服务架构图

八丶微服务面试题

  • 1.微服务中A服务调用B服务如何传递Token?
  • 2.微服务 如何把A服务提供为工具,然后引入到别的服务中,原理是什么?

九丶Elasticsearch

参考

1.搜索字段的类型设计 field type【textkeyword
text用于全文搜索,支持分词,
keyword则用于精确搜索,不分词。
2. Elasticsearch 的打分机制

2.1、TF-IDF 逆文档频率,TF term frequency 词频IDF inverse document frequency 逆文档频率
在这里插入图片描述
第一个句子提到 Elasticsearch 一次 ,而第二个句子提到 Elasticsearch 两次,所以包含第二句话的文档应该比包含第一句话的文档拥有更高的得分。按照数量来讨论,第一句话的

    **词频( TF )是 1,而第二句话的词频将是 2。**

2.2、如果一个分词在索引的不同文档中出现越多的次数,那么它就越不重要。逆文档频率是一个重要的因素,用于平衡词条的词频。
在这里插入图片描述

词条 Elasticsearch的文档频率是 2 (3篇中2篇出现),文档频率的逆源自得分乘以 1/DF,这里 DF是该词条的文档频率。
词条拥有更高的文档频率, 它的权重就会降低。
词条 the 的文档频率是 3 (3篇中都出现),尽管the在最后一篇文档中出现了两次,它的文档频率还是3。
因为逆文档频率只检查一个词条是否出现在某文档中,而不检查它出现多少次。

2.3、 Lucene评分公式

   	词条的词频越高,得分越高
	索引中词条越罕见,逆文档频率越高![在这里插入图片描述](https://img-blog.csdnimg.cn/0bf6e680f8834a798b2b71d75422e542.png)   
2.4. 其他打分方法
1.Okapi BM25
2.随机性分歧 (Divergence from randomness),即 DFR 相似度
3.基于信息的 (Information based), 即lB相似度
4.LM Dirichlet相似度
5.LM Jelinek Mercer相似度
3. elasticsearch 的倒排索引是什么
4. elasticsearch 是如何实现 master 选举的
5. elasticsearch 分片,备份的设计

3.我是文本 粉红

十丶JVM

文档参考

1 jstack
2 Arthas(Java 应用诊断利器)
3、内存模型

在这里插入图片描述

4、java文件加载过程

在这里插入图片描述

5、CMS 和 G1的区别

在这里插入图片描述

  1. CMS(Concurrent Mark - Sweep)垃圾回收器

    • 工作原理

      • 初始标记(Initial Mark):这是垃圾回收过程中的第一个阶段,需要暂停应用程序(Stop - The - World,STW)。在这个阶段,垃圾回收器会标记从根对象(如栈帧中的本地变量、静态变量等)直接可达的对象。这个阶段的停顿时间通常比较短,因为它只需要标记最开始的一层对象。
      • 并发标记(Concurrent Mark):在这个阶段,垃圾回收器与应用程序并发运行,通过追踪对象引用链来标记所有可达的对象。在并发标记过程中,应用程序可以正常运行,但是由于应用程序在运行过程中可能会改变对象的引用关系,所以可能会产生标记不准确的情况。
      • 重新标记(Remark):为了修正并发标记阶段产生的标记不准确的问题,这个阶段需要再次暂停应用程序。垃圾回收器会重新扫描在并发标记阶段可能发生变化的对象,确保标记的准确性。这个阶段的停顿时间一般比初始标记阶段长。
      • 并发清除(Concurrent Sweep):在这个阶段,垃圾回收器与应用程序并发运行,清除那些在标记阶段被确定为不可达的对象。由于是并发执行,所以这个阶段不会暂停应用程序。
    • 特点

      • 低延迟:CMS的主要目标是减少垃圾回收时的停顿时间,它通过并发标记和并发清除阶段来尽量减少对应用程序的影响。这使得它在对响应时间要求较高的应用场景中比较受欢迎,比如Web应用服务器。
      • 内存碎片问题:由于采用的是标记 - 清除算法,在长期运行后,可能会产生内存碎片。内存碎片会导致在分配大对象时,虽然总的空闲内存足够,但是没有连续的内存空间来分配,从而不得不触发一次Full GC来进行内存整理。
      • CPU资源占用:在并发阶段,虽然应用程序可以正常运行,但是垃圾回收器会占用一定的CPU资源。如果CPU资源紧张,可能会影响应用程序的性能。
    • 适用场景

      • 适用于对响应时间敏感,能够容忍一定程度的内存碎片,并且有足够的CPU资源来支持并发垃圾回收的应用。例如,在一个高并发的电商网站后台服务器中,大量的用户请求需要快速响应,CMS垃圾回收器可以在不影响用户体验的情况下,有效地清理垃圾。
  2. G1(Garbage - First)垃圾回收器

    • 工作原理

      • 内存划分:G1将堆内存划分为多个大小相等的区域(Region),这些区域可以是Eden区、Survivor区或者Old区。这种划分方式使得G1能够更加灵活地处理不同大小的对象,并且可以根据对象的存活情况和垃圾分布情况,有针对性地选择部分区域进行回收。
      • 垃圾回收阶段
        • 初始标记(Initial Mark):暂停应用程序,标记从根对象直接可达的对象,这个阶段的停顿时间很短。
        • 并发标记(Concurrent Mark):使用三色标记算法,在应用程序运行的同时进行对象标记,标记出所有可达对象。这个阶段可能会产生新的垃圾,但是G1会在后续阶段处理。
        • 最终标记(Final Mark):再次暂停应用程序,处理在并发标记阶段产生的新的引用变化,确保标记的准确性。
        • 筛选回收(Live Data Counting and Eviction):根据每个区域中的垃圾比例和回收成本等因素,选择部分区域进行回收,将存活对象复制到新的区域中,释放旧的区域。这个阶段也会有短暂的暂停。
    • 特点

      • 可预测的停顿时间:G1可以通过设置-XX:MaxGCPauseMillis参数来控制垃圾回收的最大暂停时间目标。例如,-XX:MaxGCPauseMillis = 200表示期望每次垃圾回收的暂停时间不超过200毫秒。G1会尽量满足这个目标,通过调整回收的区域数量和顺序来实现。
      • 高效的内存利用:G1可以根据垃圾分布情况灵活地选择回收区域,避免了像传统分代垃圾回收器那样,整代内存空间不足才进行回收的情况,提高了内存的利用率。
      • 内存整理:在回收过程中,G1会将存活对象复制到新的区域,在一定程度上避免了内存碎片的产生。
    • 适用场景

      • 适用于大内存、高并发的应用场景,特别是对停顿时间有严格要求的应用。例如,在大数据处理或者大型分布式系统中,G1可以有效地管理内存,并且在有限的停顿时间内完成垃圾回收,保证系统的稳定性和性能。

十一、Springboot

1、springboot自动装配

参考链接

  1. 启动入口与自动配置加载
    • Spring Boot应用的启动是从@SpringBootApplication注解开始的。这个注解是一个组合注解,它包含了@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan
    • @EnableAutoConfiguration的关键作用:这是自动装配的核心注解。它实际上是借助@Import(AutoConfigurationImportSelector.class)来实现自动配置的导入。AutoConfigurationImportSelector是一个实现了ImportSelector接口的类,它的主要任务是从类路径下加载自动配置类。
  2. 自动配置类的发现机制
    • META - INF/spring.factories文件:在Spring Boot的自动配置中,META - INF/spring.factories文件扮演着至关重要的角色。所有的自动配置类都是通过这个文件来被发现的。在这个文件中,org.springframework.boot.autoconfigure.EnableAutoConfiguration键对应的值是一系列自动配置类的全路径。例如,DataSourceAutoConfiguration用于自动配置数据源相关的内容,它的配置信息就会在这个文件中被引用。
    • 条件注解的筛选:并不是所有在spring.factories文件中的自动配置类都会被加载。Spring Boot使用了大量的条件注解(如@ConditionalOnClass@ConditionalOnMissingBean等)来筛选自动配置类。这些条件注解会根据类路径下是否存在特定的类、是否已经存在特定的Bean等来决定自动配置类是否生效。
    • 示例说明条件注解:以DataSourceAutoConfiguration为例,它使用了@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })条件注解。这意味着只有当类路径中存在DataSource类和EmbeddedDatabaseType类时,这个自动配置类才会被加载。如果项目中没有引入数据库相关的依赖,那么这个自动配置类就不会生效,从而避免了不必要的配置加载。
  3. 自动配置的具体过程
    • 属性绑定:Spring Boot会将配置文件(如application.propertiesapplication.yml)中的配置属性与自动配置类中的属性进行绑定。例如,在DataSourceAutoConfiguration中,会有一个DataSourceProperties类来接收关于数据源的配置属性,如数据库的URL、用户名、密码等。通过@ConfigurationProperties注解,这些属性可以从配置文件中获取对应的配置值。
    • Bean的创建与注册:自动配置类会根据配置属性和条件判断来创建和注册Bean。还是以DataSourceAutoConfiguration为例,当满足条件并且配置属性正确时,它会创建DataSource这个Bean并注册到Spring容器中。这些被注册的Bean可以被应用程序中的其他组件(如Repository层的类)所使用,从而实现了自动配置的功能。
  4. 自定义自动配置
    • 如果开发者想要自定义自动配置,可以通过创建自己的自动配置类来实现。首先,需要在META - INF/spring.factories文件中添加自定义自动配置类的路径,使其能够被Spring Boot发现。然后,同样要使用条件注解来确保自定义自动配置类在合适的条件下生效。例如,开发一个自定义的消息队列自动配置类,根据是否引入了特定的消息队列依赖和配置属性来决定是否创建消息队列相关的Bean。

十三、项目

1、项目流程图

在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值