知识点笔记

CopyOnWriteArrayList的底层原理是怎样的?

1. ⾸先CopyOnWriteArrayList内部也是⽤过数组来实现的,在向CopyOnWriteArrayList添加元素时,会复制⼀个新的数组,写操作在新数组上进⾏,读操作在原数组上进⾏
2. 并且,写操作会加锁,防止出现并发写入丢失数据的问题;
3. 写操作结束之后会把原数组指向新数组;
4. CopyOnWriteArrayList允许在写操作时来读取数据,大大提高了读的性能,因此适合读多写少的应用场景,但是CopyOnWriteArrayList会比较占内存,同时可能读到的数据不是实时最新的数据,所以不适合实时性要求很⾼的场景。

Jdk1.7到Jdk1.8 HashMap 发生了什么变化(底层)?

1. 1.7中底层是数组+链表,1.8中底层是数组+链表+红黑树,加红黑树的目的是提高HashMap插入和查询整体效率。
2. 1.7中表插入使用的是头插法,1.8中表插入使用的是尾法,因为1.8中入kevale需要判断表素个数,所以需要遍历表统计表元素个数,所以正好就直接使用尾插法。
3. 1.7中哈希算法比较复杂,存在各种右移与异或运算,1.8中进行了简化,因为复杂的哈希算法的目的就是提高性能,来提供HashMap的整体性能,而1.8中新增了红黑树,所以可以适当的简化哈希算法,节省CPU资源。

ES index、type的初衷

之前es将index、type类比于关系型数据库(例如mysql) 中database、 table, 这么考虑的目的是“方便管理数据之间的关系”。

为什么现在要移除type?

2.1在关系型数据库中table是独立的(独立存储),但es中同一个index中不同ype是存储在一个索中的(lucene的索引文件),因此不同ype中相同的字段的定义(mapping)必须一致。
2.2不同类型的“记录”存储在同一个index中,会影响lucene的压缩性能。

ArrayList和LinkedList区别?

ArrayList:基于动态数组,连续内存存储,适合下标访问(随机访问),

扩容机制:因为数组长度固定,超出长度存数据时需要新建数组,然后将老数组的数据拷贝到新数组,如果不是尾部插入数据还会涉及到元素的移动(往后复制一份,插入新元素),使用尾插法并指定初始容量可以极大提升性能、甚至超过linkedList(需要创建大量的node对象)。

LinkedList:基于链表,可以存储在分散的内存中,适合做数据插入及删除操作,

不适合查询:需要逐一遍历。
遍历LinkedList必须使用iterator不能使用for循环,因为每次for循环体内通过get(i)取得某一元素时都需要对list重新进行遍历,性能消耗极大。
另外不要试图使用indexOf等返回元素索引,并利用其进行遍历,使用indexlOf对list进行了遍历,当结果为空时会遍历整个列表。

Jdk1.7到Jdk1.8java虚拟机发生了什么变化?

1.7中存在永代,1.8中没有永久代。取而代之的是元空间,元空间所占的内存不是在虚拟机内部,而是本地内存空间,这么做的原因是,不营是永久代还是元空间,他们都是方法区的具体实现,之所以元空间所占的内存改成本地内存,官方的说法是为了和JRokit统一,不过额外还有一些原因,比如方法区所存储的类信息通常是比较难确定的,所以对于方法区的大小是比较难指定的,太小了容易出现方法区溢出,太大了又会占用了太多虚拟机的内存空间,而转移到本地内存后则不会影响虚拟机所占用的内存。

为什么wait/notify这样的代码调用一定要出现同步代码块或者同步方法中?

Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。

sleep()、wait()、join()、yield()的区别?

1.锁池
所有需要竞争同步锁的线程都会放在锁池当中,比如当前对象的锁已经被其中一个线程得到,则其他线程需要在这个锁池进行等待,当前面的线程释放同步锁后锁池中的线程去竞争同步锁,当某个线程得到后会进入就绪队列进行等待cpu资源分配。

2.等待池
当我们调用wait()方法后,线程会放到等待池当中,等待池的线程是不会去竞争同步锁。只有调用了notify()或notifyAll()后等待池的线程才会开始去竞争锁,notify()是随机从等待池选出一个线程放到锁池,而notifyAll()是将等待池的所有线程放到锁池当中。

1、sleep 是 Thread 类的静态本地方法,wait 则是 Object 类的本地方法。
2、sleep方法不会释放lock,但是wait会释放,而且会加入到等待队列中。
3、sleep方法不依赖于同步器synchronized,但是wait需要依赖synchronized关键字。
4、sleep不需要被唤醒(休眠之后推出阻塞),但是wait需要(不指定时间需要被别人中断)。
5、sleep 一般用于当前线程休眠,或者轮循暂停操作,wait 则多用于多线程之间的通信。
6、sleep 会让出 CPU 执行时间且强制上下文切换,而 wait 则不一定,wait 后可能还是有机会重新竞争到锁继续执行的。
7、yield()执行后线程直接进入就绪状态,马上释放了cpu的执行权,但是依然保留了cpu的执行资格,所以有可能cpu下次进行线程调度还会让这个线程获取到执行权继续执行
8、join()执行后线程进入阻塞状态,例如在线程B中调用线程A的join(),那线程B会进入到阻塞队列,直到线程A结束或中断线程

线程池中阻塞队列的作用? 为什么是先添加列队而不是先创建最大线程?

1、一般的队列只能保证作为一个有限长度的缓冲区,如果超出了缓冲长度,就无法保留当前的任务了,阻塞队列通过阻塞可以保留住当前想要继续入队的任务。
阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使得线程进入wait状态,释放cpu资源.
阻塞队列自带阻塞和唤醒的功能,不需要额外处理,无任务执行时,线程池利用阻塞队列的take方法挂起,从而维持核心线程的存活、不至于一直占用cpu资源。
2、在创建新线程的时候,是要获取全局锁的,这个时候其它的就得阻塞,影响了整体效率.就好比一个企业里面有10个 (core) 正式工的名额,最多招10个正式工,要是任务超过正式工人数 (task>core)的情况下,工厂领导(线程池)不是首先扩招工人,还是这10人,但是任务可以稍微积压一下,即先放到队列去(代价低)。10个正式工慢慢千,迟早会干完的,要是任务还在继续增加,超过正式工的加班忍耐极限了(队列满了),就的招外包帮忙了(注意是临时工)要是正式工加上外包还是不能完成任务,那新来的任务就会被领导拒绝了 (线程池的拒绝策略) 。

线程池中线程复用原理?

线程池将线程和任务进行解耦,线程是线程,任务是任务,摆脱了之前通过 Thread 创建线程时的一个线程必须对应一个任务的限制。
在线程池中,同一个线程可以从阻塞队列中不断获取新任务来执行,其核心原理在于线程池对 Thread 进行了封装,并不是每次执行任务都会调用 Thread.start() 来创建新线程,而是让每个线程去执行一个"循环任务”,在这个"循环任务”中不停检查是否有任务需要被执行,如果有则直接执行,也就是调用任务中的 run 方法,将 run 方法当成一个普通的方法执行,通过这种方式只使用固定的线程就将所有任务的 run 方法串联起来。

为什么匿名内部类引用方法中的变量,要定义为final类型?

首先需要知道的一点是:内部类和外部类是处于同一个级别的,内部类不会因为定义在方法中就会随着方法的执行完毕就被销毁。

这里就会产生问题:当外部类的方法结束时,局部变量就会被销毁了,但是内部类对象可能还存在(只有没有人再引用它时,才会死亡)。这里就出现了一个矛盾:内部类对象访问了一个不存在的变量。为了解决这个问题,就将局部变量复制了一份作为内部类的成员变量,这样当局部变量死亡后,内部类仍可以访问它,实际访问的是局部变量的"copy"”。这样就好像延长了局部变量的生命周期。

将局部变量复制为内部类的成员变量时,必须保证这两个变量是一样的,也就是如果我们在内部类中修改了成员变量,方法中的局部变量也得跟着改变,怎么解决问题呢?

就将局部变量设置为final,对它初始化后,我就不让你再去修改这个变量,就保证了内部类的成员变量和方法的局部变量的一致性。这实际上也是一种妥协。使得局部变量与内部类内建立的拷贝保持一致。

Mysql的MVCC机制?

MVCC机制的实现就是通过read-view机制与undo版本链比对机制,使得不同的事务会根据数据版本链对比规则读取同一条数据在版本链上的不同版本数据。

在可重复读隔离级别,当事务开启,执行任何查询sql时会生成当前事务的一致性视图read-view,该视图在事务结束之前都不会变化(如果是读已提交隔离级别在每次执行查询sql时都会重新生成),这个视图由执行查询时所有未提交事务id数组(数组里最小的id为min_id)和已创建的最大事务id(max_id)组成,事务里的任何sql查询结果需要从对应版本链里的最新数据开始逐条跟read-view做比对从而得到最终的快照结果。

HashMap的put方法的大体流程

1根据Key通过哈希算法与与运算得出数组下标

2.如果数组下标位置元素为空,则将key和value封装为Entry对象(JDK1.7中是Entry对象,JDK1.8中是Node对象)并放入该位置

3.如果数组下标位置元素不为空,则要分情况讨论
  a. 如果是JDK1.7,则先判断是否需要扩容,如果要扩容就进行扩容,如果不用扩容就生成Entry对象,并使用头插法添加到当前位置的链表中

  b.如果是JDK1.8,则会先判断当前位置上的Node的类型,看是红黑树Node,还是链表Node

    i.  如果是红黑树Node,则将key和value封装为一个红黑树节点并添加到红黑树中去,在这个过程中会判断红黑树中是否存在当前key,如果存在则更新value。

    ii. 如果此位置上的Node对象是链表节点,则将key和value封装为一个表Nde并通过尾插法插入到链表的最后位置去,因为是尾插法,所以需要遍历排表,在遍历链表的过程中会判断是否存在当前key,如果存在则更新value,当遍历完链表后,将新链表Node插入到链表中,插入到链表后,会看当前销表终节点个数。如要超过了8,那么则会将该链表转成红黑树。
    iii. 将key和value封装为Node插入到链表或红黑树中后,再判断是否需要进行扩容,如果需要就扩容,如果不需要就结束PUT方法。

Redis是单线程吗?

Redis 的单线程主要是指 Redis 的网络 IO 和键值对读写是由一个线程来完成的,这也是 Redis 对外提供键值存储服务的主要流程。但 Redis 的其他功能,比如持久化、异步删除、集群数据同步等,其实是由额外的线程执行的。

Redis数据备份策略

写crontab定时调度脚本,每小时都copy一份rdb或aof的备份到一个目录中去,仅仅保留最近48小时的备份。
每天都保留一份当日的数据备份到一个目录中去,可以保留最近1个月的备份。
每次copy备份的时候,都把太旧的备份给删了。
每天晚上将当前机器上的备份复制一份到其他机器上,以防机器损坏。

ReentrantLock中的公平锁和非公平锁的底层实现

首先不管是公平锁和非公平锁,它们的底层实现都会使用AQS来进行排队,它们的区别在于: 线程在使用locK0方法加锁时,如果是公平锁,会先检查AQS队列中是否存在线程在排队,如果有线程在排队,则当前线程也进行排队,如果是非公平锁,则不会去检查是否有线程在排队,而是直接竞争锁。
不管是公平锁还是非公平锁,一旦没竞争到锁,都会进行排队,当锁释放时,都是唤醒排在最前面的线程,所以非公平锁只是体现在了线程加锁阶段,而没有体现在线程被唤醒阶段。
另外,ReentrantLock是可重入锁,不管是公平锁还是非公平锁都是可重入的。

Redis主从工作原理

如果你为master配置了一个slave,不管这个slave是否是第一次连接上Master,它都会发送一个PSYNC命令给master请求复制数据。

master收到PSYNC命令后,会在后台进行数据持久化通过bgsave生成最新的rdb快照文件,持久化期间,master会继续接收客户端的请求,它会把这些可能修改数据集的请求缓存在内存中。当持久化进行完毕以后,master会把这份rdb文件数据集发送给slave,slave会把接收到的数据进行持久化生成rdb,然后再加载到内存中。然后,master再将之前缓存在内存中的命令发送给slave。

当master与slave之间的连接由于某些原因而断开时,slave能够自动重连Master,如果master收到了多个slave并发连接请求,它只会进行一次持久化,而不是一个连接一次,然后再把这一份持久化的数据发送给多个并发连接的slave。

Sychronized和ReentrantLock的区别

1.sychronized是一个关键字,ReentrantLock是一个类;
2.sychronized会自动的加锁与释放锁,ReentrantLock需要程序员手动加锁与释放锁3.sychronized的底层是JVM层面的锁,ReentrantLock是API层面的锁;
4.sychronized是非公平锁,ReentrantLock可以选择公平锁或非公平锁;
5.sychronized锁的是对象,锁信息保存在对象头中,ReentrantLock通过代码中int类型的state标识来标识锁的状态;

6.sychronized底层有一个锁升级的过程。

 Redis主从工作原理

如果你为master配置了一个slave,不管这个slave是否是第一次连接上Master,它都会发送一个PSYNC命令给master请求复制数据。
master收到PSYNC命令后,会在后台进行数据持久化通过bgsave生成最新的rdb快照文件,持久化期间,master会继续接收客户端的请求,它会把这些可能修改数据集的请求缓存在内存中。当持久化进行完毕以后,master会把这份rdb文件数据集发送给slave,slave会把接收到的数据进行持久化生成rdb,然后再加载到内存中。然后,master再将之前缓存在内存中的命令发送给slave。
当master与slave之间的连接由于某些原因而断开时,slave能够自动重连Master,如果master收到了多个slave并发连接请求,它只会进行一次持久化,而不是一个连接一次,然后再把这一份持久化的数据发送给多个并发连接的slave。

预防和解决缓存雪崩问题, 可以从以下三个方面进行着手。

1) 保证缓存层服务高可用性,比如使用Redis Sentinel或Redis Cluster。
2) 依赖隔离组件为后端限流熔断并降级。比如使用Sentinel或Hystrix限流降级组件。
比如服务降级,我们可以针对不同的数据采取不同的处理方式。当业务应用访问的是非核心数据(例如电商商品属性,用户信息等)时,暂时停止从缓存中查询这些数据,而是直接返回预定义的默认降级信息、空值或是错误提示信息;当业务应用访问的是核心数据(例如电商商品库存)时,仍然允许查询缓存,如果缓存缺失,也可以继续通过数据库读取。
3) 提前演练。 在项目上线前, 演练缓存层宕掉后, 应用以及后端的负载情况以及可能出现的问题, 在此基础上做一些预案设定。

ThreadLocal的底层原理

1.ThreadLocal是Java中所提供的线程本地存储机制,可以利用该机制将数据缓存在某个线程内部该线程可以在任意时刻、任意方法中获取缓存的数据2.ThreadLocal底层是通过ThreadLocalMap来实现的,每个Thread对象(注意不是ThreadLocal对象)中都存在一个ThreadLocalMap,Map的key为ThreadLocal对象,Map的value为需要缓存的
值3.如果在线程池中使用ThreadLocal会造成内存泄漏,因为当ThreadLocal对象使用完之后,应该要把设置的key,value,也就是Entry对象进行回收,但线程池中的线程不会回收,而线程对象是通过强引用指向ThreadLocalMap,ThreadLocalMap也是通过强引用指向Entry对象,线程不被回收,Entry对象也就不会被回收,从而出现内存泄漏,解决办法是,在使用了ThreadLocal对象之后,手动调用ThreadLocal的remove方法,手动清楚Entry对象ThreadLocal经典的应用场景就是连接管理(一个线程持有一个连接,该连接对象可以在不同的方4法之间进行传递,线程之间不共享同一个连接)。

Dubbo会默认创建一个自适应扩展点实例,后续,在A接口的代理对象被真正用到时,才会结合URL信息找到真正的A接口对应的扩展点实例进行调用。

线程之间如何进行通讯的

1.线程之间可以通过共享内存或基于网络来进行通信
2.如果是通过共享内存来进行通信,则需要考虑并发问题,什么时候阻塞,什么时候唤醒
3.像ava中的wait()、notify()就是阻塞和唤醒
4.通过网络就比较简单了,通过网络连接将通信数据发送给对方,当然也要考虑到并发问题,处理方式就是加锁等方式。

谈谈你对AQS的理解。AQS如何实现可重入锁?

1、AQS是一个JAVA线程同步的框架。是JDK中很多锁工具的核心实现框架。
2、在AQS中,维护了一个信号量state和一个线程组成的双向链表队列。其中,这个线程队列,就是用来给线程排队的,而state就像是一个红绿灯,用来控制线程排队或者放行的。 在不同的场景下,有不用的意义。
3、在可重入锁这个场景下,state就用来表示加锁的次数。0标识无锁,每加一次锁,state就加1。释放锁state就减1。

Java的内存结构,堆分为哪几部分,默认年龄多大进入老年代

1.年轻代
a.Eden区(8)
b.From Survivor区(1)
cTo Survivor区(1)
2.老年代
默认对象的年龄达到15席,就会进入老年代。

你们项目如何排查JVM问题

对于还在正常运行的系统:
1可以使用imap来查看JVM中各个区域的使用情况
2.可以通过istack来查看线程的运行情况,比如哪些线程阻塞、是否出现了死锁
3.可以通过jstat命令来查看垃圾回收的情况,特别是fullgc,如果发现fullgc比较频繁,那么就得进行调优了
4.通过各个命令的结果,或者ivisualvm等工具来进行分析
5.首先,初步猜测频繁发送fullgc的原因,如果频繁发生fullgc但是又一直没有出现内存溢出,那么表示fulgc实际上是回收了很多对象了,所以这些对象最好能在younggc过程中就直接回收掉,避免这些对象进入到老年代,对于这种情况,就要考虑这些存活时间不长的对象是不是比较大导致年轻代放不下,直接进入到了老年代,尝试加大年轻代的大小,如果改完之后,fullgc减少,则证明修改有效6.同时,还可以找到占用CPU最多的线程,定位到具体的方法,优化这个方法的执行,看是否能避免某些对象的创建,从而节省内存
对于已经发生了OOM的系统:
1.一般生产系统中都会设置当系统发生了00M时,生成当时的dump文件(-XX:+HeapDumpOnOutOfMemoryError
XX:HeapDumpPath=/usr/local/base)
2.我们可以利用isisuaivm等工具来分析dump文件3.根据dump文件找到异常的实例对象,和异常的线程(占用CPU高),定位到具体的代码4.然后再进行详细的分析和调试
总之,调优不是一蹴而就的,需要分析、推理、实践、总结、再分析,最终定位到具体的问题。

MySQL的锁有哪些? 什么是间隙锁?

从锁的粒度来区分
1、行锁: 加锁粒度小,但是加锁资源开销比较大。 innDB支持。

  共享锁: 读锁。多个事务可以对同一个数据共享同一把锁。持有锁的事务都可以访问数据,但是只能读不能修改。selectXXX LOCK IN SHARE MODE.
  排他锁:写锁。只有一个事务能够获得排他锁,其他事务都不能获取该行的锁。lnnoDB会对update delete insert语句自动添加排他锁。SELECT xxx FOR UPDATE。

2、表锁: 加锁粒度大,加锁资源开销比较小。MylSAM和InnoDB都支持。
  表共享读锁
  表排他写锁
  意向锁: 是InnoDB自动添加的一种锁,不需要用户干预
3、全局锁: Flush tables with read lock 。加锁之后整个数据库实例都处于只读状态。所有的数据变更操作都会被挂起。一般用于全库备份的时候。

3、全局锁: Flush tables with read lock 。加锁之后整个数据库实例都处于只读状态。所有的数据变更操作都会被挂起一般用于全库备份的时候。
常见的锁算法: user: userid ( 1,4,9) update user set xxx where userid=5; REPEATABLE READ 间锁锁住(5,9)
1、记录锁: 锁一条具体的数据
2、间隙锁: RR隔离级别下,会加间隙锁。锁一定的范围,而不锁具体的记录。是为了防止产生幻读。(-xx,1)(1,4)4,9)(9,xxx)
3、Next-key:间隙锁+右记录锁。(-xx,1](1,4](4,9] (9,xxx)

Mysql优化总结

1、MySQL支持两种方式的排序filesort和index,Using index是指MySQL扫描索引本身完成排序。index效率高,filesort效率低。
2、order by满足两种情况会使用Using index。
  1)order by语句使用索引最左前列。
  2)使用where子句与order by子句条件列组合满足索引最左前列。
3、尽量在索引列上完成排序,遵循索引建立(索引创建的顺序) 时的最左前缀法则
4、如果order by的条件不在索引列上,就会产生Using filesort。
5、能用覆盖索引尽量用覆盖索引
6、group by与order by很类似,其实质是先排序后分组,遵照索引创建顺序的最左前缀法则。对于groupby的优化如果不需要排序的可以加上order by null禁止排序。注意,where高于having,能写在where中的限定条件就不要去having限定了。

被驱动表的关联字段没索引为什么要选择使用BNL算法而不使用 Nested-Loop Join 呢?

如果上面第二条sql使用Nested-Loop Join,那么扫描行数为 10010000= 100万次,这个是磁盘扫描很显然,用BNL磁盘扫描次数少很多,相比于磁盘扫描,BNL的内存计算会快得多因此MySQL对于被驱动表的关联字段没索引的关联查询,一般都会使用 BNL 算法。如果有索引一般选择 NLJ算法有索引的情况下NLJ算法比BNL算法性能更高.

开发时事务的隔离级别该怎么设置?

数据库的事务隔离越严格,并发副作用越小,但付出的代价也就越大,因为事务隔离实质上就是使事务在一定程度上“串行化”进行这显然与“并发”是矛盾的。
同时 不同的应用对读一致性和事务隔离程度的要求也是不同的,比如许多应用对“不可重复读”和“幻读”并不敏感可能更关心数据并发访问的能力。
常看当前数据库的事务隔离级别:show variables liketxisolation'设置事务隔离级别:settxisolation=REPEATABLE-READMysql默认的事务隔离级别是可重复读,用Spring开发程序时,如果不设置隔离级别默认用Mysql设置的隔离级别,如果Spring设置了就用已经设置的隔离级别。

对无索引的列,锁机制是怎么样的?

无索引行锁会升级为表锁(RR级别会升级为表锁,RC级别不会升级为表锁)
锁主要是加在索引上,如果对非索引字段更新,行锁可能会变表锁
session1 执行: update account set balance = 800 where name ='lilei

session2对该表任一行操作都会阻塞住
lnnoDB的行锁是针对索引加的锁,不是针对记录加的锁。并且该索引不能失效,否则都会从行锁升级为表锁。

可以怎么手动添加行级锁?

锁定某一行还可以用lock in share mode(共享锁)和for update(排它锁),例如: select* from test innodb lock where a=2for update;这样其他session只能读这行数据,修改则会被阻塞,直到锁定行的session提交。

为什么Mysql不能直接更新磁盘上的数据而且设置这么一套复杂的机制来执行SQL了?

因为来一个请求就直接对磁盘文件进行随机读写,然后更新磁盘文件里的数据性能可能相当差因为磁盘随机读写的性能是非常差的,所以直接更新磁盘文件是不能让数据库抗住很高并发的。Mysg这套机制看起来复朵,但它可以保证每个更新请求都是更新内存BufferPool,然后顺序写日志文件,同时还能保证各种异常情况下的数据一致性。
更新内存的性能是极高的,然后顺序写磁盘上的日志文件的性能也是非常高的,要远高于随机读写磁盘文件正是通过这套机制,才能让我们的MVSQL数据库在较高配置的机器上每秒可以抗下几干甚至上万的读写请求。

Mysql慢查询该如何优化?

1.检查是否走了索引,如果没有则优化SQL利用索引。

2.检查所利用的索引,是否是最优索引。

3.检查所查字段是否都是必须的,是否查询了过多字段,查出了多余数据.

4.检查表中数据是否过多,是否应该进行分库分表了。

5,检查数据库实例所在机器的性能配置,是否太低,是否可以适当增加资源。

MySQL有哪几种数据存储引擎? 有什么区别?

MySQL中通过show ENGINES指令可以看到所有支持的数据库存储引擎。最为常用的就是MylSAM 和InnoDB 两种。
MylSAM和InnDB的区别:
1、存储文件。MyISAM每个表有两个文件。MYD和MYISAM文件。MYD是数据文件。MY是索文件。而lnnDB每个表只有一个文件,idb。
2、InnoDB支持事务,支持行级锁,支持外键。
3、InnoDB支持XA事务。
4、InnoDB支持savePoints。

mysql主从同步原理

mysql主从同步的过程:
Mysql的主从复制中主要有三个线程: master (binlog dump thread)、sTave(I/0 thread 、SQLthread),Master一条线程和Slave中的两条线程。
·主节点 binlog,主从复制的基础是主库记录数据库的所有变更记录到 binlog。binlog 是数据库服务器启动的那一刻起,保存所有修改数据库结构或内容的一个文件。
主节点logdump线程,当binlog有变动时,log dump 线程读取其内容并发送给从节点。
从节点I/O线程接收 binog 内容,并将其写入到relay log文件中。
从节点的SQL线程读取relaylog 文件内容对数据更新进行重放,最终保证主从数据库的一致性。
注:主从节点使用 binglog文件+ position 偏移量来定位主从同步的位置,从节点会保存其已接收到的偏移量如果从节点发生宕机重启,则会自动从 position的位置发起同步。

由于mysl默认的复制方式是异步的,主库把日志发送给从库后不关心从库是否已经处理,这样会产生一个问题就是假设主库挂了,从库处理失败了,这时候从库升为主库后,日志就丢失了。由此产生两个概念。
全同步复制
主库写入binlog后强制同步日志到从库,所有的从库都执行完成后才返回给客户端,但是很显然这个方式的话性能会受到严重影响。
半同步复制
和全同步不同的是,半同步复制的逻辑是这样,从库写入日志成功后返回ACK确认给主库,主库收到至少一个从库的确认就认为写操作完成。

存储拆分后如何解决唯一主键问题

UUID:简单、性能好,没有顺序,没有业务含义,存在泄漏mac地址的风险

数据库主键:实现简单,单调递增,具有一定的业务可读性,强依赖db、存在性能瓶颈,存在暴露业务信息的风险
redis,mongodb,zk等中间件: 增加了系统的复杂度和稳定性

雪花算法

海量数据下,如何快速查找一条记录?

1、使用布降过滤器,快速过滤不存在的记录。
  使用Redis的bitmap结构来实现布隆过滤器。
2、在Redis中建立数据缓存。 - 将我们对Redis使用场景的理解尽量表达出来
  以普通字符串的形式来存储,(userld -> user,json)以一个hash来存储一条记录(userld key-> username field->userAge->)。 以一个整的hash来存储所有的数据,Userlnfo-> field就用userld ,value就用user.jison,一个hash最多能支持2^32-1(40多个亿)个键值对。
  缓存击穿:对不存在的数据也建立key。这些key都是经过布隆过滤器过滤的,所以一般不会太多。
  缓存过期:将热点数据设置成永不过期,定期重建缓存。 使用分布式锁重建缓存。
3、查询优化。
  按槽位分配数据
  自己实现槽位计算,找到记录应该分配在哪台机器上,然后直接去目标机器上找。

简述mysql中索引类型及对数据库的性能的影响

普通索引:允许被索引的数据列包含重复的值。
唯一索:可以保证数据记录的唯一性主键:是一种特殊的唯一索引,在一张表中只能定义一个主键索引,主键用于唯一标识一条记录,使用关键字PRIMARYKEY来创建
联合索引|:索可以覆盖多个数据列,如像INDEX(columnA,columnB)索引。
全文索引: 通过建立倒排索引,可以极大的提升检索效率,解决判断字段是否包含的问题,是目前搜索引擎使用的种关键技术。可以通过ALTER TABLE table_name ADD FULLTEXT(column);创建全文索引。

索引可以极大的提高数据的查询速度。
通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能但是会降低插入、删除、更新表的速度,因为在执行这些写操作时,还要操作索引文件
索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚族索引,那么需要的空间就会更大,如果非聚集索引很多,一旦聚集索引改变,那么所有非聚集索引都会跟着变。

常用的分片策略有

取余取模: 优点 均存放数据,缺点 扩容非常麻烦

按照范围分片 : 比较好扩容, 数据分布不够均匀

按照时间分片 : 比较容易将热点数据区分出来。

按照枚举值分片 : 例如按地区分片

按照目标字段前缀指定进行分区:自定义业务规则分片

Redis集群原理分析

Redis Cluster 将所有数据划分为16384个slots(槽位),每个节点负责其中一部分槽位。槽位的信息存储于每个节点中。当Redis Cluster 的客户端来连接集群时,它也会得到一份集群的槽位配置信息并将其缓存在客户端本地。这样当客户端要查找某个kev时,可以直接定位到目标节点。同时因为槽位的信息可能会存在客户端与服务器不一致的情况,还需要纠正机制来实现槽位信息的校验调整。
槽位定位算法
Cluster 默认会对 key 值使用crc16 算法进行 hash 得到一个整数值,然后用这个整数值对16384 进行取模来得到具体槽位.HASH SLOT = CRC16(key) mod 16384。

跳转重定位

当客户端向一个错误的节点发出了指令,该节点会发现指令的 key 所在的槽位并不归自己管理,这时它会向客户端发送一个特殊的跳转指令携带目标操作的节点地址,告诉客户端去连这个节点去获取数据。客户端收到指令后除了跳转到正确的节点上去操作,还会同步更新纠正本地的槽位映射表缓存,后续所有key将使用新的槽位映射表。

网络抖动

真实世界的机房网络往往并不是风平浪静的,它们经常会发生各种各样的小问题。比如网络抖动就是非常常见的一种现象,突然之间部分连接变得不可访问,然后很快又恢复正常。
为解决这种问题,Redis Cluster 提供了一种选项cluster-node-timeout,表示当某个节点持续timeout的时间失联时,才可以认定该节点出现故障,需要进行主从切换。如果没有这个选项,网络抖动会导致主从频繁切换(数据的重新复制)。

Redis集群选举原理分析

当slave发现自己的master变为FAIL状态时,便尝试进行Failover,以期成为新的master。由于挂掉的master可能会有多个slave,从而存在多个slave竞争成为master节点的过程,其过程如下:
1、slave发现自己的master变为FAIL
2、将自己记录的集群currentEpoch加1,并广播FAILOVER AUTH REQUEST信息

3、其他节点收到该信息,只有master响应,判断请求者的合法性,并发送FALOVER AUTH ACK,对每一个epoch只发送一次ack

4、尝试failover的slave收集master返回的FAILOVERAUTHACK
5.slave收到超过半数master的ack后变成新Master(这里解释了集群为什么至少需要三个主节点,如果只有两个,当其中一个挂了,只剩个主节点是不能选举成功的)
6.slave广播Pong消息通知其他集群节点。

热点缓存key重建优化

开发人员使用“缓存+过期时间”的策略既可以加速数据读写,又保证数据的定期更新,这种模式基本能够满足绝大部分需求。但是有两个问题如果同时出现,可能就会对应用造成致命的危害:
当前key是一个热点key (例如一个热门的娱乐新闻),并发量非常大。重建缓存不能在短时间完成,可能是一个复杂计算,例如复杂的SQL、多次I0、多个依赖等
在缓存失效的瞬间,有大量线程来重建缓存,造成后端负载加大,甚至可能会让应用崩溃。要解决这个问题主要就是要避免大量线程同时重建缓存。
我们可以利用互斥锁解决,此方法只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完,重新从缓存获取数据即可。

redis分布式锁实现

setnx+setex:存在设置超时时间失败的情况,导致死锁set(key;value,nx,px):将setnx+setex变成原子操作问题:
任务超时,锁自动释放,导致并发问题。使用redisson解决(看门狗监听,自动续期)
以及加锁和释放锁不是同一个线程的问题。在value中存入uuid(线程唯一标识),删除锁时判断该标识(使用lua保证原子操作)
不可重入,使用redisson解湖(实现机制类似AQS,计数)
异步复制可能造成锁丢失,使用redLock解决
1.顺序向五个节点请求加锁
2.根据一定的超时时间来推断是不是跳过该节点
3.三个节点加锁成功并且花费时间小于锁的有效期
4.认定加锁成功。

bean容器的启动阶段

读取bean的配置将bean元素分别转换成一个BeanDefinition对象
然后通过BeanDefinitionRegistry将这些bean注册到beanFactory中,保存在它的一个ConcurrentHashMap中将BeanDefinition注册到了beanFactory之后,在这里Spring为我们提供了一个扩展的切口,允许我们通过实现接口BeanFactoryPostProcessor 在此处来插入我们定义的代码典型的例子就是: PropertyPlaceholderConfigurer,我们一般在配置数据库的dataSource时使用到的占位符的值,就是它注入进去的。

Redis线程模型、单线程快的原因

Redis基于Reactor模式开发了网络事件处理器,这个处理器叫做文件事件处理器 file event handler。这个文件事件处理器,它是单线程的,所以 Redis 才叫做单线程的模型,它采用10多路复用机制来同时监听多个Socket,根据Socket上的事件类型来选择对应的事件处理器来外理这个事件。可以实现高性能的网络通信模型,又可以跟内部其他单线程的模块进行对接,保证了 Redis 内部的线程模型的简单性
文件事件处理器的结构包含4个部分: 多个Socket、IO多路复用程序、文件事件分派器以及事件处理器(命令请求处理器、命令回复处理器、连接应答处理器等)。

多个 Socket 可能并发的产生不同的操作,每个操作对应不同的文件事件,但是IO多路复用程序会监听多个Socket,会将 Socket放入一个队列中排队,每次从队列中取出一个Socket 给事件分派器,事件分派器把 Socket给对应的事件处理器。
然后一个Socket 的事件处理完之后,10多路复用程序才会将队列中的下一个Soket 给事件分派器。文件事件分派器会根据每个socket 当前产生的事件,来选择对应的事件处理器来处理。
单线程快的原因:
1)纯内存操作
2)核心是基于非阻塞的IO多路复用机制
3)单线程反而避免了多线程的频繁上下文切换带来的性能问题

变同步为异步

再一种对于计算的优化,就是变同步为异步,这通常涉及编程模型的改变。同步方式请求会一直阻塞,直到有成功,或者失败结果的返回。虽然它的编程模型简单,但应对突发的、时间段倾斜的流量,问题就特别大,请求很容易失败。
异步操作可以方便地支持横向扩容,也可以缓解瞬时压力,使请求变得平滑。同步请求,就像拳头打在钢板上;异步请求,就像拳头打在海绵上。你可以想象一下这个过程,后者肯定是富有弹性的,体验更加友好。

 线程池中阻塞队列的作用? 为什么是先添加列队而不是先创建最大线程?LC

1、一般的队列只能保证作为一个有限长度的缓冲区,如果超出了缓冲长度,就无法保留当前的任务了,阻塞队列通过阻塞可以保留住当前想要继续入队的任务。
阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使得线程进入wait状态,释放cpu资源.
阻塞队列自带阻塞和唤醒的功能,不需要额外处理,无任务执行时,线程池利用阻塞队列的take方法挂起,从而维持核心线程的存活、不至于一直占用cpu资源
2、在创建新线程的时候,是要获取全局锁的,这个时候其它的就得阻塞,影响了整体效率.就好比一个企业里面有10个 (core) 正式工的名额,最多招10个正式工,要是任务超过正式工人数 (task>core)的情况下,工厂领导(线程池)不是首先扩招工人,还是这10人,但是任务可以稍微积压一下,即先放到队列去(代价低)。10个正式工慢慢千,迟早会干完的,要是任务还在继续增加,超过正式工的加班忍耐极限了(队列满了),就的招外包帮忙了(注意是临时工)要是正式工加上外包还是不能完成任务,那新来的任务就会被领导拒绝了 (线程池的拒绝策略) 。

Java死锁如何避免?

造成死锁的几个原因:
1.一个资源每次只能被一个线程使用
2.一个线程在阻塞等待某个资源时,不释放已占有资源
3.一个线程已经获得的资源,在未使用完之前,不能被强行剥夺
4.若干线程形成头屋相接的循环等待资源关系
这是造成死锁必须要达到的4个条件,如果要游免死锁,只需要不满足其中某一个条件即可。而其中前3个条件是作为锁要符合的条件,所以要游免死微就需要打喷算4个条件不出现循环等待锁的关系。
在开发过程中
1.要注意加锁顺序,保证每个线程按同样的顺序进行加锁。
2.要注意加锁时限,可以针对所设置一个超时时间。
3.要注意死锁检查,这是一种预防机制,确保在第一时间发现死锁并进行解决。

如果你提交任务时,线程池队列已满,这时会发生什么

1.如果使用的无界队列,那么可以继续提交任务时没关系的
2.如果使用的有界队列,提交任务时,如果队列满了,如果核心线程数没有达到上限,那么则增加线理,如果线程数已经达到了最大值,则使用拒绝策略进行拒绝。

volatile关键字,他是如何保证可见性,有序性

1.对于加了volatle关键字的成员变量,在对这个变量进行修改时,会成接将CPU高级存中的数据写回到主内存,对这个变量的读取也会直接从主内存中读取,从而保证了可见性
2.在对volatile修饰的成员变量进行读写时,会插入内存屏障,而内存屏障可以达到禁止重排序的效果,从而可以保证有序性。

sychronized的自旋锁、偏向锁、轻量级锁、重量级锁,分别介绍和联系
1.偏向锁:在锁对象的对象头中记录一下当前获取到该锁的线程ID,该线程下次如果又来获取该锁就可以直接获取到了

2.轻量级锁:由偏向铁升级而来,当一个线程获取到后,此时这把是偏向锁,此时如果有第二个线程来竞争锁,偏向锁就会升级为轻量级锁,之所以叫轻量级锁,是为了和重量级锁区分开来,轻量级锁底层是通过自旋来实现的,并不会阻塞线程。

3.如果自旋次数过多仍然没有获取到锁,则会升级为重量级锁,重最级锁会导致线程阻塞。

4.自旋锁:自旋锁就是线程在获取锁的过程中,不会去阻塞线程,也就无所得唤醒线程,阻塞和唤醒这两个步骤都是需要操作系统去进行的,比消时间,自旋锁是线程通过CAS获取预期的一个标记,如果没有获取到,则继续循环获取,如果获取到了则表示获取到了锁,这个过程线程一直在运行中,相对而言没有使用太多的操作系统资源,比较轻量。

简述线程池原理,FixedThreadPool用的阻塞队列是什么

线程池内部是通过队列+线程实现的,当我们利用线程池执行任务时:

1.如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务

2.如果此时线程池中的数量等于corePoolsize,但是缓冲队列workQueue未满,那么任务被放入缓冲队列。

3.如果此时线程池中的数量大于等于corePoolsize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。

4.如果此时线程池中的数量大于corePoolsize,缓冲队列workueue满,并线程池中的数量等于maximumPoolSize。那通过 handler所指定的路来外理此务。

5.当线程池中的线程数量大于corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,这样,线程池可以动态的调整池中的线程数。

FixedThreadPool代表定长线程池,底层用的LinkedBlockingQueue,表示无界的阻塞队列。

对寸护线程的埋解
守护线程:为所有非守护线程提供服务的线程,任何一个守护线程都是整个IVM中所有非守护线程的保姆

守护线程类似于整个进程的一个默默无闻的小喽喽,它的生死无关重要,它却依赖整个进程而运行,哪天其他线程结束了,没有要执行的了,程序就结束了,理都没理守护线程,就把它中断了;

注意:由于守护线程的终止是自身无法控制的,因此千万不要把10、File等重要操作逻辑分配给它,因为它不靠谱,

守护线程的作用是什么?举例,GC垃圾回收线程: 就是一个经典的守护线程,当我们的程序中不再有任何运行的Thread,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是IVM上仅剩的线程时,垃圾回收线程会自动离开。它始终在低级别的状态中运行,用于实时监控和管理系统中的可回收资源。

应用场景: (1)来为其它线程提供服务支持的情况: (2)或者在任何情况下,程序结束时,这个线程必须正常目立刻关闭,就可以作为守护线程来使用,反之,如果一个正在执行某个操作的线程必须要正确地关闭掉否则就会出现不好的后果的话,那么这个线程就不能是守护线程,而是用户线程。通常都是些关键的事务,比方说,数据库录入或者更新,这些操作都是不能中断的。thread.setDaemon(true)必须在thread.start)之前设置,否则会跑出一个legalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。
在Daemon线程中产生的新线程也是Daemon的。
守护线程不能用于去访问固有资源,比如读写操作或者计算逻辑。因为它会在任何时候甚至在一个操作的中间发生中断。

为什么用线程池?解释下线程池参数?

1、降低资源消耗;提高线程利用率,降低创建和销毁线程的消耗。
2、提高响应速度,任务来了,直接有线程可用可执行,而不是先创建线程,再执行。
3、提高线程的可管理性;线程是稀缺资源,使用线程池可以统一分配调优监控。
corePoo1size代表核心线程数,也就是正常情况下创建工作的统程数,这些线程创建后并不会消除,而是一种常驻线程
maxinumPoo1size代表的是最大线程数,它与核心线程数相对应,表示最大允许被创建的线程数,比如当前任务较多,将核心线程数都用完了,还无法满足需求时,此时就会创建新的线程,但是线程池内线程总数不会超过最大线程数
keepAliveTime、unit 表示超出核心线程数之外的线程的空闲存活时间,也就是核心线程不会消除,但是超出核心线程数的部分线程如果空闲一定的时间则会被消除,我们可以通过 setkeepAliveime 来设置空闲
workQueue 用来存放待执行的任务,假设我们现在核心线程都已被使用,还有任务进来则全部放入队列,直到整个队列被放满但任务还再持续进入则会开始创建新的线程
ThreadFactory 实际上是一个线程工厂,用来生产线程执行任务。我们可以选择使用默认的创建工厂,产生的线程都在同一个组内,拥有相同的优先级,且都不是守护线程。当然我们也可以选择自定义线程工厂,一般我们会根据业务来制定不同的线程工厂
Handler任务拒绝策略,有两种情况,第一种是当我们调用shutdown 等方法关闭线程池后,这时候即使线程池内部还有没执行完的任务正在执行,但是由于线程池已经关闭,我们再继续想线程池提交任务就会遭到拒绝。另一种情况就是当达到最大线程数,线程池已经没有能力继续处理新提交的任务时,这时也就拒绝。

线程池中阻塞队列的作用? 为什么是先添加列队而不是先创建最大线程?

1、一般的队列只能保证作为一个有限长度的缓冲区,如果超出了缓冲长度,就无法保留当前的任务了,阻塞队列通过阻塞可以保留住当前想要继续入队的任务。
阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使得线程进入wait状态,释放pu资源.
阻塞队列自带阻塞和唤醒的功能,不需要额外处理,无任务执行时,线程池利用阻塞队列的take方法挂起,从而维持核心线程的存活、不至于一直占用cpu资源
2、在创建新线程的时候,是要获取全局锁的,这个时候其它的就得阻塞,影响了整体效率.就好比一个企业里面有10个(core) 正式工的名额,最多招10个正式工,要是任务超过正式工人数(task>core)的情况下,工厂领导(线程池)不是首先扩招工人,还是这10人,但是任务可以稍微积压一下,即先放到队列去(代价低)。10个正式工慢慢千,迟早会干完的,要是任务还在继续增加,超过正式工的加班忍耐极限了(队列满了),就的招外包帮忙了(注意是临时工)要是正式工加上外包还是不能完成任务,那新来的任务就会被领导拒绝了 (线程池的拒绝策略)。

线程的生命周期?线程有几种状态
1.线程通常有五种状态,创建,就绪,运行、阻塞和死亡状态
2.阻塞的情况又分为三种:
(1)、等待阻塞:运行的线程执行wait方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify或notifyAll方法才能被唤醒,wait是obiect类的方

(2)、同步阻塞: 运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则VM会把该线程放入”锁池"中。
(3)、其他阻塞:运行的线程执行sleep或ioin方法,或者发出了/O请求时,JVM会把该程置为阻塞状态。当sleep状态超时、ioin等待线程终止或者超时、或者/处理完毕时,线程重新转入就绪状态。sleep是Thread类的方法
1.新建状态(New):新创建了一个线程对象
2就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3.运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。

4阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态
5.死亡状态(Dead):线程执行完了或者因异常退出了run方法,该线程结束生命周期。

GC如何判断对象可以被回收

引用计数法:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收
可达性分析法:从GC Roots 开始向下搜索,搜索所走过的路径称为用链。当一个对象到GC Roots 没有任何引用链相连时,则证明此对象是不可用的,那么虚拟机就判断是可回收对象。
引用计数法,可能会出现A引用了B,B又席了A,这时候就算他们都不再使用了,但因为相互引用计数器=1永远无法被回收。
GCRoots的对象有:
。虚拟机栈(栈顿中的本地变量表)中引用的对象
。方法区中类静态属性引用的对象
。方法区中常量引用的对象
。本地方法栈中INI(即一般说的Native方法)引|用的对象

JAVA类加载的全过程是怎样的? 什么是双亲委派机制? 有什么作用?一个对象从加载到JVM,再到被GC清除,都经历了什么过程?

加载:在硬盘上查找并通过10读入字节码文件,使用到类时才会加载,例如调用类的main0方法,new对象等等,在加载阶段会在内存中生成一个代表这个类的
java.lang.Class 对象,作为方法区这个类的各种数据的访问入口
验证:校验字节码文件的正确性
准备:给类的静态变量分配内存,并赋予默认值
解折:将符号引用替换为直接引用,该阶段会把一些静态方法符号引用,比如main方法替换为指向数据所存内存的指针或有柄等(直接引用),这是所谓的静态链接过程(类加载期间完成),动态链接是在程序运行期间完成的将符号引用替换为直接引用
初始化:对类的静态变量初始化为指定的值,执行静态代码块

Java类加载器

]DK自带有三个类加载器: bootstrap ClassLoader、ExtClassLoader、AppClassLoader。BootStrapClassLoader是ExtClassLoader的父类加载器,默认负责加载物AVA_HOME%lib下的iar包和class文件。
ExtClassLoader是AppClassLoader的父类加载器,负责加载%AVA HOME%/lib/ext文件夹下的iar包和class类

AppClassLoader是自定义类加载器的父类,负责加载classpath下的类文件。

继承ClassLoader实现自定义类加载器。

漏标-读写屏障

漏标会导致被引用的对象被当成垃圾误删除,这是严重bug,必须解决,有两种解决方案: 增量更新(ncrementalUpdate) 和原始快照 (Snapshot At The Beginning,SATB) 。增量更新就是当黑色对象插入新的指向白色对象的引用关系时,就将这个新插入的引用记录下来,等并发扫描结束之后,再将这些记录过的引用关系中的黑色对象为根,重新扫描一次。这可以简化理解为,黑色对象一旦新插入了指向白色对象的引用之后,它就变回灰色对象了。
原始快照就是当灰色对象要删除指向白色对象的引用关系时,就将这个要删除的引用记录下来,在并发扫描结束之后再将这些记录过的引用关系中的灰色对象为根,重新扫描一次,这样就能扫描到白色的对象,将白色对象直接标记为黑色(目的就是让这种对象在本轮qc清理中能存活下来,待下一轮c的时候重新扫描,这个对象也有可能是浮动垃圾)

以上无论是对引用关系记录的插入还是删除,虚拟机的记录操作都是通过写屏障实现的。

JVM有哪些垃圾回收器? 他们都是怎么工作的? 什么是STW? 他都发生在哪些阶段?什么是三色标记? 如何解决错标记和漏标记的问题?为什么要设计这么多的垃圾回收器?->内存逐渐变大

sTw:Stop-The-World。是在垃圾回收算法执行过程当中,需要将JVM内存冻结的一种状态。在STW状态下,JAVA的所有线程都是停止执行的-GC线程除外,native方法可以执行,但是,不能与JVM交互。GC各种算法优化的重点,就是减少STW,同时这也是JVM调优的重点。

为什么G1用SATB?CMS用增量更新?

我的理解:SATB相对增量更新效率会高(当然SATB可能造成更多的浮动垃圾),因为不需要在重新标记阶段再次深度扫描被删除引用对象,而CMS对增量引用的根对象会做深度扫描,G1因为很多对象都位于不同的region,CMS就一块老年代区域,重新深度扫描对象的话G1的代价会比CMS高,所以G1选择SATB不深度扫描对象,只是简单标记,等到下一轮G(再深度扫描。

JVM有哪些垃圾回收算法?

MarkSweep 标记清除算法
Copying 拷贝算法
MarkCompack 标记压缩算法

JVM中哪些可以作为gcroot

什么是oc root,NM在进行垃圾回收时,需要找到“垃圾”对象,也就是没有被引用的对象,但是直接找“垃圾对象是比较耗时的,所以反过来,先找“非垃圾”对象,也就是正常对象,那么就需要从某些“根”开始去找,根据这些“根”的引用路径找到正常对象,而这些“根”有一个特征,就是它只会引用其他对象,而不会被其他对象引用,例如:栈中的本地变量、方法区中的静态变量、本地方法栈中的变量、正在运行的线程等可以作为qcroot。

一个对象从加载到JVM,再到被GC清除,都经历了什么过程?

method{ ClassLoaderDemo1 c =new ClassLoaderDemo10; cxxx)GC
1、用户创建一个对象,JVM首先需要到方法区去找对象的类型信息。然后再创建对象。
2、JVM要实例化一个对象,首先要在堆当中先创建一个对象。->半初始化状态
3、对象首先会分配在堆内存中新生代的Eden。然后经过一次Minor GC,对象如果存活,就会进入S区。在后续的每次GC中,如果对象一直存活,就会在S区来回拷贝,每移动一次,年龄加1。->多大年龄才会移入老年代? 年龄最大15,超过一定年龄后,对象转入老年代。
当方法执行结束后,栈中的指针会先移除掉4
堆中的对象,经过Full GC,就会被标记为垃圾,然后被GC线程清理掉。

你们项目如何排查JVM问题
对于还在正常运行的系统:
1.可以使用jmap来查看VM中各个区域的使用情况
2.可以通过jstack来查看线程的运行情况,比如哪些线程阻塞、是否出现了死锁
3.可以通过jstat命令来查看垃圾回收的情况,特别是fullgc,如果发现fullgc比较频繁,那么就得进行调优了
4.通过各个命令的结果,或者jvisualvm等工具来进行分析
5.首先,初步猜测频繁发送ulg的原因,如果频发生fulgc又一直没有出现内存,那么表示fullgc实际上是回收了很多对象了,所以这些对象最好能在younggc过程中就直接回收掉,避免这些对象进入到老年代,对于这种情况,就要考虑这些存活时间不长的对象是不是比较大,导致年轻代放不下,直接进入到了老年代,尝试加大年轻代的大小,如果改完之后,fullgc减少,则证明修改有效
6.同时,还可以找到占用CPU最多的线程,定位到具体的方法,优化这个方法的执行,看是否能避免某些对象的创建,从而节省内存
对于已经发生了OOM的系统:
1.一般生产系统中都会设置当系统发生了00M时,生成当时的dump文件(-XX:+HeapDumpOn0utOfMemorvError -XX:HeapDumpPath=/usr/ocal/base)
2.我们可以利用isisualvm等工具来分析dump文件
3.根据dump文件找到异常的实例对象,和异常的线程(占用CPU高),定位到具体的代码
4.然后再进行详细的分析和调试
总之,调优不是一蹴而就的,需要分析、推理、实践、总结、再分析,最终定位到具体的问题.

什么是字节码?采用字节码的好处是什么?

java中的编译器和解释器:Java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟的机器。这台虚拟的机器在任何平台上都提供给编泽程序一个的共同的接口。
编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解程器来将虚拟机代码转换为特定系统的机器码执行。在Java中,这种供虚拟机理解的代码叫做 字节码(即扩展名为lass的文件),它不面向任何特定的处理器,只面向虚拟机。
每一种平台的解释器是不同的,但是实现的虚拟机是相同的。Java源程序经过编译器编译后变成字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定机器上的机器码,然后在特定的机器上运行。这也就是解释了Java的编译与解释并存的特点。Java源代码..编译器--jvm可执行的ava字节码(即虚拟指令).->jvm-vm中解释器-机器可执行的二进制机器码--->程序运行。
采用字节码的好处:
ava语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以Java程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,Java程序无须重新编译便可在多种不同的计算机上运行。

 

双亲委派模型的好处

主要是为了安全性,避免用户自己编写的类动态替换Java的一些核心类,比如String。
同时也避免了类的重复加载,因为1VM中区分不同类,不仅仅是根据类名,相同的ass文件被不同的ClassLoader加载就是不同的两个类

ACID靠什么保证的?

A原子性由undo log日志保证,它记录了需要回滚的日志信息,事务回滚时撤销已经执行成功的sql

C一致性由其他三大特性保证、程序代码要保证业务上的一致性

I隔离性由MVCC来保证
D持久性由内存+redolog来保证,mysql修改数据同时在内存和redo log记录这次操作,宕机的时候可以从redolog恢复
InnoDBredoTog写盘,InnoDB 事务进入 prepare 状态,如果前面 prepare 成功,binog 写盘,再继续将事务日志持久化到 binog,如果持久化成功,那么 InnoDB 事务则进入 commit 状态(在 redo log 里面写一个 commit 记录)
redolog的刷盘会在系统空闲时进行

B树和B+树的区别,为什么Mysql使用B+树

B树的特点:

1. 节点排序

2. 一个节点了可以存多个元素,多个元素也排序了

B+树的特点:

1.拥有B树的特点

2.叶子节点之间有指针

3,非叶子节点上的元素在叶子节点上都几余了,也就是叶子节点中存储了所有的元素,并且排好顺序

Mysql索引使用的是B+树,因为索引是用来加快查询的,而B+树通过对数据进行排序所以是可以提高查询速度的,然后通过一个节点中可以存储多个元素,从而可以使得B+树的高度不会太高,在Mysq中一个innodb页就是一个B+树节点,一个innodb页默认16kb,所以一般情况下一颗两层的B+树可以存2000万行左右的数据,然后通过利用B+树叶子节点存储了所有数据并且进行了排序,并且叶子节点之间有指针,可以很好的支持全表扫描,范围查找等SQL语句。

 

 

 

红黑树的特性

(1)每个节点或者是黑色,或者是红色。

(2)根节点是黑色。

(3)每个叶子节点(NIL)是黑色。[注意:这里叶子节点,是指为空NIL或NULL)的叶子节点!]

(4)如果一个节点是红色的,则它的子节点必须是黑色的。

(5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

Innodb是如何实现事务的

Innodb通过Buffer Pool,LogBuffer,Redo Log,Undo Log来实现事务,以一个update语句为例:
1.Innodb在收到一个update语句后,会先根据条件找到数据所在的页,并将该页缓存在Buffer Pool中。
2执行update语句,修改Buffer Pool中的数据,也就是内存中的数据。
3,针对update语句生成一个RedoLog对象,并存入LogBuffer中。
4.针对update语句生成undolog日志,用于事务回滚。
5.如果事务提交,那么则把Redolog对象进行持久化,后续还有其他机制将Buffer Pool中所修改的数据页持久化到磁盘中。
6,如果事务回滚,则利用undolog日志进行回滚。

Dubbo和Dubbox之间的区别?

Dubbox是继Dubbo停止维护后,当当网基于Dubbo做的一个扩展项目,如加了服务可Restful调用,更新了开源组件等.

Dubbo可以对结果进行缓存吗?

为了提高数据访问的速度。Dubbo提供了声明式缓存,以减少用户加缓存的工作量

<dubbo:reference cache="true" />

Dubbo如何优雅停机

Dubbo是通过JDK的ShutdownHook来完成优雅停机的,所以如果使用kill -9 PID等强制关闭指令,是不会执行优雅停机的,只有通过kill PID 时,才会执行。

Dubbo支持分布式事务吗

目前暂时不支持,可与通过tcc-transaction框架实现

介绍:tcc-transaction 是开源的TCC补偿性分布式事务框架

TCC-Transaction 通过Dubbo隐式传参的功能,避免自己对业务代码的入侵。

Dubbo配置文件是如何加载到Spring中的?

Spring容器在启动的时候,会读取到Spring默认的一些schema以及Dubbo自定义的schema,每个schema都会对应一个自己的NamespaceHandler,NamespaceHandler里面通过BeanDefinitionParser来解析配置信息并转化为需要加载的 bean 对象!

同一个服务多个注册的情况下可以直连某一个服务吗?

可以点对点直连,修改配置即可,也可以通过telnet直接某个服务。

Dubbo Monitor 实现原理?

Consumer 端在发起调用之前会先走 filter 链;provider 端在接收到请求时也是先走 filter 链,然后才进行真正的业务逻辑处理。默认情况下,在 consumer 和 provider 的 filter 链中都会有 Monitorfilter。

1、MonitorFilter 向 DubboMonitor 发送数据

2、DubboMonitor 将数据进行聚合后(默认聚合 1min 中的统计数据)暂存到ConcurrentMap<Statistics, AtomicReference> statisticsMap,然后使用一个含有 3 个线程(线程名字:DubboMonitorSendTimer)的线程池每隔 1min 钟,调用 SimpleMonitorService 遍历发送 statisticsMap 中的统计数据,每发送完毕一个,就重置当前的 Statistics 的 AtomicReference

3、SimpleMonitorService 将这些聚合数据塞入 BlockingQueue queue 中(队列大写为 100000)

4、SimpleMonitorService 使用一个后台线程(线程名为:DubboMonitorAsyncWriteLogThread)将 queue 中的数据写入文件(该线程以死循环的形式来写)

5、SimpleMonitorService 还会使用一个含有 1 个线程(线程名字:DubboMonitorTimer)的线程池每隔 5min 钟,将文件中的统计数据画成图表

Dubbo 用到哪些设计模式

Dubbo 框架在初始化和通信过程中使用了多种设计模式,可灵活控制类加载、权限控制等功能。我这里提几种

工厂模式

Provider 在 export 服务时,会调用 ServiceConfig 的 export 方法。ServiceConfig中有个字段:

private static final Protocol protocol =
ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtensi
on();

Dubbo 里有很多这种代码。这也是一种工厂模式,只是实现类的获取采用了 JDKSPI 的机制。这么实现的优点是可扩展性强,想要扩展实现,只需要在 classpath下增加个文件就可以了,代码零侵入。另外,像上面的 Adaptive 实现,可以做到调用时动态决定调用哪个实现,但是由于这种实现采用了动态代理,会造成代码调试比较麻烦,需要分析出实际调用的实现类。

装饰器模式

Dubbo 在启动和调用阶段都大量使用了装饰器模式。以 Provider 提供的调用链为例,具体的调用链代码是在 ProtocolFilterWrapper 的 buildInvokerChain 完成的,具体是将注解中含有 group=provider 的 Filter 实现,按照 order 排序,最后的调用顺序是:

EchoFilter -&gt; ClassLoaderFilter -&gt; GenericFilter -&gt; ContextFilter -&gt;
ExecuteLimitFilter -&gt; TraceFilter -&gt; TimeoutFilter -&gt; MonitorFilter -&gt;
ExceptionFilter

更确切地说,这里是装饰器和责任链模式的混合使用。例如,EchoFilter 的作用是判断是否是回声测试请求,是的话直接返回内容,这是一种责任链的体现。而像ClassLoaderFilter 则只是在主功能上添加了功能,更改当前线程的 ClassLoader,这是典型的装饰器模式。

观察者模式

Dubbo 的 Provider 启动时,需要与注册中心交互,先注册自己的服务,再订阅自己的服务,订阅时,采用了观察者模式,开启一个 listener。注册中心会每 5 秒定时检查是否有服务更新,如果有更新,向该服务的提供者发送一个 notify 消息,provider 接受到 notify 消息后,运行 NotifyListener 的 notify 方法,执行监听器方法。

动态代理模式

Dubbo 扩展 JDK SPI 的类 ExtensionLoader 的 Adaptive 实现是典型的动态代理实现。Dubbo 需要灵活地控制实现类,即在调用阶段动态地根据参数决定调用哪个实现类,所以采用先生成代理类的方法,能够做到灵活的调用。生成代理类的代码是 ExtensionLoader 的 createAdaptiveExtensionClassCode 方法。代理类主要逻辑是,获取 URL 参数中指定参数的值作为获取实现类的 key。

Dubbo 使用过程中都遇到了些什么问题?

在注册中心找不到对应的服务,检查 service 实现类是否添加了@service

注解无法连接到注册中心,检查配置文件中的对应的测试 ip 是否正确

Dubbo 服务降级,失败重试怎么做?

可以通过 dubbo:reference 中设置 mock=“return null”。mock 的值也可以修改为 true,然后再跟接口同一个路径下实现一个 Mock 类,命名规则是 “接口名称+Mock” 后缀。然后在 Mock 类里实现自己的降级逻辑。

如何解决服务调用链过长的问题?

可以结合 zipkin (链路追踪组件)实现分布式服务追踪。

服务上线怎么不影响旧版本?

采用多版本开发,不影响旧版本,小版本可以兼容,大版本升级采用蓝绿部署。

服务提供者能实现失效踢出是什么原理?

服务失效踢出基于 zookeeper 的临时节点原理。

Dubbo服务调用是阻塞的吗?

默认是阻塞的,可以异步调用,没有返回值的可以这么做。Dubbo 是基于 NIO 的非阻塞实现并行调用,客户端不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小,异步调用会返回一个 Future 对象。

默认使用的是什么通信框架,还有别的选择吗

默认也推荐使用 netty 框架,还有 mina

Dubbo 的整体架构设计有哪些分层?

接口服务层(Service):该层与业务逻辑相关,根据 provider 和 consumer 的业务设计对应的接口和实现

配置层(Config):对外配置接口,以 ServiceConfig 和 ReferenceConfig 为中心

服务代理层(Proxy):服务接口透明代理,生成服务的客户端 Stub 和 服务端的 Skeleton,以 ServiceProxy 为中心,扩展接口为 ProxyFactory

服务注册层(Registry):封装服务地址的注册和发现,以服务 URL 为中心,扩展接口为 RegistryFactory、Registry、RegistryService

路由层(Cluster):封装多个提供者的路由和负载均衡,并桥接注册中心,以Invoker 为中心,扩展接口为 Cluster、Directory、Router 和 LoadBlancce

监控层(Monitor):RPC 调用次数和调用时间监控,以 Statistics 为中心,扩展接口为 MonitorFactory、Monitor 和 MonitorService

远程调用层(Protocal):封装 RPC 调用,以 Invocation 和 Result 为中心,扩展接口为 Protocal、Invoker 和 Exporter

信息交换层(Exchange):封装请求响应模式,同步转异步。以 Request 和Response 为中心,扩展接口为 Exchanger、ExchangeChannel、ExchangeClient 和 ExchangeServer

网络 传输 层(Transport):抽象 mina 和 netty 为统一接口,以 Message 为中心,扩展接口为 Channel、Transporter、Client、Server 和 Codec

数据序列化层(Serialize):可复用的一些工具,扩展接口为 Serialization、ObjectInput、ObjectOutput 和 ThreadPool

Dubbo SPI 和 Java SPI 区别?

JDK SPI:

JDK 标准的 SPI 会一次性加载所有的扩展实现,如果有的扩展很耗时,但也没用上,很浪费资源。所以只希望加载某个的实现,就不现实了

DUBBO SPI:

1、 对 Dubbo 进行扩展,不需要改动 Dubbo 的源码

2、 延迟加载,可以一次只加载自己想要加载的扩展实现。

3、 增加了对扩展点 IOC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。

4、 Dubbo 的扩展机制能很好的支持第三方 IoC 容器,默认支持 Spring Bean。

dubbo 通信协议 dubbo 协议适用范围和适用场景

适用范围:传入传出参数数据包较小(建议小于 100K),消费者比提供者个数多,单一消费者无法压满提供者,尽量不要用 dubbo 协议传输大文件或超大字符串。

适用场景:常规远程服务方法调用

dubbo 协议补充:

连接个数:单连接

连接方式:长连接

传输协议:TCP

传输方式:NIO 异步传输

序列化:Hessian 二进制序列化

Dubbo 服务注册与发现的流程?

  • Provider(提供者)绑定指定端口并启动服务
  • 提供者连接注册中心,并发本机 IP、端口、应用信息和提供服务信息发送至注册中心存储
  • Consumer(消费者),连接注册中心 ,并发送应用信息、所求服务信息至注册中心
  • 注册中心根据 消费 者所求服务信息匹配对应的提供者列表发送至Consumer 应用缓存。
  • Consumer 在发起远程调用时基于缓存的消费者列表择其一发起调用。
  • Provider 状态变更会实时通知注册中心、在由注册中心实时推送至Consumer

Dubbo 集群的负载均衡有哪些策略

Dubbo 提供了常见的集群策略实现,并预扩展点予以自行实现。

  • Random LoadBalance: 随机选取提供者策略,有利于动态调整提供者权重。截面碰撞率高,调用次数越多,分布越均匀;
  • RoundRobin LoadBalance: 轮循选取提供者策略,平均分布,但是存在请求累积的问题;
  • LeastActive LoadBalance: 最少活跃调用策略,解决慢提供者接收更少的请求;
  • ConstantHash LoadBalance: 一致性 Hash 策略,使相同参数请求总是发到同一提供者,一台机器宕机,可以基于虚拟节点,分摊至其他提供者,避免引起提供者的剧烈变动;

Dubbo 有哪些注册中心?

  • Multicast注册中心: Multicast 注册中心不需要任何中心节点,只要广播地址,就能进行服务注册和发现。基于网络中组播传输实现;
  • Zookeeper注册中心: 基于分布式协调系统 Zookeeper 实现,采用Zookeeper 的 watch 机制实现数据变更;
  • redis注册中心: 基于 redis 实现,采用 key/Map 存储,住 key 存储服务名和类型,Map 中 key 存储服务 URL,value 服务过期时间。基于 redis 的发布/订阅模式通知数据变更;
  • Simple注册中心

Dubbo 超时时间怎样设置?

Dubbo 超时时间设置有两种方式:

  • 服务提供者端设置超时时间,在 Dubbo 的用户文档中,推荐如果能在服务端多配置就尽量多配置,因为服务提供者比消费者更清楚自己提供的服务特性。
  • 服务消费者端设置超时时间,如果在消费者端设置了超时时间,以消费者端为主,即优先级更高。因为服务调用方设置超时时间控制性更灵活。如果消费方超时,服务端线程不会定制,会产生警告。

Dubbo 支持哪些协议,每种协议的应用场景,优缺点?

  • dubbo:单一长连接和 NIO 异步通讯,适合大并发小数据量的服务调用,以及消费者远大于提供者。传输协议 TCP,异步,Hessian 序列化;
  • rmi: 采用 JDK 标准的 rmi 协议实现,传输参数和返回参数对象需要实现Serializable 接口,使用 java 标准序列化机制,使用阻塞式短连接,传输数据包大小混合,消费者和提供者个数差不多,可传文件,传输协议 TCP。多个短连接,TCP 协议传输,同步传输,适用常规的远程服务调用和 rmi 互操作。在依赖低版本的 Common-Collections 包,java 序列化存在安全漏洞;
  • webservice: 基于 WebService 的远程调用协议,集成 CXF 实现,提供和原生 WebService 的互操作。多个短连接,基于 HTTP 传输,同步传输,适用系统集成和跨语言调用;
  • http: 基于 Http 表单提交的远程调用协议,使用 Spring 的 HttpInvoke 实现。多个短连接,传输协议 HTTP,传入参数大小混合,提供者个数多于消费者,需要给应用程序和浏览器 JS 调用;
  • hessian:集成 Hessian 服务,基于 HTTP 通讯,采用 Servlet 暴露服务,Dubbo 内嵌 Jetty 作为服务器时默认实现,提供与 Hession 服务互操作。多个短连接,同步 HTTP 传输,Hessian 序列化,传入参数较大,提供者大于消费者,提供者压力较大,可传文件;
  • memcache: 基于 memcached 实现的 RPC 协议
  • redis: 基于 redis 实现的 RPC 协议

 Dubbo是如何完成服务导出的?

1. ⾸先Dubbo会将程序员所使⽤的@DubboService注解或@Service注解进⾏解析得到程序员所定义 的服务参数,包括定义的服务名、服务接⼝、服务超时时间、服务协议等等,得到⼀个 ServiceBean。

2. 然后调⽤ServiceBean的export⽅法进⾏服务导出

3. 然后将服务信息注册到注册中⼼,如果有多个协议,多个注册中⼼,那就将服务按单个协议,单个 注册中⼼进⾏注册

4. 将服务信息注册到注册中⼼后,还会绑定⼀些监听器,监听动态配置中⼼的变更

5. 还会根据服务协议启动对应的Web服务器或⽹络框架,⽐如Tomcat、Netty等

Dubbo是如何完成服务引⼊的?

1. 当程序员使⽤@Reference注解来引⼊⼀个服务时,Dubbo会将注解和服务的信息解析出来,得到 当前所引⽤的服务名、服务接⼝是什么

2. 然后从注册中⼼进⾏查询服务信息,得到服务的提供者信息,并存在消费端的服务⽬录中

3. 并绑定⼀些监听器⽤来监听动态配置中⼼的变更

4. 然后根据查询得到的服务提供者信息⽣成⼀个服务接⼝的代理对象,并放⼊Spring容器中作为Bean

Kafka为什么⽐RocketMQ的吞吐量要⾼

Kafka的⽣产者采⽤的是异步发送消息机制,当发送⼀条消息时,消息并没有发送到Broker⽽是缓存起 来,然后直接向业务返回成功,当缓存的消息达到⼀定数量时再批量发送给Broker。这种做法减少了⽹ 络io,从⽽提⾼了消息发送的吞吐量,但是如果消息⽣产者宕机,会导致消息丢失,业务出错,所以理 论上kafka利⽤此机制提⾼了性能却降低了可靠性

Kafka的Pull和Push分别有什么优缺点

1. pull表示消费者主动拉取,可以批量拉取,也可以单条拉取,所以pull可以由消费者⾃⼰控制,根据 ⾃⼰的消息处理能⼒来进⾏控制,但是消费者不能及时知道是否有消息,可能会拉到的消息为空

2. push表示Broker主动给消费者推送消息,所以肯定是有消息时才会推送,但是消费者不能按⾃⼰的 能⼒来消费消息,推过来多少消息,消费者就得消费多少消息,所以可能会造成⽹络堵塞,消费者 压⼒⼤等问题

RocketMQ的底层实现原理

RocketMQ由NameServer集群、Producer集群、Consumer集群、Broker集群组成,消息⽣产和消费 的⼤致原理如下:

1. Broker在启动的时候向所有的NameServer注册,并保持⻓连接,每30s发送⼀次⼼跳

2. Producer在发送消息的时候从NameServer获取Broker服务器地址,根据负载均衡算法选择⼀台服 务器来发送消息

3. Conusmer消费消息的时候同样从NameServer获取Broker地址,然后主动拉取消息来消费

Dubbo是如何做系统交互的

Dubbo底层是通过RPC来完成服务和服务之间的调⽤的,Dubbo⽀持很多协议,⽐如默认的dubbo协议,⽐如http协议、⽐如rest等都是⽀持的,他们的底层所使⽤的技术是不太⼀样的,⽐如dubbo协议底层使⽤的是netty,也可以使⽤mina,http协议底层使⽤的tomcat或jetty。
服务消费者在调⽤某个服务时,会将当前所调⽤的服务接⼝信息、当前⽅法信息、执⾏⽅法所传⼊的⼊参信息等组装为⼀个Invocation对象,然后不同的协议通过不同的数据组织⽅式和传输⽅式将这个对象传送给服务提供者,提供者接收到这个对象后,找到对应的服务实现,利⽤反射执⾏对应的⽅法,得到⽅法结果后再通过⽹络响应给服务消费者。
当然,Dubbo在这个调⽤过程中还做很多其他的设计,⽐如服务容错、负载均衡、Filter机制、动态路由机制等等,让Dubbo能处理更多企业中的需求。

redis使用过程中遇到的瓶颈有哪些,怎样解决?

瓶颈
1)机器内存大小,因为redis的数据放在内存里,所以存放数据量的多少取决于内存的多少

2)Master写内存快照,save命令调度rdbSave函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务,所以Master最好不要写内存快照。

3)单点故障

4)主从复制

解决办法
总结:

1.Master最好不要做任何持久化工作,包括内存快照和AOF日志文件,特别是不要启用内存快照做持久化。

2.如果数据比较关键,某个Slave开启AOF备份数据,策略为每秒同步一次。

3.为了主从复制的速度和连接的稳定性,Slave和Master最好在同一个局域网内。

4.尽量避免在压力较大的主库上增加从库

5.为了Master的稳定性,主从复制不要用图状结构,用单向链表结构更稳定,即主从关系为:Master<--Slave1<--Slave2<--Slave3.......,这样的结构也方便解决单点故障问题,实现Slave对Master的替换,也即,如果Master挂了,可以立马启用Slave1做Master,其他不变。

 

posted @ 2023-03-08 12:06  君莫笑我十年游  阅读(165)  评论(0编辑  收藏  举报