面试总结(某地产)

面试总结(某地产)

gRPC 如何做负载的

gRPC负载均衡,官方只提供了pick_firstround_robin两种负载均衡策略

gRPC 限流怎么做的

可以通过信号量来做限流

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 如何做负载的

dubbo的负载均衡全部由AbstractLoadBalance的子类来实现,Dubbo默认采用的是随机负载策略

Dubbo的负载均衡策略

dubbo 总共有四种负载均衡策略

  • RandomLoadBalance 随机:默认情况下,dubbo 是 random load balance 随机调用实现负载均衡,可以对 provider 不同实例设置不同的权重,会按照权重来负载均衡,权重越大分配流量越高,一般就用这个默认的就可以了。
  • RoundRobinLoadBalance 轮询:这个的话默认就是均匀地将流量打到各个机器上去,但是如果各个机器的性能不一样,容易导致性能差的机器负载过高。所以此时需要调整权重,让性能差的机器承载权重小一些,流量少一些。
  • LeastActiveLoadBalance 最少活跃调用数:这个就是自动感知一下,如果某个机器性能越差,那么接收的请求越少,越不活跃,此时就会给不活跃的性能差的机器更少的请求。
  • ConsistentHashLoadBalance 一致性 Hash:一致性 Hash 算法,相同参数的请求一定分发到一个 provider 上去,provider 挂掉的时候,会基于虚拟节点均匀分配剩余的流量,抖动不会太大。如果你需要的不是随机负载均衡,是要一类请求都到一个节点,那就走这个一致性 Hash 策略。

Dubbo 的容错策略

dubbo 集群的六种容错策略:

  1. failover cluster 模式:失败自动切换,自动重试其他机器,默认就是这个,常见于读操作。(失败重试其它机器)
  2. failfast cluster模式:一次调用失败就立即失败,常见于写操作。(调用失败就立即失败)
  3. failsafe cluster 模式:出现异常时忽略掉,常用于不重要的接口调用,比如记录日志。
  4. failback cluster 模式:失败了后台自动记录请求,然后定时重发,比较适合于写消息队列这种。
  5. forking cluster 模式:并行调用多个 provider,只要一个成功就立即返回。
  6. broadcacst cluster:逐个调用所有的 provider。

Spring 循环依赖怎么解决的,为什么使用三级缓存?

Spring无法解决构造器注入的循环依赖,可以解决setter注入的循环依赖并且只能解决单例bean的循环依赖

  1. 先从一级缓存singletonObjects中去获取。(如果获取到就直接return)
  2. 如果获取不到或者对象正在创建中(isSingletonCurrentlyInCreation()),那就再从二级缓存earlySingletonObjects中获取。(如果获取到就直接return)
  3. 如果还是获取不到,且允许singletonFactories(allowEarlyReference=true)通过getObject()获取。就从三级缓存singletonFactory.getObject()获取。(如果获取到了就从singletonFactories中移除,并且放进earlySingletonObjects。其实也就是从三级缓存移动(是剪切、不是复制哦~)到了二级缓存)

Spring Bean 生命周期

参考面试总结2里的Spring Bean生命周期总结

Spring 两种动态代理区别

  • Spring JDK原生动态代理是基于Java反射实现的,Java动态代理是基于接口的,如果对象没有实现接口,Spring就会使用CGLIB动态代理
  • CGLIB是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB通过继承方式实现代理

Spring的BeanPostProcessor和BeanFactoryPostProcessor区别

BeanPostProcessor接口:后置bean处理器,允许自定义修改新的bean实例,应用程序上下文可以在其bean定义中自动检测BeanPostProcessor类型的bean,并将它们应用于随后创建的任何bean。(例如:配置文件中注册了一个自定义BeanPostProcessor类型的bean,一个User类型的bean,应用程序上下文会在创建User实例之后对User应用BeanPostProcessor)。

BeanFactoryPostProcessor接口:后置工厂处理器,允许自定义修改应用程序上下文的bean定义,调整bean属性值。应用程序上下文可以在其bean定义中自动检测BeanFactoryPostProcessor,并在创建任何非BeanFactoryPostProcessor类型bean之前应用它们(例如:配置文件中注册了一个自定义BeanFactoryPostProcessor类型的bean,一个User类型的bean,应用程序上下文会在创建User实例之前对User应用BeanFactoryPostProcessor)

Spring AOP 和 AspectJ AOP 区别

Spring AOP

  1. Spring是基于动态代理来实现的AOP,默认如果使用接口,就用JDK提供的动态代理实现;如果是方法则使用CGLIB实现
  2. Spring AOP需要依赖IoC容器来管理,并且只能作用于Spring容器,使用纯Java代码实现
  3. 在性能上,由于Spring AOP是基于动态代理来实现的,在容器启动时需要生成代理实例,在方法调用上也会增加栈的深度,使得Spring AOP的性能不如AspectJ

AspectJ

  • AspectJ来自于Eclipse基金会
  • AspectJ属于静态织入,通过修改代码来实现,有如下几个织入的时机:
    1. 编译期织入(Compile-time weaving):如类A使用AspectJ添加一个属性,类B引用了它,这个时候就需要编译期的时候进行织入,否则没法编译类B
    2. 编译后织入(Post-compile weaving):也就是生成了.class文件或已经打成了jar包,这种情况我们需要增强处理的话,就要用到编译后织入
    3. 类加载后织入(Load-time weaving):指的是在加载类的时候进行织入,要实现这个时期的织入,有几种常见的方法:
      1. 自定义类加载器来干这个,这个应该是最容易想到的办法,在被织入类加载到JVM前去对它进行加载,这样就可以在加载到时候定义行为来
      2. 在JVM启动的时候指定AspectJ提供的agent:-javaagent:xxx/xxx/aspectjweaver.jar
  • AspectJ可以做Spring AOP干不了的事情,它是AOP编程的完全解决方案,Spring AOP则致力于解决企业级开发中最普遍的AOP(方法织入)。而不是成为像AspectJ一样的AOP方案
  • 因为AspectJ在实际运行之前就完成了织入,所以说它生成的类是没有额外运行时开销的

HashMap 如何解决hash碰撞,以及何时扩容的

Hash冲突:由于用于计算的数据是无限的H(key),key属于(-∞,+∞),而映射到区间是有限的,所以肯定会存在两个key:key1,key2,H(key1)=H(key2),这就是hash冲突。一般的解决Hash冲突方法有:开放定址法、再哈希法、链地址法(拉链法)、建立公共溢出区。

开放地址法:也称为再散列法,基本思想就是,如果p=H(key)出现冲突时,则以p为基础,再次hash,p1=H(p),如果p1再次出现冲突,则以p1为基础,以此类推,直到找到一个不冲突的哈希地址pi。 因此开放定址法所需要的hash表的长度要大于等于所需要存放的元素,而且因为存在再次hash,所以只能在删除的节点上做标记,而不能真正删除节点。

缺点:容易产生堆积问题;不适合大规模的数据存储;插入时会发生多次冲突的情况;删除时要考虑与要删除元素互相冲突的另一个元素,比较复杂。

再哈希法(双重散列,多重散列):提供多个不同的hash函数,当R1=H1(key1)发生冲突时,再计算R2=H2(key1),直到没有冲突为止。 这样做虽然不易产生堆集,但增加了计算的时间。

链地址法(拉链法):将哈希值相同的元素构成一个同义词的单链表,并将单链表的头指针存放在哈希表的第i个单元中,查找、插入和删除主要在同义词链表中进行。链表法适用于经常进行插入和删除的情况。HashMap采用的就是链地址法来解决hash冲突。(链表长度大于等于8时转为红黑树)

建立公共溢出区:将哈希表分为公共表和溢出表,当溢出发生时,将所有溢出数据统一放到溢出区。

何时扩容:HashMap的容量是有限的。当经过多次元素插入的时候,使得HashMap达到一定的饱和度,Key映射位置的几率不断变大。这个时候,HashMap就需要扩容了,也就是Resize。当 HashMap.size >= Capacity*LoadFactor 时,HashMap可能会进行Resize。

Hashmap的扩容需要满足两个条件:当前数据存储的数量(即size())大小必须大于等于阈值;当前加入的数据是否发生了hash冲突。

ArrayList与LinkedList区别

  • ArrayList基于数组实现的列表
  • LinkeddList基于链表实现的列表

LinkedHashMap如何实现有序的

LinkedHashMap是继承于HashMap,是基于HashMap和双向链表来实现的。

HashMap是无序的;LinkedHashMap有序的,可分为插入顺序和访问顺序两种。如果是访问顺序,那put和get操作已存在的Entry时,都会把Entry移动到双向链表的表尾(其实是先删除再插入)。

LinkedHashMap存取数据,还是跟HashMap一样使用的Entry[]的方式,双向链表只是为了保证顺序。

Queue的阻塞队列用过哪几种

Java提供了7个阻塞队列:

  • ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列
    • ArrayBlockingQueue按照先进先出(FIFO)的原则对元素进行排序
    • 默认情况下不保证线程公平的访问队列,所谓公平访问队列是指阻塞的线程,可以按照先后顺序访问队列
    • 非公平性是对先等待的线程是非公平的,当队列可用时,阻塞的线程都可以争夺访问队列的资格
    • 为了保证公平性,通常会降低吞吐量
  • LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列
    • LinkedBlockingQueue队列默认和最大长度为Integer.MAX_VALUE
    • 此队列也是按照先入先出(FIFO)的原则对元素进行排序
  • PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列
    • 默认情况下元素采取自然顺序升序排列
    • 也可以自定义实现compareTo()方法来指定元素排序规则,或者初始化时,指定构造参数Comparator来对元素进行排序
    • 需要注意的是不能保证同优先级元素的顺序
  • DelayQueue:一个支持延时获取元素的无界阻塞队列
    • DelayQueue队列使用PriorityQueue来实现
    • 队列中的元素必须实现Delayed接口,在创建元素时可以指定多久才能从队列中获取当前元素
    • 只有在延迟期满时才能从队列中提取元素
  • SynchronousQueue:一个不存储元素的阻塞队列
    • 是一个不存储元素的阻塞队列
    • 每个put操作必须等待一个take操作,否则不能继续添加元素
    • 它支持公平访问队列,默认采用非公平性策略访问队列
    • 该队列非常适合传递性场景
  • LinkedTransferQueue:一个由链表结构组成的无界阻塞队列
    • 是一个由链表结构组成的无界阻塞TransferQueue队列
    • 相比于其他阻塞队列,LinkedTransferQueue多个tryTransfer和transfer方法
  • LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列
    • 是一个由链表结构组成的双向阻塞队列
    • 所谓双向队列指的是可以从队列的两端插入和移出元素
    • 双向队列因为多了一个操作队列的入口、在多线程同时入队时,也就减少了一半的竞争
    • 在初始化LinkedBlockingDeque时可以设置容量防止其过度膨胀
    • 另外双向阻塞队列可以运用在“工作窃取”模式中

AQS的实现原理

什么是AQS

AQS全称为AbstractQueuedSynchronizer,它提供了一个FIFO队列,可以看成是一个用来实现同步锁以及其他涉及到同步功能的核心组件,常见的有:ReentrantLock、CountDownLatch、信号量等。AQS是一个抽象类,主要是通过继承的方式来使用,它本身没有实现任何的同步接口,仅仅是定义了同步状态的获取以及释放的方法来提供自定义的同步组件。

AQS提供的功能

从使用层面来说,AQS的功能分为两种:独占和共享

  • 独占锁:每次只能被一个线程持有锁,如:ReentrantLock
  • 共享锁:允许多个线程同时获取锁,并发访问共享资源,如:ReentrantReadWriteLock

AQS的内部实现

AQS的实现依赖内部的同步队列,也就是FIFO的双向队列,如果当前线程竞争锁失败,那么AQS会把当前线程以及等待状态信息构造成一个Node加入到同步队列中,同时再阻塞该线程。当获取锁的线程释放锁以后,会从队列中唤醒一个阻塞的节点(线程)。

AQS队列内部维护的是一个FIFO的双向链表,这种结构的特点是每个数据结构都有两个指针,分别指向直接的后继节点和直接前驱节点。所以双向链表可以从任意一个节点开始很方便的访问前驱和后继。每个Node其实是由线程封装,当线程争抢锁失败后会封装成Node加入到ASQ队列中去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
static final class Node {
/** 指示节点正在共享模式下等待的标记 */
static final Node SHARED = new Node();
/** 指示节点正在以独占模式等待的标记 */
static final Node EXCLUSIVE = null;

/** waitStatus值,指示线程已取消 */
static final int CANCELLED = 1;
/** waitStatus值,指示后续线程需要释放 */
static final int SIGNAL = -1;
/** waitStatus值,指示线程正在等待条件 */
static final int CONDITION = -2;
/**
* waitStatus值,指示下一个acquireShared应该无条件传播
*/
static final int PROPAGATE = -3;

volatile int waitStatus;

// 前驱节点
volatile Node prev;

// 后置节点
volatile Node next;

/**
* 使该节点排队的线程。 在构造上初始化,使用后消失。
*/
volatile Thread thread;

// 存储在condition队列中的后继节点
Node nextWaiter;

/**
* 如果节点在共享模式下等待,则返回true。
*/
final boolean isShared() {
return nextWaiter == SHARED;
}

/**
* 返回上一个节点,如果为null,则抛出NullPointerException。
* 空检查可能会被忽略,但是它可以帮助VM。
* @return the predecessor of this node
*/
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}

Node() { // 用于建立初始标头或SHARED标记
}

Node(Thread thread, Node mode) { // 由addWaiter使用
this.nextWaiter = mode;
this.thread = thread;
}

Node(Thread thread, int waitStatus) { // 根据条件使用
this.waitStatus = waitStatus;
this.thread = thread;
}
}

CAS的原理

CAS全称是Compare and Swap,即比较并交换,是通过原子指令来实现多线程的同步功能,将获取存储在内存地址的原值和指定的内存地址进行比较,只有当他们相等时,交换指定的预期值和内存中的值,这个操作是原子操作,若不相等,则重新获取存储在内存地址的原值。

CAS操作都是使用的Unsafe类的方法里设置:

1
2
3
4
5
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

事务的隔离级别,以及分别解决了什么

事务隔离级别 脏读 不可重复读 幻读
Read-uncommitted
Read-committed
Repeatable-read
Serializable

Mysql除了可重复读的隔离级别还有哪些?

还有读未提交、读已提交、串行化这些隔离级别

Mysql 日志类型

重做日志(redo log):确保事务的持久性。redo日志记录事务执行后的状态,用来恢复未写入data file的已成功事务更新的数据。防止在发生故障的时间点,尚有脏页未写入磁盘,在重启mysql服务的时候,根据redo log进行重做,从而达到事务的持久性这一特性。

回滚日志(undo log):保证数据的原子性,保存了事务发生之前的数据的一个版本,可以用于回滚,同时可以提供多版本并发控制下的读(MVCC),也即非锁定读。

二进制日志(binlog):用于复制,在主从复制中,从库利用主库上的binlog进行重播,实现主从同步。用于数据库的基于时间点的还原。

错误日志(errorlog):错误日志记录着mysqld启动和停止,以及服务器在运行过程中发生的错误的相关信息。在默认情况下,系统记录错误日志的功能是关闭的,错误信息被输出到标准错误输出。

慢查询日志(slow query log):慢日志记录执行时间过长和没有使用索引的查询语句,报错select、update、delete以及insert语句,慢日志只会记录执行成功的语句。

一般查询日志(general log):记录了服务器接收到的每一个查询或是命令,无论这些查询或是命令是否正确甚至是否包含语法错误,general log 都会将其记录下来 ,记录的格式为 {Time ,Id ,Command,Argument }。也正因为mysql服务器需要不断地记录日志,开启General log会产生不小的系统开销。 因此,Mysql默认是把General log关闭的。

中继日志(relay log):中继日志是连接mastert和slave的信息,它是复制的核心,I/O线程将来自master的事件存储到中继日志中,中继日志充当缓冲,这样master不必等待slave执行完成就可以发送下一个事件。

Mysql索引为什么使用B+tree

  • B树能解决的问题,B+树都能解决,且能够更好的解决,降低了树的高度,增加节点的数据存储量。
  • B+树的扫库和扫表能力更强,如果根据索引去根据数据表扫描,对B树扫描,需要整颗树遍历,B+树只需要遍历所有的叶子节点
  • B+树的磁盘读写能力更强,根结点和支节点不保存数据区,所有的根结点和支节点同样大小的情况下,保存的关键字更多,叶子结点不存子节点的引用,所以,B+树读写一次磁盘加载的关键字更多
  • B+树具有天然的排序功能
  • B+树的查询效率更加稳定,每次查询数据,查询IO次数是稳定的

Mysql的最左匹配原则是什么

  • 最左前缀匹配原则,非常重要的原则,mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。

  • =和in可以乱序,比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,mysql的查询优化器会帮你优化成索引可以识别的形式。

Mysql的锁有哪些

参考官方文档

参考其他文档分享

共享锁和排他锁

在InnoDB中是实现标准的行级锁定,其中有两种类型的锁定:共享(S)锁定和排他(X)锁定。

  • 读锁(read lock),也叫共享锁(shared lock)允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁
  • 写锁(write lock),也叫排他锁(exclusive lock)允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享锁和排他锁

意向锁

InnoDB支持多种粒度锁定,允许行锁和表锁并存。 例如,诸如LOCK TABLES … WRITE之类的语句对指定表采用排他锁(X锁)。 为了使在多个粒度级别上的锁定变得切实可行,InnoDB使用了意图锁定。 意向锁是表级锁,指示事务稍后对表中的行需要哪种类型的锁(共享锁或排他锁)。 有两种类型的意图锁:

  • 意向共享锁(IS)一个事务给一个数据行加共享锁时,必须先获得表的IS锁
  • 意向排它锁(IX)一个事务给一个数据行加排他锁时,必须先获得该表的IX锁

记录锁

记录锁定是对索引记录的锁定。 例如SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE; 防止任何其他事务插入,更新或删除t.c1值为10的行。

间隙锁

间隙锁定是对索引记录之间的间隙的锁定,或者是对第一个或最后一个索引记录之前的间隙的锁定。 例如**SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE;**。 防止其他事务将值15插入到t.c1列中,无论该列中是否已有这样的值,因为该范围中所有现有值之间的间隙是锁定的。

Next-Key 锁

下一键锁定是索引记录上的记录锁定和索引记录之前的间隙上的间隙锁定的组合。

插入意图锁

插入意图锁是在行插入之前通过INSERT操作设置的间隙锁的一种。 此锁以这种方式发出信号,表明要插入的意图是:如果多个事务未插入间隙中的相同位置,则不必等待彼此插入的多个事务。 假设有索引记录,其值分别为4和7。单独的事务分别尝试插入值5和6,在获得插入行的排他锁之前,每个事务都使用插入意图锁来锁定4和7之间的间隙, 但不要互相阻塞,因为行是无冲突的。

自增锁

AUTO-INC锁是一种特殊的表级锁,由插入到具有AUTO_INCREMENT列的表中的事务获取。 在最简单的情况下,如果一个事务正在向表中插入值,那么任何其他事务都必须等待自己在该表中进行插入,以便第一个事务插入的行接收连续的主键值。

Mysql InnoDB和MyISAM的区别

  1. InnoDB 支持事务,MyISAM 不支持事务。这是 MySQL 将默认存储引擎从 MyISAM 变成 InnoDB 的重要原因之一;
  2. InnoDB 支持外键,而 MyISAM 不支持。对一个包含外键的 InnoDB 表转为 MYISAM 会失败;
  3. InnoDB 是聚集索引,MyISAM 是非聚集索引。聚簇索引的文件存放在主键索引的叶子节点上,因此 InnoDB 必须要有主键,通过主键索引效率很高。但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据。因此,主键不应该过大,因为主键太大,其他索引也都会很大。而 MyISAM 是非聚集索引,数据文件是分离的,索引保存的是数据文件的指针。主键索引和辅助索引是独立的。
  4. InnoDB 不保存表的具体行数,执行 select count(*) from table 时需要全表扫描。而MyISAM 用一个变量保存了整个表的行数,执行上述语句时只需要读出该变量即可,速度很快;
  5. InnoDB 最小的锁粒度是行锁,MyISAM 最小的锁粒度是表锁。一个更新语句会锁住整张表,导致其他查询和更新都会被阻塞,因此并发访问受限。这也是 MySQL 将默认存储引擎从 MyISAM 变成 InnoDB 的重要原因之一;

Redis如何实现持久化的

Redis 提供了不同级别的持久化方式:

  • RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储.
  • AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾.Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大.
  • 如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式.
  • 你也可以同时开启两种持久化方式, 在这种情况下, 当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整.
  • 最重要的事情是了解RDB和AOF持久化方式的不同,让我们以RDB持久化方式开始

RDB 优点:

  • RDB是一个非常紧凑的文件,它保存了某个时间点得数据集,非常适用于数据集的备份,比如你可以在每个小时报保存一下过去24小时内的数据,同时每天保存过去30天的数据,这样即使出了问题你也可以根据需求恢复到不同版本的数据集.
  • RDB是一个紧凑的单一文件,很方便传送到另一个远端数据中心或者亚马逊的S3(可能加密),非常适用于灾难恢复.
  • RDB在保存RDB文件时父进程唯一需要做的就是fork出一个子进程,接下来的工作全部由子进程来做,父进程不需要再做其他IO操作,所以RDB持久化方式可以最大化redis的性能.
  • 与AOF相比,在恢复大的数据集的时候,RDB方式会更快一些

RDB 缺点:

  • 如果你希望在redis意外停止工作(例如电源中断)的情况下丢失的数据最少的话,那么RDB不适合你.虽然你可以配置不同的save时间点(例如每隔5分钟并且对数据集有100个写的操作),是Redis要完整的保存整个数据集是一个比较繁重的工作,你通常会每隔5分钟或者更久做一次完整的保存,万一在Redis意外宕机,你可能会丢失几分钟的数据.
  • RDB 需要经常fork子进程来保存数据集到硬盘上,当数据集比较大的时候,fork的过程是非常耗时的,可能会导致Redis在一些毫秒级内不能响应客户端的请求.如果数据集巨大并且CPU性能不是很好的情况下,这种情况会持续1秒,AOF也需要fork,但是你可以调节重写日志文件的频率来提高数据集的耐久度.

AOF 优点

  • 使用AOF会让你的Redis更加耐久: 你可以使用不同的fsync策略:无fsync,每秒fsync,每次写的时候fsync.使用默认的每秒fsync策略,Redis的性能依然很好(fsync是由后台线程进行处理的,主线程会尽力处理客户端请求),一旦出现故障,你最多丢失1秒的数据.
  • AOF文件是一个只进行追加的日志文件,所以不需要写入seek,即使由于某些原因(磁盘空间已满,写的过程中宕机等等)未执行完整的写入命令,你也也可使用redis-check-aof工具修复这些问题.
  • Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写: 重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。 整个重写操作是绝对安全的,因为 Redis 在创建新 AOF 文件的过程中,会继续将命令追加到现有的 AOF 文件里面,即使重写过程中发生停机,现有的 AOF 文件也不会丢失。 而一旦新 AOF 文件创建完毕,Redis 就会从旧 AOF 文件切换到新 AOF 文件,并开始对新 AOF 文件进行追加操作。
  • AOF 文件有序地保存了对数据库执行的所有写入操作, 这些写入操作以 Redis 协议的格式保存, 因此 AOF 文件的内容非常容易被人读懂, 对文件进行分析(parse)也很轻松。 导出(export) AOF 文件也非常简单: 举个例子, 如果你不小心执行了 FLUSHALL 命令, 但只要 AOF 文件未被重写, 那么只要停止服务器, 移除 AOF 文件末尾的 FLUSHALL 命令, 并重启 Redis , 就可以将数据集恢复到 FLUSHALL 执行之前的状态。

AOF 缺点:

  • 对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。
  • 根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB 。 在一般情况下, 每秒 fsync 的性能依然非常高, 而关闭 fsync 可以让 AOF 的速度和 RDB 一样快, 即使在高负荷之下也是如此。 不过在处理巨大的写入载入时,RDB 可以提供更有保证的最大延迟时间(latency)。

项目中如何做链路跟踪的

Zipkin 做服务链路跟踪