大明哥 大明哥

程序员、死磕 Java 创始人

死磕 Java 大明哥精心打造的 Java 进阶类系列教程,希望大家少走弯路。 更多死磕系列

  • Java现在发布的版本很快,每年两个,但是真正会被大规模使用的是3年一个的LTS版本。每3年发布一个LTS(Long-TermSupport),长期维护版本。意味着只有Java8,Java11,Java17,Java21才可能被大规模使用。每年发布两个正式版本,分别是3月份和9月份。在Java版本中,一个特性的发布都会经历孵化阶段、预览阶段和正式版本。其中孵化和预览可能会跨越多个Java版本。所以大明哥在介绍Java新特性时采用如下这种策略:每个版本的新特性,大明哥都会做一个简单的概述。单独出文介绍跟编码相关的新特性,一些如JVM、性能优化的新特性不单独出文介绍。孵化阶段的新特性不出文介绍。首

  • 又到年底了,又是好多小伙伴主动或者被动找工作的时候了,最近好多小伙伴,都问我有没有最新面试题,有!!肯定有!!我就把我看过的和整理过的面试题,以答案都整理好,整理了目前最新版的《互联网大厂面试题》,涵盖了所有的Java面试题,并且为了各位小伙伴更好地有针对性地刷题,大明哥按照技术类别将面试题分了类,包括:Java基础、Java并发、Spring、SpringBoot、设计模式、Redis、Kafka等等共25个分类,99个PDF文件。面试题:25个文件夹99个PDF,包括Java集合、JVM、多线程、并发编程、设计模式、Java、MyBatis、ZooKeeper、Dubbo、Elastics

    2023-12-06
  • 上一个死磕Java专栏【死磕NIO】(当然写的不是很好,争取今年将它重写一遍)是**【死磕Netty】**的铺垫,对于我们Java程序员而言,我们在实际开发过程一般都不会直接使用JavaNIO作为我们的网络编程框架,因为写出一套高质量的JavaNIO程序并不是一件容易的事,除了JavaNIO固有的复杂性和bug之外,作为NIO服务端,我们要处理的事情太多了,如网络闪断、客户端认证、消息编解码、半包读写,客户端一样也有很多复杂的事情要处理,所以如果我们对JavaNIO没有足够了解,没有足够的网络编程经验的话,利用JavaNIO来编写一个高性能的稳定网络编程框架并不是一件容易的事。所以我们一般都不

    2023-10-06
  • 回答在Java中我们可以通过如下几种方式来判断线程池中的任务是否已全部执行完成。使用isTerminated()方法使用ThreadPoolExecutor的getCompletedTaskCount()使用CountDownLatch扩展使用isTerminated()方法isTerminated()用来判断线程池是否结束,如果结束返回true。使用它时我们必须要在shutdown()方法关闭线程池之后才能使用,否则isTerminated()永不为true。shutdown():拒绝接受新任务,但会继续处理已提交的任务。shutdownNow():尝试停止所有正在执行的任务并返回未执行的任

    2024-08-04
  • 回答因为在try-catch中是无法捕获到子线程抛出的异常。主要原因是因为,每个线程都有自己独立的堆栈空间和执行上下文。由于它是独立的,所以当一个线程抛出异常后,这个异常只会沿着该线程的调用栈向上传播,它不会自动传播到启动它的主线程上去,所以也就不会被主线程的try-catch捕获。那子线程抛了异常要怎么处理呢?目前主流的处理方法有四个:子线程自己捕获异常,自己处理。这是一种最直接的方式。使用Thread.UncaughtExceptionHandler:UncaughtExceptionHandler是Thread的一个内部函数式接口,它用于处理由于线程抛出的未捕获异常而导致线程突然终止的情

    2024-08-04
  • 回答run()run()是Thread的一个实例方法,它实际上是线程执行的入口,但是它本身不会启动一个新的线程,它仅仅只是一个普通的方法调用。publicclassThreadTest{publicstaticvoidmain(String[]args){MyRunnablerunnable=newMyRunnable();runnable.run();}staticclassMyRunnableimplementsRunnable{@Overridepublicvoidrun(){System.out.println("Runningin"+Thread.currentT

    2024-08-04
  • 回答yield()是Thread中的一个静态方法,它的作用是让当前正在执行的线程主动让出CPU使用权,但并不将线程从可运行状态(Runnable)变为阻塞状态(Blocked),也不放弃锁。换句话说,调用yield()的线程仍然是可运行的(Runnable),它只是告诉线程调度器,当前线程愿意让出CPU时间片,供其他同样处于可运行状态的线程使用。扩展通常情况下,yield()的使用场景比较少,使用它时我们需要注意两个问题:不可预测性:yield()仅仅只是让出CPU时间片,使用它理论上可以增加线程切换的机会,但是也只是理论上的可能,因为线程调度器并不会立刻进行切换,而且它可能会忽略这个提示,继

    2024-08-04
  • 回答死锁死锁是指两个或两个以上的线程相互等待对方释放已经持有的锁,从而导致所有涉及的线程都无法继续执行的情况。死锁的发生需要满足四个条件:互斥:线程对资源的访问是互斥的,即一次只能一个线程访问资源。占有并等待:线程已经占有至少一个资源,并且在等待其他线程占有的资源。不可剥夺:资源不能被强制性地从一个线程中剥夺,只能由占有它的线程释放。循环等待:存在一个线程集{T1,T2,...,Tn},其中T1等待T2占有的资源,T2等待T3占有的资源,直到Tn等待T1占有的资源,形成一个循环等待链。活锁活锁是指两个或两个以上的线程不断地相互让出资源,但是都无法继续执行具体的工作。活锁与死锁不同的地方就在于:

    2024-08-04
  • 回答notify()用于唤醒在该对象的监视器上等待的单个线程。如果有多个线程在该对象上等待,notify()会随机选择其中一个线程进行唤醒。notifyAll()用于唤醒在该对象的监视器上等待的所有线程。所有被唤醒的线程将重新获取对象的锁,但是只有其中一个线程能获取成功,其他线程则会继续等待直到再次获取锁。扩展锁池和等待池锁池(EntrySet):指那些试图获取某个对象的锁,当又没有获取到锁的线程的集合。当一个线程尝试获取对象锁时,如果该对象锁已经被其他线程持有,那么该线程就会进入锁池,等待获取锁的机会。等待池(WaitSet):指那些调用了wait()方法,并且当前正在等待被唤醒的线程的集合

    2024-08-04
  • 回答在Java中我们可以通过Thread类的isAlive()来判断线程是否存活。扩展isAlive()返回一个Boolean,true表示线程还在执行,false表示线程已经终止。@TestpublicvoidisAliveTest(){Threadthread=newThread(()->{for(inti=0;i<5;i++){log.warn("{}-正在执行-[{}]...",Thread.currentThread().getName(),i);try{Thread.sleep(1000L);}catch(InterruptedExceptione)

    2024-08-04
  • 回答LongAdder和AtomicLong都是用于确保在多线程环境下对Long类型变量进行原子性操作,而不会出现线程安全问题。虽然两者功能相似,但是还是存在一些差异。一、实现方式AtomicLong是基于CAS方式自旋更新,使用单个变量来存储计数值,每次更新操作都是对这个单一变量进行CAS操作。LongAdder是基于分段计数,将值分散到多个变量(称之为Cell),每个线程会选择不同的Cell来更新,减少了对单个变量的竞争。在最终需要获取总计数值时,将所有Cell的值累加起来返回。二、性能AtomicLong由于使用的CAS自旋方式,如果在高并发环境下,则容易导致CAS操作失败,需要不断自旋

    2024-08-04
  • 回答Java的原子操作类是位于java.util.concurrent.atomic包中的类,他们主要用于确保在多线程环境下对某些变量的操作是原子操作。在多线程环境下,对单个变量进行操作,会出现多线程安全问题,导致数据不一致,为了避免这种情况发生,我们可以使用同步机制,比如synchronized或者Lock,但是他们成本太高了,而原子操作类则提供了一种高效的方法来实现线程安全操作。Java提供了12个原子操作类,共分为四大类。一、原子更新基本类AtomicBoolean:原子更新布尔值。AtomicInteger:原子更新整数值。AtomicLong:原子更新长整数值。二、原子更新数组Ato

    2024-08-04
  • 回答Unsafe是Java中一个非常底层的类,它位于sun.misc,它主要用于执行低级别、不安全操作的方法,如直接访问系统内存资源、自主管理内存资源等。Unsafe中的方法大多数都是native方法,这些native方法都是操作系统级别的方法:执行内存操作:我们可以利用Unsafe直接操作内存,比如分配内存(allocateMemory)、释放内存(freeMemory)、设置内存(setMemory)、获取内存值(getInt、getLong等)。CAS操作:调用操作系统的CAS指令,实现CAS的功能。对象操作:利用Unsafe,我们可以突破Java语法本身的限制,直接从内存级别去操作Ja

    2024-08-04
  • 回答默认情况下,线程池的核心线程是不会被回收的,即使它们处于空闲状态。但是ThreadPoolExecutor还是提供了一个参数来控制这个行为,通过allowCoreThreadTimeOut(true)设置后,核心线程在空闲超过keepAliveTime时就会被回收。我们直接看源码,直接看ThreadPoolExecutor中runWorker():finalvoidrunWorker(Workerw){Threadwt=Thread.currentThread();Runnabletask=w.firstTask;w.firstTask=null;w.unlock();//allowint

    2024-08-04
  • 回答ThreadPoolExecutor对核心参数提供了一些setter方法,根据这些setter方法我们可以在程序运行时动态调整线程池的核心参数:publicvoidsetCorePoolSize(intcorePoolSize);publicvoidsetMaximumPoolSize(intmaximumPoolSize);publicvoidsetKeepAliveTime(longtime,TimeUnitunit);publicvoidsetThreadFactory(ThreadFactorythreadFactory);publicvoidsetRejectedExecutio

    2024-08-04
  • 回答线程池的拒绝策略是任务在提交给线程池时,线程池无法接受新任务的情况下执行的处理策略。目前线程池内置了四种拒绝策略。一、AbortPolicy默认的拒绝策略。抛出异常,中止任务。如果任务无法提交到线程池时会抛出RejectedExecutionException,我们需要处理好该异常,否则会影响后续任务的执行。二、CallerRunsPolicy该策略不会丢弃任务,只要线程池没有关闭的话,则使用提交任务的线程来执行这个任务。它一般适用于并发量比较小,对任务执行时间要求不严格的场景。但是,由于是调用者自身执行任务,如果任务提交速度过快,可能导致程序阻塞,性能效率上必然的损失较大。三、Disca

    2024-08-04
  • 回答Executors是Java并发包里面提供的一个用于创建Java线程池的工具类,它提供了一个些工厂方法用于创建常见的线程池,给我们带来了一定程度上的遍历,但是为什么不推荐时间呢?主要有两个原因:一、任务队列没有设置固定容量大小newFixedThreadPool()和newSingleThreadExecutor(),他们在创建线程池时使用的是无界队列LinkedBlockingQueue。这就意味着当所有线程都在处理任务时,新来的任务会不断地加入到队列中,由于是无界的,所以可以无限制的添加直到系统内存耗尽,从而OOM。二、最大线程数量是Integer.MAX_VALUEnewCached

    2024-08-04
  • 回答从公平的角度来看,Java的锁分为两类:公平锁和非公平锁。公平锁公平锁是指多个线程在尝试获取同一个锁时,锁会按照线程请求的顺序来分配。也就是说,谁先请求锁,谁就先获取锁。公平锁的底层实现依赖于一个FIFO队列,当一个线程请求获取锁时,如果锁被其他线程占用,则该线程会被加入到等待队列的末尾。当锁被释放时,锁会从队列取出第一个等待的线程并将锁分配给它。公平锁的优点在于它避免了线程饥饿问题,因为所有线程都有同等的获取锁的权利。但是,它性能相比于非公平锁是低的,因为它每次获取锁都需要维护一个队列,增加了额外的开销。非公平锁非公平锁是指多个线程在尝试获取同一个锁时,锁的分配不考虑请求顺序,谁抢到就是

    2024-08-04
  • 回答Java提供了多种解决线程安全问题的方法,主要分为几类:同步机制无锁机制线程局部变量同步方式一、synchronizedsynchronized是一个用于实现线程同步,确保多线程环境下对共享资源安全访问的关键字。它可以修饰方法或代码块,保证同一时刻只有一个线程可以执行同步方法或代码块内的代码。publicvoidincrement(){synchronized(this){count++;}}关于synchronized的核心原理请阅读这几篇文章:synchronized的实现原理是怎样的?synchronized锁的是什么?synchronized的锁升级过程是怎样的?synchroni

    2024-08-04
  • 回答要想保证T1、T2、T3顺序执行,有多种方法。我们先新建一个Tread:classMyThreadextendsThread{privateStringthreadName;publicMyThread(Stringname){this.threadName=name;}@Overridepublicvoidrun(){System.out.println(threadName+"isrunning");}}一、使用join()join()是Java提供的一种用于线程同步的机制,它能够让一个线程等待另一个线程完成后再继续执行。它有三个重载方法:join():无限等待,直

    2024-08-04
  • 回答ThreadPoolExecutor使用corePoolSize和maximumPoolSize控制核心线程数和最大线程数。初始时,线程池会创建核心线程数(corePoolSize)的线程来处理任务。当任务被提交到线程池后,线程池会首先尝试使用空闲的核心线程执行任务,如果没有空闲的核心线程,则任务会被放入任务队列(workQueue)。工作线程会从任务队列中获取任务执行,从而避免频繁创建和销毁线程。那线程是如何实现复用的呢?在ThreadPoolExecutor#runWorker()方法中,工作线程会不断地从任务队列(workQueue)中获取任务,当一个线程执行完任务后,线程并不会直接

    2024-08-04
  • 回答死锁指的是两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,如果没有外力的帮助下,它们都将无法推进下去。简单的说就是两个或以上的进程都在互相等待对方释放资源,但是它们谁都不释放进程,系统就卡住了。解决死锁的方法主要有以下几种:以确定的顺序获得锁:在获取锁的顺序上进行控制,确保所有线程获取锁的顺序是一致的,这样就可以预防死锁的产生。超时放弃:在获取锁的时候设置超时时间,如果在指定时间内未获取锁,我们可以做其他操作,避免死锁。比如使用ReentrantLock的tryLock()。监控和检测:使用一些死锁检测工具来检测线程是否发生了死锁,如果发现死锁,我们可以采取一些必要

    2024-08-04
  • 回答在我们调用ConcurrentHashMap的put()时,ConcurrentHashMap会校验key和value是否为null,如果为null就抛出NullPointerException,如下:为什么要这么设计呢?大明哥认为主要有如何几个原因。一、避免多线程下的二义性null是一个特殊的值,表示没有对象或者引用。在ConcurrentHashMap中如果key或者value为null,当我们通过get(key)获取对应的value的时候,如果返回的结果是null,我们是没有办法判断的,它是put(k,v)的时候,value本身为null值,还是这个key本身就不存在呢?在并发编程中

    2024-08-04
  • 回答线程安全的本质是内存安全,在Java中我们绝大多数的数据都是存储在Java堆中的,而Java堆是所有线程共享的。当多个线程访问同一个对象时,如果能够获得预期的结果,那么我们就会说这个对象线程安全,反之线程不安全。一个对象是否线程安全需要考虑两个问题:共享资源首先我们的资源需要是共享的,例如线程独享的局部变量我们是无需考虑其线程安全问题的。多个线程访问和修改同一块资源,可能会导致资源状态的不一致。竞争条件如果多个线程同时访问共享资源,并试图同时修改它,就会产生竞争条件。同时,线程安全还问题还涉及三个核心问题:原子性原子性指的是一个或多个操作在CPU执行的过程中是不可分割的、执行过程中不可被中

    2024-08-04
  • 回答在进行分库分表后,如果我们对业务的预估数据不准确,由于数据增长超出预期导致表再次不够用了,那么这时应该怎么办呢?遇到这种情况我们首先需要考虑的是能否利用一些常规手段,如历史数据归档或者清理不必要的数据,通过这样的常规手段来减少表的数据量,可以参考这篇文章:如果常规的方式不行,那就只能进行二次分表了。二次分表我们主流的做法是选择水平双倍扩容法,即在原来的基础上扩大一倍,例如原来表为64张,此次扩容则是128张。为什么选择双倍呢?可以参考这篇:分库分表时为什么数量都推荐使用2的幂次方?扩容简单,但是扩容后的工程量就比较大了,我们需要更新分表算法、进行数据迁移,在数据迁移的过程中我们还需要考虑考

    2024-08-04
  • 回答在我们分库分表时,我们首先需要确认我们要划分为多少个库,每个库多少张表,这个时候我们一般都是建议使用2^N,也就是2、4、8、18、32这样的数据。这样做有什么好处呢?一、哈希算法的优化如果我们的分库分表策略采用的是Hash取模策略时,若分片的数量是2^N,则哈希值对分片数量取模的计算可以通过简单的位与运算来完成。例如,假设分片数量是8(即2^3),要计算哈希值H对8取模,可以用位与运算H&(8-1)来替代H%8。位与运算(&)比取模运算(%)更高效,因为它直接操作二进制位而无需进行除法计算。二、数据分布更加均匀在分裤时,如果我们库、表的分片数量都是2^N,例如8库64表,

    2024-08-04
  • 回答在分库分表后,我们的数据都分散不同数据库的不同表中,那遇到后端进行模糊查询怎么办?比如用户分库分表后,我们需要查询所有姓“张”的用户,怎么办?你在网上会看到各种各样的奇淫技巧,其实,真正可行的方案只有一个,那就是上搜索引擎,比如ES。该方案比任何奇淫技巧都有效,尤其是面对海量数据和多维度的复杂查询。你数据库优化得再牛逼,索引再屌,在海量数据面前,执行类似like'%大明哥%'这样的操作都顶不住。所以,方案只有一个,上搜索引擎。

    2024-08-04
  • 回答当我们完成数据迁移且数据校验没什么问题的时候,我们就开始切流了。为什么是切流,而不是一把切呢?因为我们无法保证新系统就一定是没有问题的,如果有问题的话,直接上线新系统关闭老系统这样的操作会带来史诗级灾难。所以,为了避免你年底背绩效,你需要灰度、按流慢慢来切换到新系统。第一步:切流测试如果线上有流量录制功能,我们可以在线上采集一些流量,然后将新库数据dump到准生产环境(或者独立的测试环境),进行回放。回放完成后,我们需要进行数据验证,待数据验证无误后,准备发布上线。第二步:二次校验条件允许的情况下,为了安全起见,在正式切流之前,我们可以进行二次校验,这次校验最好是全量,如果二次校验发现还有

    2024-08-04
  • 回答要分为两种情况,停机和不停机。停机迁移如果可以进行停机迁移,那么就提前几天发布一个公告,通知用户会预计停机多长时间。到了预订时间就关闭应用进行数据迁移,数据完成后进行数据对比,数据对比没有问题就发布新系统。停机迁移是一个非常理想的情况,可以避免很多麻烦,当然,在绝大多数情况下我们是不会允许停机迁移的,毕竟数据迁移是一个大工程,不是几个小时能够处理好的。大明哥经历过一次数据迁移,前前后后搞了两个多月。不停机迁移如果需要不停服将单库数据迁移到分库分表系统去的话,我们需要一个数据迁移系统,这个数据迁移系统的职责就是负责将数据从单库按照某种路由规则迁移到分库分表的环境中。如下:那数据迁移程序怎么做

    2024-08-04
  • 回答当MySQL中的单表数据量很大以后(超千万级别),我们一般都会考虑采用分库分表,主要因为单表单库的性能瓶颈和存储限制。详情见:我们为什么需要分库分表?为什么MySQL中单表操作千万后就要分库分表?但是,当数据量一大就要分库分表吗?其实不然,虽然分库分表在一定程度上提升了系统的性能和扩展了存储限制,但是分库分表的实现方案比较复杂,需要考虑的问题有很多,比如如何从单库迁移系统到分库分表系统,如何进行数据迁移,而且分库分表后也会带来很多问题,比如事务、跨库join等等。分库分表后,如何解决分页查询问题?分库分表后,如何解决join的问题?分库分表后,有哪些查询问题?该如何解决?所以,不到万不得已

    2024-08-04
  • 回答分库分表后,我们通常会遇到下面几个查询问题:跨库查询问题分页查询问题排序问题聚合问题关联查询问题为了更好地理解,大明哥按照分库分表的维度来对这几个问题进行阐述。垂直分库产生的问题垂直分裤一般来说都是按照业务维护来进行拆分,其核心理念就是专库专用,即将不同的业务数据分别放入到不同的数据库中。将原来单一的数据库按照业务维度拆分为多个库,虽然在性能上能够带来很大的提升,但是带来的问题也是非常多,主要设计如下几个问题:跨库join问题将不同的业务数据拆分到不同的库中,这就会导致原来一个join就能解决的问题现在解决不了。那怎么办呢?有如下几种方案:数据冗余:将需要jion查询的数据表字段冗余在一个

    2024-08-04
  • 回答在进行水平分库分表后,我们可能会将一个类型的数据拆分到了多个库中的多个表中去了,比如对于订单表t_order,为了用户端能够更加方便地查询他的订单,我们一般都会选择用户id(uid)作为分库分表的键。虽然它方便了用户端查询,但是他给商户端和运营端带来了麻烦(不考虑其他架构的情况下),比如查询分页如何做?假如我们要执行下面这条SQL语句:select*fromt_orderorderbyidasclimit10,5;//查询第三页的5条数据为了更好地演示和理解,我们只将t_order表拆分为两个表:t_order_1、t_order_2,各自数据如下:t_order_1t_order_218

    2024-08-04
  • 详情在拆分之前,我们系统中很多列表和详情所需要的数据大部分从多张表中获取的,这个时候我们使用join操作即可。但是分库分表后,这些表可能会被分散在多个库中,这个时候我们是无法进行join操作的。虽然ShardingSphere支持跨库join,但是我们基于架构规范、安全、性能方面的考虑,我们都会强制禁止跨库join操作的。那怎么解决呢?有如下几种方案:一、全局表OR数据冗余为了避免join操作我们可以使用全局表,即一个表中存储所有需要查询的字段,这样我们就只需要查询这一张全局表了,而不需要执行join操作。比如在订单列表中,我们总是会显示用户名,这时我们可以在t_order表中冗余用户信息:C

    2024-08-04
  • 回答在分库分表中,我们大概率都会选择表的id来作为拆分的键,例如我们有一张tb_user表,它有四个字段:id、name、phone、address,我们利用tb_user的id作为拆分id。在大多数的场景下,我们都是利用id来查询tb_user表中的数据,这种情况是没有问题的。但是,有些场景我们需要根据其他不是拆分键的id来查询,例如查询name='张三',如下:select*fromtb_userwherename='张三'由于name不是拆分键,所以我们无法定位到具体的某张表上去执行这条语句,于是就会对所有的tb_user表都执行一次:对于这种情况,如果有100张表,就要查询100次,当

    2024-08-04
  • 回答目前主流的分表策略有三种:Range拆分Hash取模拆分Rang拆分和Hash取模拆分的组合Range拆分Range拆分也叫做范围分表,即根据某个字段的范围来分表,比如我们将订单表的主键order_id,按照0~500万,500~1000万的梯度来分表,如下:有时候我们也会根据时间来排序,比把按年维度拆分工单表:这种拆分的优点是它有利于扩容,比如我们按照时间来拆分,理论上是可以无限的扩容,而且不需要担心已经拆分好的数据。但是它可能会有热点问题,比如2024年的工单表,在2024年内,几乎所有的请求都会打在2024年这张分表中,而2022、2023的表几乎是已半归档的方式存在。同时,按照时间

    2024-08-04
  • 回答主要是因为MySQL使用B+树作为索引结构,当数据量较小时,B+树的的高度较低,查询时需要访问的节点较少,性能也就高些。当数据量增加到千万级别,B+树的高度就会增加,树的高度增加会导致每次查询需要访问更多的磁盘页,增加了磁盘I/O操作次数,导致查询性能下降。扩展MySQL的B+树高度计算Mysql的默认存储引擎是InnoDB,InnoDB将表的数据存储在数据页中,每个数据页的默认大小是16KB。B+树的叶子节点存的是数据,非叶子节点存的是键值+指针,索引组织表通过非叶子节点的二分查找法以及指针确定数据在哪个页中,进而再去数据页中查找所需的数据。如下:假设我们有一张表,其主键类型为bigin

    2024-08-04
  • 回答衡量是否要分库分表的关键指标是数据量是数据量和业务模块。什么时候分库?分库主要是为了解决单个数据库实例存储容量和并发处理能力的瓶颈问题。当系统的并发量过大时,数据库就会成为我们系统的瓶颈,主要是因为数据库的连接是有限的,当我们数据库的读写请求过高,导致数据库的连接数不足时,这个时候我们就需要考虑分库了。通过增加数据库实例的方式来提供更多的数据库连接,从而提高系统的性能。同时,随着我们业务系统的逐渐庞大,模块之间的耦合度越来越高,越来越复杂的时候,我们可以通过分库的方式将不同的业务模块分散到不同的数据库中,实现业务解耦。什么时候分表?分表主要是为了解决单张表数据量过大导致的查询和写入性能问题

    2024-08-04
  • 回答为什么需要分库分表?一个原因:单表、单库已经无法支撑我们的业务了。一、数据库容量限制存储容量:单个数据库的存储容量是有限的,当我们应用的数据量达到一定规模时,但是数据库就无法存储整个应用的所有数据。IO性能:当数据库的容量很大时,磁盘的读写性能会成为瓶颈,影响查询和写入的效率。二、并发处理能力限制CPU和内存限制:单库的CPU和内存资源有限,他是无法应对高并发场景的连接数限制:数据的最大连接数是有限的,如果在高并发场景下,容易造成连接数耗尽,连接数耗尽,导致数据库性能下降或者连接失败。在MySQL中我们可以通过以下命令获取最大连接数:showvariableslike'%max_conne

    2024-08-04
  • 回答当Redis内存不足时,我们可以使用如下几个策略来进行优化。一、增加物理内存这是最直观有效的方法,内存不够,那就加内存,而且现在内存便宜。二、使用集群方案使用Redis集群方案将数据分散在多个节点,这样可以有效的解决Redis内存不足问题。为什么需要RedisCluster?它解决了什么问题?三、配置maxmemory-policy和maxmemorymaxmemory用于配置Redis能够使用的最大内存容量,一旦内存使用达到这个限制,Redis就会根据maxmemory-policy指定的策略来处理内存溢出。请描述一下Redis的缓存淘汰策略Redis缓存满了怎么办?四、优化数据结构使用

    2024-05-02
  • 回答Redispipeline是Redis的一种优化计划,主要通过减少网络往返次数(RTT)来提高命令执行效率。当我们需要执行多个命令时,如果采取依次调用Redis并等待Redis响应的方式来处理,然而这种一来一回的通信模式在命令多、网络延迟显著的情况下会严重影响应用的性能。采用pipeline会有如下几个显著的优势。减少网络延迟:通过pipeline,可以将多个命令打包后再一起发送,然后接收所有命令的响应。这样只需要一次往返就可以完成多个命令的交互,减少了网络延迟。提高命令吞吐率:因为减少了网络往返次数,可以在更短的时间内执行更多的命令,从而提高了系统的整体命令处理速度。扩展Redis性能瓶

    2024-05-02
  • 回答Redis在持久化时有如下几种情况会阻塞Redis服务器。执行SAVE命令:当我们手动执行SAVE命令时,Redis会阻塞所有其他客户端的请求直到RDB快照文件创建完毕。执行BGSAVE命令:虽然BGSAVE是在一个子进程中进行的,在创建RDB快照文件过程中,主进程依然可以继续响应客户端请求。但是在执行BGSAVE命令后,主进程需要进行一次fork()操作,fork()操作在内存资源紧张或者数据集较大的情况下可能非常耗时,这期间主进程会被短暂阻塞。重写AOF文件:与BGSAVE命令一样,重写AOF文件(BGREWRITEAOF)也是发生在fork子进程过程中。配置always同步策略:在a

    2024-05-02
  • 回答Redis的高性能是我们毋庸置疑的,但是Redis也会存在阻塞的情况,导致整个系统变慢,从而影响用户体验。以下几种场景会导致Redis阻塞。一、慢查询若Redis执行一个命令的时间超过指定的阈值(slowlog-log-slower-than),则Redis会将该命令记录到慢查询日志中。由于Redis执行命令是单线程,如果执行某个命令花费时间比较长,则会阻塞其他命令的执行,如果执行大量的慢查询,则会严重阻碍Redis服务的性能。关于慢查询详情请阅读:了解Redis的慢查询吗?二、操作大key大key是指key对应的value很大。大key可能导致Redis阻塞的情况有两种:超时阻塞:由于R

    2024-05-02
  • 回答Redis的慢查询是一个记录查询执行时间超过指定阈值的查询命令的功能。它的工作原理是:当一个命令的执行时间超过slowlog-log-slower-than设置的阈值时,该命令就会被记录到慢查询日志中。日志中详细记录了慢查询发生的时间、执行时长、具体什么命令等信息。它有两个参数:slowlog-log-slower-than:慢查询阈值,单位为微秒,当命令执行时间超过这个阈值时,命令会被记录到慢查询日志中。如果该值设置为负数,则会关闭Redis慢查询功能。slowlog-max-len:慢查询日志长度,用于限制慢查询日志的大小,防止消耗过多内存。慢查询日志是一个先进先出队列,当新的慢查询产

    2024-05-02
  • 回答在Redis生产环境中我们是禁止使用KEYS命令的。因为KEYS命令它会返回所有匹配的key,Redis在执行该命令时会扫描整个键空间来匹配该模式下的所有key,这个操作的时间复杂度为O(N),N表示Redis中key的总数,这意味如果在Redis中的key非常多的情况下,KEYS命令的执行可能会非常慢。同时,由于Redis是单线程的,当它在执行KEYS命令时,它会阻塞其他所有命令的执行,这会严重拖慢整个服务的吞吐量。扩展使用Scan代替keysSCAN命令是KEYS命令的一个安全替代品。SCAN命令不像KEYS一次性锁定Redis整个key空间,而是采用逐步迭代Redis中的key,每次

    2024-05-02
  • 回答Redis通过一个叫做过期字典(可以看作是hash表)来保存数据过期的时间。当我们为一个key设置过期时间,例如SETsikesike-javaEX100,Redis会在国企字典中添加一个题目,其中key指向该键(sike),value是当前时间加上指定的秒数(转换成的Unix时间戳)。数据结构如下:typedefstructredisDb{...dict*dict;//数据库键空间,保存着数据库中所有键值对dict*expires//过期字典,保存着键的过期时间...}redisDb;我们知道Redis的过期数据删除分为两种策略:惰性删除和定时删除。惰性删除:每次访问某个key时,Red

    2024-05-02
  • 回答虽然Redis最常用的用途是作为内存使用,提供高性能的缓存解决方案。但是它的功能远不止于此,以下是Redis除了作为缓存外的常见应用场景。一、消息队列Redis支持发布/订阅模式,可以用作消息队列来处理异步任务或实现系统间的消息传递。生产者通过PUBLISH命令向特定频道发布消息。消费者使用SUBSCRIBE命令订阅一个或多个频道,订阅频道后,消费者便可以接收到所有发送到该频道的消息。详细情况请阅读:怎么使用Redis实现一个消息队列二、计数器Redis的命令是原子性的,所以它非常适合用作计数器,比如网站访问统计、在线活动的用户数统计或社交媒体中的点赞和评论计数。Redis提供了四个命令来

    2024-05-02
  • 回答RedisCluster中采用哈希槽而不是一致性hash算法,主要原因是为了简化节点的管理和数据的迁移。一、故障转移在RedisCluster中,每个Master节点都有一个或多个Slave节点,Slave节点会复制Master节点的数据。当Master节点出现故障时,Slave节点会被迅速提升Master节点,接管Master节点负责的哈希槽和数据,继续提供服务。而一致性哈希算法是没有Master-Slaver的概念的,当一个节点失败时,其负责的数据会自动转移到哈希环中的下一个节点,虽然这种方案是可行的,但是还是存在一些问题的:数据重分配代价高:数据迁移到下一个节点,原节点的数据需要在下

    2024-05-02
  • 回答可以提供服务。RedisCluster扩容、缩容的本质其实是slot的迁移。在slot迁移过程,如果客户端给当前Redis节点发送请求,则有两种情况:如果该key所对应的slot还在当前Redis节点,则直接处理,并返回处理结果。如果该key所对应的slot还在迁移过程中,则该节点返回一个ASK重定向错误,告诉客户端该请求对应的slot正在进行迁移,请去目标节点发送请求吧。客户端接受ASK响应后,则先向目标节点发送一个ASKING指令,告诉目标节点,接下来的这条指令,你必须执行,然后紧接着发送原请求。如果该key所对应的slot不在当前Redis节点,或者已经被迁移到其他目标节点了,则该节

    2024-05-02