赞
踩
定义:
内存泄漏memory leak :是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出。
内存溢出 out of memory :指程序申请内存时,没有足够的内存供申请者使用,或者说,给了你一块存储int类型数据的存储空间,但是你却存储long类型的数据,那么结果就是内存不够用,此时就会报错OOM,即所谓的内存溢出。
关系:
内存泄露 → 剩余内存不足 → 后续申请不到足够内存 →内存溢出。
内存泄露产生场景:
1、静态集合类:集合内对象无法被释放。
2、集合里的对象属性值被改变。
3、监听器。
4、各种连接。
5、外部模块的引用。
6、单例模式。
栈: 栈的优势是,存取速度比堆要快,仅次于寄存器,栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。每个线程包含一个栈区,栈中存放着局部变量表(用于存储方法参数和定义在方法体内的局部变量这些数据类型包括各类基本数据类型、对象引用(reference),以及returnAddress类型)
、操作数栈、动态链接(指向运行时常量池的方法引用)
、方法返回地址(方法正常退出或者异常退出的定义)
、附件信息,对象都存放在堆区中。 栈是线程独享(私有)的。
堆:堆是一个运行时数据区。可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的。缺点是,由于要在运行时动态分配内存,存取速度较慢。jvm只有一个堆区(heap)被所有线程共享,堆中主要存放实例变量、数组等,每个对象都包含一个与之对应的class的信息(class信息存放在方法区)。
jdk7之前堆内存逻辑上分为三部分:新生区+养老区+永久区;jdk8及之后内存逻辑上分为三部分:新生区+养老区+元空间;
除了堆空间外的一些结构,比如 虚拟机栈、本地方法栈、方法区、字符串常量池 等地方对堆空间进行引用的,都可以作为GC Roots
方法区是可供各线程共享的运行时内存区域,属于一种规范。
垃圾收集主要回收两部分内容:常量池中废弃的常量和不再使用的类型。
方法区内常量池之中主要存放的两大类常量:字面量和符号引用。字面量指文本字符串、被声明为final的常量值等。符号引用指类和接口的全限定名、字段的名称和描述符、方法的名称和描述符。
常量的回收:没有被任何地方引用即可回收。
类型的回收:该类所有的实例都已经被回收,即Java堆中不存在该类及其任何派生子类的实例。加载该类的类加载器已经被回收、该类对应的java.lang.C1ass对象没有在任何地方被引用、无法在任何地方通过反射访问该类的方法,Java虚拟机被允许对满足上述三个条件的无用类进行回收。
类加载器回收很难达成,除非是经过精心设计的可替换类加载器的场景,如大量使用反射、动态代理、CGLib等字节码框架,动态生成JSP以及oSGi。
CMS收集器是缩短暂停应用时间为目标而设计的,针对老年代的收集,是基于标记-清除算法实现,整个过程分为4个步骤,包括:
其中,初始标记、重新标记这两个步骤仍然需要暂停应用线程。初始标记只是标记一下GC Roots能直接关联到的对象,速度很快,并发标记阶段是标记可回收对象,从GC Roots的直接关联对象开始遍历整个对象图,而重新标记阶段则是为了修正并发标记期间因用户程序继续运作导致标记产生变动的那一部分对象的标记记录,这个阶段暂停时间比初始标记阶段稍长一点,但远比并发标记时间短。由于整个过程中消耗最长的并发标记和并发清除过程收集器线程都可以与用户线程一起工作,所以,CMS收集器内存回收与用户一起并发执行的,大大减少了暂停时间。
特点:
备注:新生代可选择串行垃圾回收器、并行垃圾回收器或综合使用G1。
为何不使用标记整理算法:当并发清除的时候,不会STW,要保证用户线程能继续执行,前提的它运行的资源不受影响,如果Compact整理内存的话,原来的用户线程使用的内存将无法使用。
G1收集器将堆内存划分多个大小相等的独立区域(Region),并且能预测暂停时间,能预测原因它能避免对整个堆进行全区收集。G1跟踪各个Region里的垃圾堆积价值大小(所获得空间大小以及回收所需时间),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region,从而保证了再有限时间内获得更高的收集效率。
G1收集器工作工程分为4个步骤,包括:
其中,初始标记、最终标记这两个步骤仍然需要暂停应用线程,但初始标记耗时短,最终标记可以并行。初始标记与CMS一样,标记一下GC Roots能直接关联到的对象。并发标记从GC Root开始标记存活对象,这个阶段耗时比较长,但也可以与应用线程并发执行。而最终标记也是为了修正在并发标记期间因用户程序继续运作而导致标记产生变化的那一部分标记记录。最后在筛选回收阶段对各个Region回收价值和成本进行排序,根据用户所期望的GC暂停时间来执行回收。此阶段非并发,会暂停相关用户线程。
特点:
使用可达性分析算法来判断内存是否可回收,那么分析工作必须在一个能保障一致性的快照中进行。这点不满足的话分析结果的准确性就无法保证。所以GC时需要停顿来枚举根节点时。
定义:堆栈溢出,某个线程的线程栈空间被耗尽,没有足够资源分配给新创建的栈帧。
原因:
java heap space 分配的堆内存不足
原因:
超出预期的访问量/数据量
内存泄漏耗光堆内存
GC overhead limit exceeded 超过98%的时间用来做GC并且回收了不到2%的堆内存
原因:
Direct buffer memory 本地内存不足,但是堆内存充足的时候
原因:
NIO引起的,使用Native 函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。通过ByteBuffer.allocteDirect分配OS本地内存,不属于GC管辖范围,由于不需要内存的拷贝,所以速度相对较快。但如果不断分配本地内存,堆内存很少使用,那么JVM就不需要执行GC,DirectByteBuffer对象就不会被回收,这时候堆内存充足,但本地内存可能已经使用光了,再次尝试分配本地内存就会出现OutOfMemoryError。
unable to create new native thread 创建线程的达到上限
原因:
应用创建了太多线程,一个应用进程创建多个线程,超过系统承载极限
服务器并不允许你的应用程序创建这么多线程,linux系统默认运行单个进程可以创建的线程为1024个,如果应用创建超过这个数量,就会报 java.lang.OutOfMemoryError:unable to create new native thread
Metaspace 元空间使用的本地内存不足
原因:
加载—链接[验证—准备—解析]—初始化
加载:通过一个类的全限定名获取定义此类的二进制字节流,将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构,在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
**验证:**确保Class文件的字节流中包含信息符合当前虚拟机要求,保证被加载类的正确性,不会危害虚拟机自身安全。
**准备:**为类变量分配内存并且设置该类变量的默认初始值,即零值。
不包含用final修饰的static变量,final在编译的时候就会分配了,准备阶段会显式初始化。也不会为实例变量分配初始化,实例变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中
**解析:**将所有的类名,方法名,字段名这些符号引用替换为具体的内存地址或偏移量,也就是直接引用。
**初始化:**为标记为常量值的字段赋值的过程。换句话说,只对static修饰的变量或语句块进行初始化。如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类。如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行。
启动类加载器(引导类加载器,Bootstrap ClassLoader)
扩展类加载器(Extension ClassLoader)
应用程序类加载器(系统类加载器,AppClassLoader)
用户自定义类加载器
优点:
避免类的重复加载
保护程序安全,防止核心API被随意篡改
为何破坏双亲委派机制:
正常情况下,用户代码是依赖核心类库的,所以按照正常的双亲委派加载流程是没问题的,但是在加载核心类库时,如果需要使用用户代码,双亲委派流程就无法满足。
例如使用DriverManager.getConnection获取连接,DriverManager是由根类加载器Bootstrap加载的,在加载DriverManager时,会执行其静态方法,加载初始驱动程序,也就是Driver接口的实现类;但是这些实现类基本都是第三方厂商提供的,根据双亲委派原则,第三方的类不可能被根类加载器加载。
例如SPI机制*(Service Provider Interface,java 提供的一套用来被第三方实现的API,他可以用来启用框架扩展和替换组件,基于接口编程+策略模式+配置文件 组合实现的动态加载机制。)*,需要破坏双亲委派机制。
如何破坏:
实现关键,通过java.util.ServiceLoader#load(java.lang.Class<S>)
中通过线程上下文件类加载器(Thread Context ClassLoader)拿到用户自定义的实现类。
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
举例:
java.sql.Driver是最为典型的 SPI 接口,java.sql.DriverManager
通过扫包的方式拿到指定的实现类,完成 DriverManager
的初始化。初始化时在其loadInitialDrivers
方法中调用ServiceLoader#load
shardingsphere的SPI实现,在NewInstanceServiceLoader
初始化接口和子类关系的集合,缓存了所有SPI接口和子类的关系。在其register(java.lang.Class<T>)
方法调用ServiceLoader#load
Java Hotspot 虚拟机可以分析新创建对象的使用范围,并决定是否在 Java 堆上分配内存的一项技术。在方法中创建对象之后,如果这个对象除了在方法体中还在其它地方被引用了,此时如果方法执行完毕,由于该对象有被引用,所以 GC 有可能是无法立即回收的。通过此方法判断哪些对象是可以存储在栈内存中而不用存储在堆内存中的,从而让其随着线程的消逝而消逝,进而减少了 GC 发生的频率。
逃逸状态:
未逃逸时优化:
保证可见性;不保证原子性;禁止指令重排
**实现原理:**总线嗅探技术,每个处理器通过嗅探在总线上传播的数据,基于MESI协议来检查自己缓存值是否过期了,当处理器发现自己的缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置为无效状态,当处理器对这个数据进行修改操作的时候,会重新从内存中把数据读取到处理器缓存中。
**MESI协议:**对缓存行定义了四种状态,M: 被修改(Modified)、E: 独享的(Exclusive)、S: 共享的(Shared)、I: 无效的(Invalid)。当CPU写数据时,如果发现操作的变量是共享变量,即在其它CPU中也存在该变量的副本,会发出信号通知其它CPU将该内存变量的缓存行设置为无效,因此当其它CPU读取这个变量的时,发现自己缓存该变量的缓存行是无效的,那么它就会从内存中重新读取。
**存在隐患:**总线风暴,由于Volatile的MESI缓存一致性协议,需要不断的从主内存嗅探和CAS循环,无效的交互会导致总线带宽达到峰值。
可重入锁就是递归锁,可避免递归调用时死锁。
Executors.newFixedThreadPool(int i) :创建一个拥有 i 个线程的线程池
Executors.newSingleThreadExecutor:创建一个只有1个线程的 单线程池
Executors.newCacheThreadPool(); 创建一个可扩容的线程池
当线程数大于核心线程数时,在终止前,多余的空闲线程等待新任务的最长时间。
随着任务数量不断上升,线程池会不断的创建线程,直到到达核心线程数,就不创建线程了,这时多余的任务通过加入阻塞队列来运行,当超出(阻塞队列长度+核心线程数)时,这时不得不扩大线程个数来满足当前任务的运行,这时就需要创建新的线程了(最大线程数起作用),上限是最大线程数,那么,超出核心线程数并小于最大线程数的,可能新创建的这(最大线程数-核心线程数)个线程相当于是"借"的,如果这些线程空闲时间超过keepAliveTime
,就会被退出。keepAliveTime=0
时,"借来"的工作线程是不等待直接退出的。
优点:
缺点:
采用了2PC的方案来提交事务消息,确认 Producer 端的消息发送与本地事务执行的原子性。
Producer向Broker发送预处理消息,此时消息还未被投递出去,Consumer还不能消费。发送预处理消息成功后,开始执行本地事务;如果本地事务执行成功,发送提交请求提交事务消息,消息会投递给Consumer。如果本地事务执行失败,发送回滚请求回滚事务消息,消息不会投递给Consumer。
Producer实现RocketMQLocalTransactionListener,executeLocalTransaction方法执行本地事务方法,当executeLocalTransaction方法返回UNKNOW以后,RocketMQ会每隔一段时间调用一次checkLocalTransaction方法。
https://blog.csdn.net/yangbaggio/article/details/106450715
生产阶段:
1、采取同步发送的方式或异步发送加回调的方式。
2、可设置自动重试。默认是重试三次。
3、broker宕机。设置多个Master节点。
broker存储阶段:
1、broker未写入磁盘时异常宕机。调整刷盘策略为同步刷盘,性能降低。
2、消息写入磁盘后磁盘损害。集群部署,主从模式,高可用。设置Master和Slave都刷完盘后才通知成功。
消费阶段:
处理结果确认,消费失败自动重试
https://blog.csdn.net/weixin_42295690/article/details/118147617
生产者通过MessageQueueSelector将顺序消息路由至同一队列,消费者也需保证此队列顺序消费。
**注意点:**需要解决热点数据问题,消费者顺序消费存在瓶颈问题,消费失败时无法跳过,需自己设计策略支持。
其他思路:
NameServe:
对Broker进行管理以及提供Broker的路由查询功能。
Broker管理
NameServer会接收并保存Broker的注册信息。并且每10秒检测注册的Broker的心跳信息,如果一个Broker两分钟内都没有向NameServer发送心跳信息,那么Nameserver就认为这个Broker已经不可用,将其从路由信息中剔除。
Broker路由信息查询
NameServer中会保存Broker集群里的每一个Broker的信息。NameServer中会保存Broker集群里的每一个Broker的信息。
Broker:
Broker是RocketMQ中的核心角色,负责存储消息、转发消息。
消息的存储
Broker在内存中接收完消息,就会对消息进行持久化。分为同步刷盘或异步刷盘,采用顺序写的方式进行持久化
消息的读取
通过consumequeue索引文件读取消息。consumequeue存储消息在commitLog中的偏移量以及消息的Tag Hash,消费者根据offset去commitLog中读取真正的消息信息。
消息的过滤
Broker在接收到这条消息后会根据消息的Tag的hashcode进行过滤,发给对应的消费者队列MessageQueue。
Consumer/Produce:
与Name Server集群中的其中一个节点(随机)建立长连接,定期从Name Server拉取Topic路由信息,并向提供Topic服务的Master Broker、Slave Broker建立长连接,且定时向Master Broker、Slave Broker发送心跳。
顺序写:
使用了磁盘顺序读写来提升的性能。Kafka的message是不断追加到本地磁盘文件末尾的,而不是随机的写入,这使得Kafka写入吞吐量得到了显著提升。Kafka是不会删除数据的,消费者基于Topic的offset判断读取到哪一条。
Page Cache:
利用了操作系统本身的Page Cache,更加简单可靠,基于内存的,读写速度得到了极大的提升。当数据的请求到达时,如果在 Cache 中存在该数据且是最新的,则直接将数据传递给用户程序,免除了对底层磁盘的操作,提高了性能。
首先,操作系统层面的缓存利用率会更高,因为存储的都是紧凑的字节结构而不是独立的对象。
其次,操作系统本身也对于Page Cache做了大量优化,提供了 write-behind、read-ahead以及flush等多种机制。
再者,即使服务进程重启,系统缓存依然不会消失,避免了in-process cache重建缓存的过程。
零拷贝:
使用了sendfile方法,允许操作系统将数据从Page Cache 直接发送到网络,只需要最后一步的copy操作将数据复制到 NIC 缓冲区, 这样避免重新复制数据 ,避免了数据在内核空间和用户空间之间穿梭。
分区分段+索引:
Kafka的message是按topic分类存储的,topic中的数据又是按照一个一个的partition即分区存储到不同broker节点。每个partition对应了操作系统上的一个文件夹,partition实际上又是按照segment分段存储的。Kafka又默认为分段后的数据文件建立了索引文件,就是文件系统上的.index文件。这种分区分段+索引的设计,不仅提升了数据读取的效率,同时也提高了数据操作的并行度。
批量读写和批量压缩:
Kafka删除数据有两种方式
Kafka删除数据的最小单位:segment。kafka清理时是不管该segment中的消息是否被消费过,仅仅依据时间和大小。
消费端:
关闭自动提交 offset,在处理完之后自己手动提交 offset,就可以保证数据不会丢。需注意做好消费幂等性。
kafka:
某个 broker的leader 机器宕机,然后重新选举 partition 的 leader,此时follower 刚好还有些数据没有同步,会丢失。
replication.factor
参数,要求partition 必须有至少 2 个副本min.insync.replicas
参数,要求一个 leader 至少感知到有至少一个 follower 还跟自己保持联系,没掉队,这样才能确保 leader 挂了还有一个 followeracks=all
:这个是要求每条数据,必须是写入所有 replica 之后,才能认为是写成功retries=MAX
(很大很大很大的一个值,无限次重试的意思):这个是要求一旦写入失败,就无限重试,卡在这里生产者:
producer 端设置 acks=all
,确保写入成功
本地事务,支持因逻辑异常导致的跨库事务,基于Spring框架的DataSourceTransactionManager实现。
自定义事务管理器,Spring启动时加载,根据配置的多个数据源创建各自对应DataSourceTransactionManager,事务操作时,遍历获取所有事务状态。提交或回滚操作时,同样遍历所有事务管理器进行操作。
本地事务:弱XA事务,完全支持因逻辑异常导致的跨库事务。不支持因网络、硬件异常导致的跨库事务。
两阶段事务-XA:服务宕机重启后,提交/回滚中的事务可自动恢复。SPI机制可实现XATransactionManager。默认的XA事务管理器为Atomikos,在项目的logs目录中会生成xa_tx.log。
https://segmentfault.com/a/1190000038183881?utm_source=tag-newest
方案1:强制进入自定义路由策略算法进行校验。关键实现ShardingRouteDecorator。
跟踪源码后发现ShardingSphere是先对sql进行解析检查是否存在指定路由字段,组织条件集合ShardingConditions,再执行路由引擎ShardingStandardRoutingEngine,根据ShardingConditions是否为空决定是否执行路由策略。
方案2:查看框架本身是否存在可配置项。
方案3:实现拦截器做统一拦截。
ShardingSphere框架无自定义拦截器。通过实现mybatisplus的InnerInterceptor.beforePrepare强制检查。
难点:sql解析,使用com.github.jsqlparser。
拓展:执行引擎连接模式。
1、内存限制模式:ShardingSphere对一次操作所耗费的数据库连接数量不做限制。对每张表创建一个新的数据库连接,并通过多线程的方式并发处理,以达成执行效率最大化。 并且在SQL满足条件情况下,优先选择流式归并,以防止出现内存溢出或避免频繁垃圾回收情况。 独立的数据库连接,能够持有查询结果集游标位置的引用,在需要获取相应数据时移动游标即可 。以结果集游标下移进行结果归并的方式,称之为流式归并,它无需将结果数据全数加载至内存,可以有效的节省内存资源,进而减少垃圾回收的频次。
2、连接限制模式:ShardingSphere严格控制对一次操作所耗费的数据库连接数量。每个库的每次操作只创建一个唯一的数据库连接,复用该数据库连接获取下一张分表的查询结果集之前,将当前的查询结果集全数加载至内存。
拓展:ShardingUpdateStatementValidator.validate shardingsphere校验sql方法
采用双倍扩容策略,避免数据迁移。扩容前每个节点的数据,有一半要迁移至一个新增节点中,对应关系比较简单。 具体操作如下(假设已有 2 个节点 A/B,要双倍扩容至 A/A2/B/B2 这 4 个节点):
ID%2=0 => A
改为 ID%4=0 => A, ID%4=2 => A2
; 原 ID%2=1 => B
改为 ID%4=1 => B, ID%4=3 => B2
。READ UNCOMMITTED:
写操作会加X排他锁,事务结束之后释放。读不会加任何锁,因此读取不会被排他锁拒绝。
READ COMMITED:
写操作:行级排他锁(X lock),那么当其他事务访问另一个事务正在update (除select操作外其他操作本质上都是写操作)的同一条记录时,事务的读操作会被阻塞。
读操作:基于MVCC,根据事务ID读取副本(read view)快照数据, 只要当前语句执行前已经提交的数据都是可见的,总是读最新的一份快照数据。
REPEATABLE READ:
写操作:首先对索引记录加上行锁(Record Lock),再对索引记录(无索引时会锁全表)两边的间隙加上间隙锁(Gap Lock)。加上间隙锁之后,其他事务就不能在这个间隙修改或者插入记录。
读操作:基于MVCC,只要当前语句执行前已经提交的数据都是可见的,读事务开始时的行数据版本快照。
(幻读:在一个事务中,同一个范围内的记录被读取时,其他事务向这个范围添加了新的记录。本事务插入同样的数据却出现了错误。mysql在此级别下使用Next-Key锁,行锁+间隙锁,某种程度上解决幻读)
SERIALIZABLE:
每一个select请求下获得读锁(共享锁),在每一个update操作下尝试获得写锁。
哨兵模式,11组实例,每组实例一主一从,单机2C8G。
https://www.cnblogs.com/zhonglongbo/p/13128955.html
海量请求查询压根就不存在的数据,那么这些海量请求都会落到数据库中。
缓存空数据
存在数据一致性问题,该key对应数据写入时,需注意数据更新;
恶意攻击查询的key往往各不相同而且数据量大,需要存储所有空数据的key,而这些恶意攻击的key往往各不相同,而且同一个key往往只请求一次。因此即使缓存了这些空数据的key,由于不再使用第二次,因此也起不了保护数据库的作用
BloomFilter检查数据库中是否存在
使用互斥锁排队,阻塞减轻数据库压力?
场景一:缓存因某种原因发生了宕机,那么原本被缓存抵挡的海量查询请求就会涌向数据库。此时数据库如果抵挡不了这巨大的压力,它就会崩溃。
事前:redis高可用,主从+哨兵,redis cluster,避免全盘崩溃
事中:本地ehcache缓存 + hystrix限流&降级,避免MySQL被打死
事后:redis持久化,快速恢复缓存数据,一般重启,自动从磁盘上加载数据恢复内存中的数据。
场景二:缓存在同一时间内大量键过期(失效),接着来的一大波请求瞬间都落在了数据库中导致连接异常。
多个redis的client同时set key带来的更新时序性等问题。
**异步复制:**因为master -> slave的复制是异步的,所以可能有部分数据还没复制到slave,master就宕机了。这时Redis进行了主备切换,slave节点成了master节点,此时原master内存中的数据就丢失了。
**脑裂:**master节点出现了异常(比如网络异常),跟其他slave机器不能连接,但是实际上master还运行着,此时哨兵可能就会认为master宕机了,然后开启选举,将其他slave切换成了master。集群里就会有两个master,也就是所谓的脑裂。此时client还没来得及切换到新的master,还继续写向旧master写数据。当网络分区恢复正常后,旧master会被作为一个slave挂到新的master上去,自己的数据会清空,重新从新的master复制数据。因此之前client写的数据就会丢失。
**解决方案:**通过配置要求至少有1个slave,数据复制和同步的延迟不能超过10秒。master如果不能继续给指定数量的slave发送数据,而且slave超过10秒没有给自己ack消息,那么就直接拒绝客户端的写请求。减少存在的数据丢失范围。
定义:
将Redis中的数据,每个一段时间,进行数据持久化,生成快照文件。
优点:
定期生成此时刻的完整数据快照,时候作为冷备份数据文件;
生成RDB文件时,主进程会fork()一个子进程来处理,对Redis读写影响小,保持Redis高性能;
数据恢复速度更快;
缺点:
由于存在时间间隔,Redis故障时可能丢失一段时间范围内数据;
如果数据文件特别大或备份间隔时间特别长,会对Redis服务产生影响;
定义:
对每条写入命令作为日志,以append-only的模式写入一个日志文件,Redis重启的时候,可以通过回放AOF日志中的写入指令来重新构建整个数据集。
优点:
记录所有的写操作,数据完整性更好,相比RDB可以更好的保护数据不丢失;
AOF日志文件以append-only模式写入,所有没有任何磁盘寻址开销,写入性能非常高,而且文件不容易破损,即使文件尾部破损,也很容易快速修复;
适合做灾难性的误删除的紧急恢复,比如某人不小心用了 flushall命令,清空了整个Redis数据,只要这个时候后台rewrite还没有发生,那么就可以立即拷贝AOF文件,将最后一条flushall命令删除了,然后再将该AOF文件放回去,就可以通过恢复机制,自动回复所有的数据;
缺点:
AOF日志文件比RDB日志文件更大;
支持写QPS会比RDB支持的写QPS低;
做数据恢复的时候,会比较慢,还有做冷备,定期的备份,不太方便,可能要自己手动写复杂的脚本去做;
通过IO多路复用机制监听了多个socket,队列中插入一个socket,文件事件分派器就是将队列中的socket取出来,分派到对应的处理器,然后通过ServerSocket创建一个与客户端一对一对应的socket,在处理器处理完成后,才会从队列中在取出一个。
原因:
效率高:
继承HashMap,增加一层双向链表数据结构,重写HashMap的afterNodeAccess/afterNodeInsertion/afterNodeRemoval方法,保证双向链表的更新。
**顺序性:**依赖HashMap基础上增加双向链表实现,按照插入的顺序从头部或者从尾部迭代。
**accessOrder:**默认false,按插入顺序排序;true,按访问顺序排序(也就是插入和访问都会将当前节点放置到尾部,尾部代表的是最近访问的数据)
**LRU实现:**重写removeEldestEntry,决定何时删除队首元素。
顺序性:基于底层红黑树的数据存储结构,按Key的自然顺序(如整数从小到大),也可以指定比较函数。但不是插入的顺序。
生产集群我们部署了8台机器,3个Master节点,5个数据节点。集群总内存是237.3 GB。数据总量3.3TB。
RPC是指远程过程调用,两台服务器A、B,A上的应用想要访问B上应用提供的函数方法,由于不在同一内存空间,不能直接调用,需要通过网络来表达调用语义和传达调用数据。完整的 RPC 实现一般会包含有 传输协议 和 序列化协议 这两个。
需要解决问题:
// Client端
// int l_times_r = Call(ServerAddr, Multiply, lvalue, rvalue)
1. 将这个调用映射为Call ID。这里假设用最简单的字符串当Call ID的方法
2. 将Call ID,lvalue和rvalue序列化。可以直接将它们的值以二进制形式打包
3. 把2中得到的数据包发送给ServerAddr,这需要使用网络传输层
4. 等待服务器返回结果
5. 如果服务器调用成功,那么就将结果反序列化,并赋给l_times_r
// Server端
1. 在本地维护一个Call ID到函数指针的映射call_id_map,可以用std::map<std::string, std::function<>>
2. 等待请求
3. 得到一个请求后,将其数据包反序列化,得到Call ID
4. 通过在call_id_map中查找,得到相应的函数指针
5. 将lvalue和rvalue反序列化后,在本地调用Multiply函数,得到结果
6. 将结果序列化后通过网络返回给Client
dubbo协议:
默认协议,单一长连接,NIO异步传输,TCP协议,基于Hessian二进制序列化协议,适用于传输数据量很小(每次请求在100kb以内),但是并发量很高的场景。不适用于传输大文件或超大字符串。场景:常规远程服务方法调用。
为了要支持高并发场景,一般是服务提供者就几台机器,但是服务消费者有上百台,可能每天调用量达到上亿次!此时用长连接是最合适的,就是跟每个服务消费者维持一个长连接就可以,可能总共就100个连接。然后后面直接基于长连接NIO异步通信,可以支撑高并发请求。因为走的是单一长连接,所以传输数据量太大的话,会导致并发能力降低。所以一般建议是传输数据量很小,支撑高并发访问。
rmi协议:
多个短连接,同步传输,TCP协议,基于Java 标准二进制序列化,适合传入传出参数数据包大小混合,消费者和提供者数量差不多,适用于文件的传输。场景:常规远程服务方法调用,与原生RMI服务互操作
hessian协议:
多个短连接,同步传输,HTTP协议,基于Hessian二进制序列化,适用于传入传出参数数据包较大,提供者数量比消费者数量还多,提供者压力较大,适用于文件的传输。场景:页面传输,文件传输,或与原生hessian服务互操作。
http协议:
多个短连接,同步传输,HTTP协议,基于表单序列化,适用于传入传出参数数据包大小混合,提供者比消费者个数多,可用浏览器查看,可用表单或URL传入参数,暂不支持传文件。场景:需同时给应用程序和浏览器 JS 使用的服务。
webservice协议:
多个短连接,同步传输,HTTP协议,基于SOAP文本序列化。场景:系统集成,跨语言调用。
failover cluster模式 失败自动切换,自动重试其他机器,默认就是这个,常见于读操作
failfast cluster模式 一次调用失败就立即失败,常见于写操作
failsafe cluster模式 出现异常时忽略掉,常用于不重要的接口调用,比如记录日志
failbackc cluster模式 失败了后台自动记录请求,然后定时重发,比较适合于写消息队列这种
forking cluster 并行调用多个provider,只要一个成功就立即返回
broadcacst cluster 逐个调用所有的provider
思考点:
单应用内跨库事务保证一致性?
非强一致性要求应用,性能考虑常规使用本地事务,数据库宕机异常导致的事务不一致可依赖MySQL 自身XA事务日志回滚。或者在功能设计时允许重试补偿保证最终一致性。
事务方法尽量保证颗粒度低,集中。
多应用间事务保证一致性?
优先选择最终一致性方案,性能优势,需注意处理幂等性,失败重试及兜底方案。
事务实时性要求比较高选择TCC等事务方案,功能设计要求较高。需要主事务服务扮演协调者角色,次事务服务提供查询、提交、回滚接口。
适当考虑批量处理提升性能,减少依赖方。
适用于缓存的数据:
一级缓存:
默认开启。Mybaits中SqlSession对象的缓存,依赖SqlSession对象,SqlSession中提供一块Map结构的区域,存放的是返回数据的对象。带Spring事务的方法时可以使用到一级缓存。
二级缓存:
默认关闭的,需在映射文件配置开启同时POJO类实现Serializable接口。Mybatis中SqlSessionFactory对象的缓存,由同一个SqlSessionFactory对象创建的SqlSession共享其二级缓存,存放的是数据,不是对象。一个SqlSessionFactory对象包括多个SqlSession对象。与事务无关。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。