文章目录
REDIS
- 为什么使用redis?
考虑性能和并发。
性能上:我们在碰到需要执行耗时特别久,且结果不频繁变动的SQL,就特别适合将运行结果放入缓存。Redis基于内存级别的缓存。
并发上:高并发下优先访问redis数据,避免直接访问数据库 - 支持的数据类型
string,hash(登录存储用户信息),list(消息队列),set,zset(有序集合) - redis存在哪些问题
缓存和数据库双写一致性:先更新数据库,再删缓存。如果项目要去强一致性,则不能使用缓存。
缓存雪崩:缓存同时大量失效,导致请求全部落在数据库。解决方法:双缓存;缓存失效时间加上随机值,避免集体雪崩
缓存击穿:热点key失效。解决方法:热点key永不失效;在数据库访问时加互斥锁(会降低性能)
缓存穿透:大量不存在的key传入,请求落在数据库。解决方法:过滤非法key(redis数据结构,布隆过滤器);无效key存入redis(意义不大)
缓存并发竞争:锁;串行控制 - 单线程redis性能快的原因
纯内存操作
单线程操作,避免了频繁的上下文切换
采用了非阻塞I/O多路复用机制 - redis的过期策略以及内存淘汰机制
redis采用的是定期删除+惰性删除策略。- 为什么不用定时删除策略?
定时删除,用一个定时器来负责监视key,过期则自动删除。虽然内存及时释放,但是十分消耗CPU资源。在大并发请求下,CPU要将时间应用在处理请求,而不是删除key。 - 定期删除+惰性删除是如何工作的呢?
定期删除,redis默认每个100ms检查,是否有过期的key,有过期key则删除。需要说明的是,redis不是每个100ms将所有的key检查一次,而是随机抽取进行检查(如果每隔100ms,全部key进行检查,redis岂不是卡死)。因此,如果只采用定期删除策略,会导致很多key到时间没有删除。
于是,惰性删除派上用场。也就是说在你获取某个key的时候,redis会检查一下,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除。 - 采用定期删除+惰性删除就没其他问题了么?
如果定期删除没删除key。然后你也没即时去请求key,也就是说惰性删除也没生效。这样,redis的内存会越来越高。那么就应该采用内存淘汰机制。
当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。推荐使用,目前项目在用这种。
- 为什么不用定时删除策略?
jedis 连接 redis sentinel
- 构建参数
masterName——主节点名。
sentinels——Sentinel节点集合。
poolConfig——common-pool连接池配置。 最大连接数,空闲连接数,最长等待时间
password——主节点密码。(这个是主节点密码,而不是redis sentinel的密码)
分布式锁
- 实现分布式锁:
- 设置锁的默认超时时间和加锁的超时时间,初始化lua脚本
- 设置SetParam模式,nx:当不存在时设置key
- tryLock 在set操作完成返回OK
- unlock 执行脚本删除context中的锁,
- 我们需要判断锁是否是自己的,基于value值来判断
- 分布式锁特点:
- 互斥性: 同一时刻只能有一个线程持有锁
- 可重入性: 同一节点上的同一个线程如果获取了锁之后能够再次获取锁
- 锁超时:和J.U.C中的锁一样支持锁超时,防止死锁
- 高性能和高可用: 加锁和解锁需要高效,同时也需要保证高可用,防止分布式锁失效
- 具备阻塞和非阻塞性:能够及时从阻塞状态中被唤醒
- Redis集群的情况下,需要用redisson
缓存
- put方法 序列化value值,setex方法加入缓存池
- 全局唯一键值 incr方法;查找指定模式key keys(pattern)
- get方法 获取value,deserialize
数据结构
- 基础数据结构: String(缓存),list(消息队列),hash(类似map),set(无序不重复),
- zset 有序集合 数量较小的情况下底层实现是压缩集合(维护一个score),跳跃表
REDIS常见问题
- Redis 的两种持久化策略: AOF 与 RDB
- RDB: 定期从内存到磁盘生成快照,可能存在数据缺失
- AOF:将命令记入到文件中,随时可恢复
- 可以同时加载两种文件,以AOF为准
- 主从复制:
- 原理:
当从服务器启动时,它会向主服务器发送一条同步命令,请求复制主服务器上的数据;
主服务器收到同步命令后,会开启一个新的后台线程,将数据发送给从服务器;
从服务器接收到数据后,将其存储在本地,并周期性地向主服务器发送同步命令,以获取更新的数据;
当主服务器接收到写操作时,会将写操作发送给所有连接的从服务器,并在本地执行写操作,从而保证数据的一致性。 - 优点:可用,可靠,高效(从服务器减轻负载)
- 原理:
- 哨兵机制:哨兵用来监控主从服务器的状态
- 优点:保障了redis的优点
- 原理:
- 每个 Redis 哨兵服务器都会周期性地向主服务器和从服务器发送 INFO 命令,以获取服务器的状态信息;
- 如果某个 Redis 哨兵服务器检测到主服务器宕机,则会向其他 Redis 哨兵服务器发送消息,请求选举一个新的主服务器;
- Redis 哨兵服务器会通过投票机制选举一个新的主服务器,并将其通知给所有 Redis 客户端和 Redis 哨兵服务器;
- 新的主服务器会将数据同步到从服务器上,并开始提供服务,从服务器会自动切换到新的主服务器上。
- 哨兵机制中的 quorum:投票机制,需要有多少满足进行新的主节点选举
JAVA
JAVA21 新特性
- 虚拟线程:为了处理IO密集型的任务
- 顺序集合
- switch的模式匹配
基础点
- try-catch语句中带着return:
- 不管有没有出现异常,finally块中代码都会执行;当try和catch中有return时,finally仍然会执行;
- finally是在return后面的表达式运算之后执行的;
- finally有return,会覆盖try中的返回值
- finally改变了try中的返回值,如果是基本类型,无法改变;否则可以改变
- new String创建的对象在堆上,普通字符串指向的时字符串常量池,两者是不同的。
数据类型
- Hashset 实现是存了一个hashmap
- HashMap底层是维护一个链表,超过一定范围转红黑树
- ConcurrentHashMap 和 HashTable区别:
HashTable使用的是Synchronized 关键字修饰,ConcurrentHashMap是使用了锁分段技术来保证线程安全的。 Hashtable中采用的锁机制是一次锁住整个hash表,从而在同一时刻只能由一个线程对其进行操作;而ConcurrentHashMap 中则是一次锁住一个桶
JUC
- 并发容器:解决多线程下的线程安全问题
- ConcurrentHashMap 和 HashTable 的区别(两者均为线程安全的数据结构)
- CMap使用了分段锁,H用的全局锁,H性能差些
- CMap可以动态调整大小,H大小固定,调整时需要获取全局锁
- CMap支持空值,H不支持
- 并发容器实现原理:
- 锁:全局锁,分段锁
- 写时复制:适合多读少写的场景
- ConcurrentHashMap 和 HashTable 的区别(两者均为线程安全的数据结构)
- 阻塞队列:
- 定义:阻塞队列是一种线程安全的队列,在队列已满或为空时,入队和出队操作会阻塞线程,直到队列不为空或不满。阻塞队列通常被用于生产者-消费者模型中,以实现线程间的同步和通信
- ArrayBlockingQueue 与 LinkedBlockingQueue 有何不同:内部实现一个是数组,一个是链表
- ThreadLocal
- 内部实现:threadlocalmap存储 线程号->数据
- 内存泄漏问题解决:对于弱引用key,GC后会出现null的情况。调用set,get,remove方法时,会清理key为null的键值对。建议使用后手动调用remove
- 线程池:
- 参数:
- 核心线程数:线程池中始终保持的线程数;
- 最大线程数:线程池中最多允许的线程数(可以这样理解:最大线程数 - 核心线程数 = 非核心线程数,非核心线程可以看成「临时工」 ,任务队列满了的时候,才会启动非核心线程);
- 任务队列:存放等待执行的任务的队列;
- 线程空闲时间:线程在空闲状态下的最大存活时间;
- 线程池拒绝策略:当任务队列已满且线程数量达到最大值时,线程池如何处理新的任务。
- 饱和策略:默认丢弃并抛异常,丢弃,调用者起线程,丢弃最老的阻塞队列中的任务
- 核心线程数的设置原则:CPU密集型:核心线程数 = CPU核数 + 1【机器学习、视频转码】
IO密集型:核心线程数 = CPU核数 * 2 【Web应用】
- 参数:
JVM
- 内存区域
- 程序计数器:线程独立,记录执行的位置
- 虚拟栈:线程启动时分配独立
- JAVA堆:进程内共享,1eden,2survior,old
- 方法区:常量,静态变量,代码信息等
- String常量池位于方法区
- java中堆栈的区别:
- 堆动态内存,栈先入后出
- 堆存放实例变量,栈存放基本类型变量,对象引用
- 栈由JVM管理,堆由垃圾回收算法管理空间
- 堆会产生内存碎片,栈不会
- 垃圾回收机制
- 主要步骤
- 标记:根搜索,可达性分析
- 清除:清除没有被标记的对象
- 整理:整理活的对象,消除内存碎片
- 复制算法:内存一分为2,每次标记之后,将存货对象移动到另一半中。优点是没有内存碎片,年轻代存活对象少,使用这个方式回收
- minor GC 和 full GC
- minor GC发生在年轻代,存活对象从eden移动到survior,survior采用复制算法,清除死亡对象
- full GC 清除整个堆的死亡对象
- 常见回收器:
- CMS:并发收集,标志-清除算法
- G1:并发收集,按区域划分,标记-整理算法
- 主要步骤
- finalize方法:GC时调用,当前对象会被JVM放在队列中,再次GC时不会执行。不推荐使用
- JMM:java内存模型:共享变量存于主内存,每个线程一个本地内存
- volatile变量:轻量级锁,不引起线程上下文切换。当写一个volatile变量时,保证可见性不保证原子性;禁止指令重排。
- 实现原理:内存屏障
- 单例模式双重锁,instance是volatile变量的原因:在第二次new Instance的时候,有可能指令重排,导致另一个线程在第一次判断中直接返回未初始化的对象
类加载,反射
- 双亲委派:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成
- loadClass:会返回加载过的类
- 类加载的范围:
- 启动类加载器加载的是:存放在<JAVA_HOME>\lib 目录中的,或者被 -Xbootclasspath 参数所指定的路径中的,并且是虚拟机识别的。
- 扩展类加载器加载的是:<JAVA_HOME>\lib\ext 目录中的,或者是被 java.ext.dirs 系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。
- 应用类加载器加载的是:用户类路径(ClassPath)上所指定的类库。
- 破坏方式:findClass方法直接加载
- Class.forname JVM层的native方法逻辑:
通过计算类全名的hash值和类全名底层的loader所对应的SystemDictionary的查询类是否已经加载。
如果没有查询到,判断是否存在classloader,来决定是bootstrap classloader还是JavaCalls#call调用自定义的classloader的loadClass去加载类。 - JDK 动态代理和CGLIB动态代理的区别:
- JDK:需要被代理类实现接口,代理类也实现同样的接口。用了反射调用原方法
- JDK的动态代理只能基于接口实现,不能基于继承来实现?
答:因为Java中不支持多继承,而JDK的动态代理在创建代理对象时,默认让代理对象继承了Proxy类,所以JDK只能通过接口去实现动态代理。
- JDK的动态代理只能基于接口实现,不能基于继承来实现?
- CGLIB:继承被代理类,实现MthodIntercepter接口拦截方法,Enhance类实现功能增强
- JDK:需要被代理类实现接口,代理类也实现同样的接口。用了反射调用原方法
设计模式
- SOLID:单一职责(单一模块负责一类任务),开闭(易扩展拒修改),里氏替换(子类可以无条件替换基类),接口隔离(类之间的依赖是尽可能小的接口),依赖反转(依赖抽象接口而非具体实现)
迪米特:两个软件实体无需直接通信,通过第三方转发
合成复用:尽量使用组合聚合,少用继承 - 创建型:
- 单例模式:
- 作用域:进程
- 懒汉式:第一次调用初始化
- 饿汉式:类加载时初始化
- 双重检测:有实例时,不进入加锁逻辑
- 线程唯一的单例:hashmap存储 线程id->实例
- 集群唯一单例:外部存储区,分布式锁
- 作用域:进程
- 工厂模式:抽象工厂,具体工厂,抽象产品,具体产品。具体工厂创建具体产品
- 建造者模式:@Builder注解,将复杂对象的构造和表示分离
- 单例模式:
- 结构型:
- 代理模式:spring获得的bean不是原始的bean,而是经过了处理的代理对象
- 行为型:
- 策略模式:具体的策略调用不同的方法
- 观察者模式:一对多的依赖关系,被观察对象发生变更,观察者全部得到通知
- 同步阻塞:被观察者中注册观察者,更新时遍历通知
- 异步非阻塞:EventBus
Spring
IOC AOP
- 注解:设计通用功能
- 元注解:修饰注解的注解
- @Retention 表示注解保留到什么时候;
- @Target 表示注解可以标注在什么地方;
- @Documented 表示在使用 javadoc 生成文档的时候,被修饰的注解会被记录下来。
- @Inherited 表示被修饰的类的注解的子类也继承了该注解。SpringBoot 的启动类上使用的注解 @SpringBootApplication 就是被 @Inherited 修饰的;
- @Repeatable 表示重复注解,相同的注解可以重复修饰目标。用于注解的属性是数组的时候
- 切点和切面
- @PointCut 获取切点位置,比如规定注解的位置。修饰在Aspect的方法上
- @Around @Before @After @AfterRunning @AfterThrowing 不同位置执行具体切面逻辑,参数:joinPoint
- IOC的含义原理:
IOC(Inversion of Control,控制倒转),意思是对象之间的关系不再由传统的程序来控制,而是由spring容器来统一控制这些对象创建、协调、销毁,而对象只需要完成业务逻辑即可
Springboot
- 启动流程:
- @SpringBootApplication 注解 通过源码发现该注解只是@Configuration,@EnableAutoConfiguration,@ComponentScan合并
- 初始化SpringApplication
- 准备Environment prepareEnvironment方法,将环境包装为propertysource,这个里面包括了microservice.yaml,application.yml的配置
- 发布事件
- 创建上下文、bean。先创建context,然后调用prepareContext,绑定environment。注册一些单例bean,加载source到context。
- 刷新上下文,此处做了spring bean的代理,初始化等工作,刷新结束后beanfactory里面加载了需要管理的全部bean对象
数据库
mybatis
- @Mapper 是对单个接口类的注解。单个操作。@MapperScan 是对整个包下的所有的接口类的注解。是批量的操作。使用 @MapperScan 后,接口类 就不需要使用 @Mapper 注解
- mybatis加载流程
- spring boot 自动扫描MybatisAutoConfiguration配置类
- spring yml文件中配置数据源
- spring创建sqlsessionfactory,@mapper注解创建实例
- 二级缓存
二级缓存的原理和一级缓存原理一样,第一次查询,会将数据放入缓存中,然后第二次查询则会直接去缓存中取。但是一级缓存是基于sqlSession的,而二级缓存是基于mapper文件的namespace的,也就是说多个sqlSession可以共享一个mapper中的二级缓存区域,并且如果两个mapper的namespace 相同,即使是两个mapper,那么这两个mapper中执行sql查询到的数据也将存在相同的二级缓存区域中。
关系数据库
- 三大范式:
- 每一项都是不可分割的原子项
- 非主键必须完全依赖于主键,如果不是需要分表
- 非主键不可依赖于其他非主键。(不可传递依赖主属性)
- b+树相比于b树的查询优势:
- b+树的中间节点不保存数据,所以磁盘页能容纳更多节点元素,更“矮胖”;
- b+树查询必须查找到叶子节点,b树只要匹配到即可不用管元素位置,因此b+树查找更稳定(并不慢);
- 对于范围查找来说,b+树只需遍历叶子节点链表即可,b树却需要重复地中序遍历
- 如果一个数据库声称支持事务的操作,那么该数据库必须要具备以下四个特性:
- 原子性(Atomicity)
原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚。
原理:undolog保证这个实现 - 一致性(Consistency)
一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
原理:代码层面保障 - 隔离性(Isolation)
隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
原理:MVCC版本控制 - 持久性(Durability)
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
原理:redolog保证数据库遇到故障可以恢复
- 原子性(Atomicity)
- 事务的四个隔离等级和解决的问题。
读未提交(脏读)读已提交(不可重复读)可重复读(幻读)串行化
读提交,就是一个事务要等另一个事务提交后才能读取数据。
重复读,就是在开始读取数据(事务开启)时,不再允许修改操作。
串行化,是最高的事务隔离级别,在该级别下,事务串行化顺序执行。 - mvcc怎么是实现的。
MVCC(Multi-Version Concurrency Control | 多版本并发控制)
InnoDB通过为每一行记录添加两个额外的隐藏的值来实现MVCC,这两个值一个记录这行数据何时被创建,另外一个记录这行数据何时过期(或者被删除)。但是InnoDB并不存储这些事件发生时的实际时间,相反它只存储这些事件发生时的系统版本号(LSN)。这是一个随着事务的创建而不断增长的数字。每个事务在事务开始时会记录它自己的系统版本号。每个查询必须去检查每行数据的版本号与事务的版本号是否相同。 - MVCC实现可重复读
普通读:每次记录有版本控制,比较每个事务开启时版本,决定要不要读取对应的记录。每条记录维护一个创建版本和删除版本,解决幻读就是只能读取创建版本在当前版本之前,删除版本在当前版本之后或为空的记录。
当前读:next-key lock(记录锁+间隙锁) - mysql innoDB 几种锁
- 共享锁: 读取记录时加
- 排它锁:DML语句时加
- 意向锁:当前表记录有锁了,会给这个表一个标识
- 记录锁:行锁,按照主键更新,且必须为精准匹配的语句会加。不是主键会退化为临键锁
- 间隙锁:解决幻读的锁(当前读),锁住查询的区间(树和叶子节点的间隙)
- 自增锁:保证是自增有序
- 乐观锁和悲观锁
乐观锁:假设读的时候不会有别的用户来写,锁开销小。基于版本控制mvcc实现。java中原子类的实现方式:CAS(compare and set)
悲观锁:假设每次读都会有别的用户来写,锁开销大 - 自增主键和uuid
- 自增主键优势:有顺序,检索快;占用空间小;速度快;做数据库拆分表拆分容易。劣势:合并表的效率低;想插入对应主键id不容易
- uuid优势:容易做拆表。劣势:占用空间大;join性能低
- mysql日志
- binlog:记录了数据库的操作,sql。对于事务只有commit之后才会记录log
- redolog和undolog:保证数据一致性,原子性
- 数据库结构优化
- 字段很多的表,其中有些字段不常用,可以拆分成多个表(横向拆分:按照条件分割行;纵向拆分:拆分字段)
- 增加中间表:对于经常需要联合查询的表,建立中间表用于查询
- 增加冗余字段:不能一味追求ACID
- 主从复制:
- 从服务器用作数据备份
- 实现读写分离
- 主节点出问题,可以实时切换
- mysql 语句执行步骤:连接器,分析器,优化器,执行器
索引
- 数据库索引
数据库索引其实就是为了使查询数据效率快。
分类:- 聚集索引(主键索引):在数据库里面,所有行数都会按照主键索引进行排序。
- 非聚集索引:就是给普通字段加上索引。
- 联合索引:就是好几个字段组成的索引,称为联合索引。
联合索引遵循最左匹配原则。
- 聚集索引在叶节点储存了全部行数据(索引和数据放在一起,减少磁盘IO,一般用于频繁排序和查询的字段)
非聚集索引在叶节点只储存了聚集索引键值,然后需要通过聚集索引键值访问行数据。(此处会出现mysql回表的现象)
Mysql高版本针对这个问题的优化:索引下推:判断下一个检索字段是不是也在联合索引中,如果是,先过滤,可以减少回表次数- 只能用于range、 ref、 eq_ref、ref_or_null访问方法;
- 只能用于InnoDB和 MyISAM存储引擎及其分区表;
- 对存储引擎来说,索引下推只适用于二级索引
- 索引的优缺点:
优点:加快查询速度(B+);唯一性索引保证数据唯一
缺点:增加了额外维护耗时;增加存储索引空间 - mysql 索引实现,InnoDb底层实现:
底层数据页按照双向链表组成,数据页中记录按照主键从小到大的值组成单项列表。通过主键查询时可以二分法快速定位
数据库索引 - Hash索引和B+索引区别:
Hash索引 | B+索引 | |
---|---|---|
等值查询 | 查询性能快,不稳定,某个键值存在大量重复,效率会比较差 | 查询效率稳定logN |
范围查询 | 不支持 | 支持 |
模糊查询和最左前缀匹配 | 不支持 | 支持 |
postgre sql
- 和mysql的对比:
mysql | postgresql | |
---|---|---|
写性能 | 采用写锁实现并发 | 采用MVCC控制并发,频繁写入效果好 |
读性能 | 多用户单一进程,读取效率好 | 每个连接到数据库都有一个对应进程pid,并分配内存 |
支持的类型 | 字符,数字,日期,json | mysql支持类型 + 枚举,地址,XML等 |
索引 | B+树索引 | 表达式索引、部分索引和带有树的哈希索引 |
ACID | 在innoDB下支持 | 全量支持 |
隔离级别 | 默认可重复读 | 默认读提交,可重复读的级别下幻读也是不允许的。不存在读未提交 |
- 慢查询定位:
- 修改/pgsql/data/postgresql.conf中的log_min_duration_statement,单位毫秒。例log_min_duration_statement=5000
- 查看当前设置:show log_min_duration_statement;
rabbitmq
- 设置全系统唯一的topic,调用作为广播发布
- 生产端:
- init:维护一个map,检查routingkey下面有没有mqinfo,如果有,发布exchange,绑定queue
检查消息有没有超过阈值 - 按照配置文件,生成rabbittemple bean对象
- convertAndSend方法发送消息
- init:维护一个map,检查routingkey下面有没有mqinfo,如果有,发布exchange,绑定queue
- 消费端:
- 实现一个接口,根据消息的cosumer队列,回调对应的方法,起一个@RabbitListener 监听消息
- 生产端:
- 优点:异步处理 ,应用解耦 ,流量削锋
- 缺点:系统复杂程度提高,一致性问题
- 问题:
- 消息的顺序问题:
- 要保证全局有序:只使用一个分片
- 要保证局部有序:根据不同的设备id(或者其他标识)进行hash,保证同一个设备的id分发到同一个分片中
- 扩容中的局部有序:离线场景可以先消费完存量信息再扩容;在线场景可以搞个临时topic,先将消息暂时堆积,待扩容后,按新的路由规则重新发送
- 顺序消息,如果某条失败了怎么办?会不会一直阻塞?
- 如果失败,不会提交消费位移,系统会自动重试(有重试上限),此时会阻塞后面的消息消费,直到这条消息处理完
- 如果这个消息达到重试上限,依然失败,会进入死信队列,可以继续处理后面的消息
- 消息的重复问题:消费端保证幂等性
- 消息的顺序问题:
分布式
- 一致性哈希算法
一致性哈希算法是在哈希算法基础上提出的,在动态变化的分布式环境中,哈希算法应该满足的几个条件:平衡性、单调性和分散性。
①平衡性是指hash的结果应该平均分配到各个节点,这样从算法上解决了负载均衡问题。
②单调性是指在新增或者删减节点时,不影响系统正常运行。
③分散性是指数据应该分散地存放在分布式集群中的各个节点(节点自己可以有备份),不必每个节点都存储所有的数据- 实现:hash环上面分布不同节点和key值,增加删除节点,只会影响顺时针的节点
- 保证均衡:增加虚拟节点
- 幂等性接口:多次提交这个接口返回一致
实现原理:
1. 数据库中分布式id做主键,保证唯一性
2. PRG,post请求响应后,指引客户端调get请求获取结果,此时重复刷新只会调用get请求
3. redis缓存token,客户端调用请求前现象后端申请全局token,下发请求时随着header下发,后台先校验redis中是否匹配当前token和用户信息,如果不匹配,返回错误。匹配则正常进入下面流程 - 可重入锁:一个线程已经获取了锁,再次获取锁的时候可以正常获取
ReentrantLock 和synchronized 都是 可重入锁
实现原理:判断当前锁,线程id。同一个线程获取次数+1 - ThreadLocal:线程的本地变量。内部原理是维护了一个Map,每次获取时先获取当前线程对应的map,再进行getset
如何采用弱引用解决内存泄漏问题:如果Entry对象中的key是强引用,由于Entry对象还有一个强引用作用于ThreadLocal对象,即使设置了threadLocal=null,垃圾回收器也不会对ThreadLocal对象进行回收。造成内存泄漏。value是强引用,需要手动调用remove - 分布式CAP
- C 一致性:保证主节点和备用节点数据一致
- A 可用性:保证任何时候分布式系统都是可响应的
- P 分区容忍性:要在时限内保证数据一致性
其中这三者只能满足两个,满足CA的(关系型数据库),满足CP的(Nosql),AP:允许暂时不一致
- 服务限流算法:
- 计数器算法:固定时间窗口内,达到流量上限,就触发限流策略
- 滑动窗口:将时间切片,记录分片中的请求来源,依次向后滑动
- 漏桶算法:不管进来的请求,只按照恒定速率处理
- 令牌桶算法:按照一定速率生成令牌放入桶中,桶满则停止;请求进入需要获取令牌才能处理,不然触发限流
- RPC:远程服务调用,通过注册给CSE接口,服务之间可以实现相互调用。