java 基础
JDK1.8新特性
Lambda表达式
函数式接口
方法引用和构造器调用
Stream API
接口中的默认方法和静态方法
新日期时间API
-
Java8 对HashMap的的变更
在jdk1.8中对HashMap等map集合的数据结构优化。HashMap数据结构的优化
原来的HashMap采用的数据结构是哈希表(数组+链表),HashMap默认大小是16,一个0-15索引的数组,如何往里面存储元素,首先调用元素的hashcode方法,计算出哈希码值,经过哈希算法算成数组的索引值,如果对应的索引处没有元素,直接存放,如果有对象在,那么比较它们的equals方法比较内容,如果内容一样,后一个value会将前一个value的值覆盖,如果不一样,在1.7的时候,后加的放在前面,形成一个链表,形成了碰撞,在某些情况下如果链表
无限下去,那么效率极低,碰撞是避免不了的
加载因子:0.75,数组扩容,达到总容量的75%,就进行扩容,但是无法避免碰撞的情况发生
在1.8之后,在数组+链表+红黑树来实现HashMap,当碰撞的元素个数大于8时同时HashMap总容量大于64,会有红黑树的引入
除了添加之后,效率都比链表高,1.8之后链表新进元素加到末尾
ConcurrentHashMap (锁分段机制),concurrentLevel,jdk1.8采用CAS算法(无锁算法,不再使用锁分段),数组+链表中也引入了红黑树的使用
java异常分类及处理
- 异常的分类
- Error
- 异常
- 检验异常
- 运行时异常
java反射
- 获取字节码文件的几种方式
- 实例.getClass()
- 类名.class
- Class.forName("类全限定名")
java注解
- 元注解
- @Retention - 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问
- @Documented - 标记这些注解是否包含在用户文档中。
- @Target - 标记这个注解应该是哪种 Java 成员
- @Inherited - 标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类)
- 作用在代码上的注解
- @Override - 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
- @Deprecated - 标记过时方法。如果使用该方法,会报编译警告。
- @SuppressWarnings - 指示编译器去忽略注解中声明的警告。
java泛型
集合
Java集合面试题
集合分类
- List 有序,可重复
- Set 无需,唯一
- Map key-value
集合之间的区别
- List
- ArrayList: 底层是动态数组,线程不安全. 查询快,增删慢
- LinkedList: 底层是双向链表,线程不安全. 查询慢,增删快
- Vector: 数组、同步、线程安全
- Set
- HashSet: 使用hash表(数组)存储元素
- LinkedHashSet: 链表维护元素的插入次序
- TreeSet: 有序集合,底层实现二叉树,元素好排序
- Map
- Hashtable: 同步, 线程安全
- HashMap: 没有同步, 线程不安全
- LinkedHashMap: 双向链表和哈希表实现(存取有序)
- TreeMap: 红黑树对所有的key进行排序(默认升序)
- IdentifyHashMap:
- WeakHashMap:
ArrayList为什么线程不安全
HashMap和hashTable的区别
线程安全: HashMap是线程不安全的,hashTable是线程安全的
-
初始长度、加载因子、扩容机制:
HashMap初始长度16,加载因子0.75,扩容机制2n
hashTable初始长度11,加载因子0.75,扩容机制2n+1
Key-value是否为空: HashMap允许null键null值,hashTable不允许null键null值
如果创建线程安全的集合
使用Hashtable(效率低)
Collections.synchronizedMap(new HashMap<>(3)) (java.util)包
-
new ConcurrentHashMap() (java.util.concurrent)包 JUC包
ConcurrentHashMap效率最高
如何线程安全的使用HashMap
HashMap何时由链表转为红黑树
当链表长度大于8且数组长度超过64时,转为红黑树
Hash冲突如何解决
HashMap解决冲突的四种方法
- 开放地址法
- 拉链法
- 再哈希
- 建立公共溢出区
1.8版本HashMap的源码解析
- putVal方法的主要逻辑
- 通过hash方法计算key的hash值,进而计算得到应该放置到数组的位置
- 如果数组还没有初始化(数组长度是0),则先初始化
- 添加元素
- 根据Key判断如果已存在相同的key,则直接覆盖
- 如果元素是红黑树则插入其中
- 否则是链表,则遍历链表,如果找到相等的元素则替换,否则插入到链表尾部
- 如果链表的长度大于或等于8,则将链表转成红黑树
- 判断数组的长度是到达临界值,如果到达临界值则扩容
/**
* Implements Map.put and related methods.
*
* @param hash hash for key
* @param key the key
* @param value the value to put
* @param onlyIfAbsent if true, don't change existing value
* @param evict if false, the table is in creation mode.
* @return previous value, or null if none
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node[] tab; Node p; int n, i;
// 如果集合为空或者集合长度为0 则初始化tab和n
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null) // 两个位都为1,才是1
tab[i] = newNode(hash, key, value, null);
else {
Node e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
-
get方法主要逻辑
- 计算hash位置
- 看第一个元素是不是要找的元素,是则返回,否则遍历
public V get(Object key) { Node
e; return (e = getNode(hash(key), key)) == null ? null : e.value; }
JVM
java内存区域(运行时数据区域)
JVM的内存区域划分
堆、方法区、栈、本地方法栈、程序计算器
- 线程共享
- 栈(虚拟机栈)
- 本地方法栈
- 程序计数器
- 线程私有
- 堆: 堆是用来存储对象本身的以及数组(当然,数组引用是存放在Java栈中的)
- 方法区: 存储了每个类的信息(包括类的名称、方法信息、字段信息)、静态变量、常量以及编译器编译后的代码等
垃圾回收与算法
垃圾回收期
Jvm类加载机制
加载、连接、初始化、使用、卸载
类的生命周期
加载、验证、准备、解析、初始化、使用、卸载
OOM如何处理
性能监控工具
如何解决线上OOM
多线程(重点)
进程与线程的区别
为什么要使用线程池创建线程
使用线程池的好处是减少在创建和销毁线程上所耗的时间和系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者过度切换的问题
为什么要使用自定义的方式去创建线程,而不是使用默认实现
线程池不允许使用 Executors去创建,而是使用ThreadPoolExecutor 的方式.这样的处理方式让开发人员更加明确线程池的运行规则,规避资源耗尽的风险
1) FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE(2147483647),可能会堆积大量的请求,从而导致 OOM。
2) CachedThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE(2147483647),可能会创建大量的线程,从而导致 OOM。
如何设置线程池的初始长度才合适
- 如果是CPU密集型设置为n+1
- 如果是IO密集型设置为2n+1
多线程的实现/创建方式
继承Thread类重写run方法
实现Runable接口重写run方法
实现Callable接口重写call方法
-
线程池创建(线程池不允许使用 Executors 去创建,而是通过 new ThreadPoolExecutor 的方式,这
样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险)
Runable和Callable的区别
Runnable提供run方法,无法通过throws抛出异常,所有CheckedException必须在run方法内部处理。Callable提供call方法,直接抛出Exception异常。
Runnable的run方法无返回值,Callable的call方法提供返回值用来表示任务运行的结果
-
Runnable可以作为Thread构造器的参数,通过开启新的线程来执行,也可以通过线程池来执行。而Callable只能通过线程池执行。
Runnable和Callable的区别和联系
4种线程池
- newCachedThreadPool创建一个可缓存线程池程
- newFixedThreadPool 创建一个定长线程池
- newScheduledThreadPool 创建一个周期性执行任务的线程池
- newSingleThreadExecutor 创建一个单线程化的线程池
线程池的常见参数有哪些
名称 | 类型 | 含义 |
---|---|---|
corePoolSize | int | 核心线程池大小 |
maximumPoolSize | int | 最大线程池大小 |
keepAliveTime | long | 线程最大空闲时间 |
unit | TimeUnit | 时间单位 |
workQueue | BlockingQueue |
线程等待队列 |
threadFactory | ThreadFactory | 线程创建工厂 |
handler | RejectedExecutionHandler | 拒绝策略 |
线程的生命周期(状态)及其相互转换
- 新建
- 就绪
- 运行
- 阻塞
- 超时等待
- 终止状态
线程池4种拒绝策略
- ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
- ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。
- ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务
- ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务
线程池默认的拒绝策略
线程池的默认拒绝策略为AbortPolicy,即丢弃任务并抛出RejectedExecutionException异常
说说 sleep() ⽅法和 wait() ⽅法区别和共同点
- 两者最主要的区别在于:sleep ⽅法没有释放锁,⽽ wait ⽅法释放了锁 。
- sleep()是Thread类的静态方法,wait是是Objec类的方法
- 两者都可以暂停线程的执⾏。
- Wait 通常被⽤于线程间交互/通信,sleep 通常被⽤于暂停执⾏。wait() ⽅法被调⽤后,线程不会⾃动苏醒,需要别的线程调⽤同⼀个对象上的 notify() 或者notifyAll() ⽅法。sleep() ⽅法执⾏完成后,线程会⾃动苏醒。或者可以使⽤ wait(longtimeout)超时后线程会⾃动苏醒。
为什么我们调⽤ start() ⽅法时会执⾏ run() ⽅法,为什么我们不能直接调⽤run() ⽅法?
这是另⼀个⾮常经典的 java 多线程⾯试问题,⽽且在⾯试中会经常被问到。很简单,但是很多⼈都会
答不上来!
new ⼀个 Thread,线程进⼊了新建状态;调⽤ start() ⽅法,会启动⼀个线程并使线程进⼊了就绪状
态,当分配到时间⽚后就可以开始运⾏了。 start() 会执⾏线程的相应准备⼯作,然后⾃动执⾏
run() ⽅法的内容,这是真正的多线程⼯作。 ⽽直接执⾏ run() ⽅法,会把 run ⽅法当成⼀个 main
线程下的普通⽅法去执⾏,并不会在某个线程中执⾏它,所以这并不是多线程⼯作
说说 synchronized 关键字和 volatile 关键字的区别
- synchronized关键字和volatile关键字⽐volatile关键字是线程同步的轻量级实现,所以volatile性能肯定⽐synchronized关键字要好但是volatile关键字只能⽤于变量⽽synchronized关键字可以修饰⽅法以及代码块。synchronized关键字在JavaSE1.6之后进⾏了主要包括为了减少获得锁和释放锁带来的性能消耗⽽引⼊的偏向锁和轻量级锁以及其它各种优化之后执⾏效率有了显著提升,实际开发中使⽤synchronized 关键字的场景还是更多⼀些。
- 多线程访问volatile关键字不会发⽣阻塞,⽽synchronized关键字可能会发⽣阻塞
- volatile关键字能保证数据的可⻅性,但不能保证数据的原⼦性。synchronized关键字两者都能保证。
- volatile关键字主要⽤于解决变量在多个线程之间的可⻅性,⽽ synchronized关键字解决的是多个线程之间访问资源的同步性。
synchronized和Lock的区别
- Lock是一个接口,而synchronized是java的一个关键字。
- synchronized在发生异常时会自动释放占有的锁,因此不会出现死锁;而lock发生异常时,不会主动释放占有的锁,必须手动来释放锁,可能引起死锁的发生。
- synchronized 无法判断锁的状态 Lock可以判断锁的状态
threadLoad
通常情况下,我们创建的变量是可以被任何⼀个线程访问并修改的。如果想实现每⼀个线程都有⾃⼰的
专属本地变量该如何解决呢? JDK中提供的 ThreadLocal 类正是为了解决这样的问题。
ThreadLocal 类主要解决的就是让每个线程绑定⾃⼰的值,可以将 ThreadLocal 类形象的⽐喻成存
放数据的盒⼦,盒⼦中可以存储每个线程的私有数据。
如果你创建了⼀个 ThreadLocal 变量,那么访问这个变量的每个线程都会有这个变量的本地副本,这
也是 ThreadLocal 变量名的由来。他们可以使⽤ get() 和 set() ⽅法来获取默认值或将其
值更改为当前线程所存的副本的值,从⽽避免了线程安全问题。
spring
spring装载bean的过程
IOC/DI
IOC: 是原来由程序员new出来的对象,现在不这样做了,而是把它“反转”。由Spring容器去产生管理这些对象
DI: 依赖注入
AOP及其相关概念
常用注解
@Autowired、@Service、@Repository、@Controller、@Component、@Value、@Bean、@Resoure
Bean的作用域
spring依赖的4种注入方式
- Set注入
- 构造方法注入
- 静态工厂方法注入
- 动态工厂方法注入
- 注解注入
Spring7大事务的传播机制
springmvc
MVC相关流程
springmvc常见经典面试题
- 用户发送请求至前端控制器DispatcherServlet
- DispatcherServlet收到请求后,调用HandlerMapping处理器映射器,请求获取Handler
- 处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成),一并返回给DispatcherServlet
- DispatcherServlet调用HandlerAdapter处理器适配器
- HandlerAdapter经过适配器调用具体处理器(Handler,也叫后端控制器)
- Handler执行完成返回ModelAndView
- HandlerAdapter将Handler执行结果ModelAndView返回给DispatcherServlet;
- DispatcherServlet将ModelAndView传给ViewResolver视图解析器进行解析;
- ViewResolver解析后返回具体View;
- DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)
- DispatcherServlet响应用户
MVC常用注解
@RestController、@RequestMapping、@PostMapping、@GetMapping、@RequestBody、@ResponeBody
mybaits
springboot
spring cloud
nacos和eureke的区别
dubbo
原理
dubbo的序列化方式
- dubbo 协议
- rmi 协议
- hessian 协议
- http 协议
- webservice
dubbo的几种连接方式
- 注册中心(zk)
- 直连
- 广播
dubbo和Cloud的区别
- Dubbo严重依赖第三方组件(ZK或者Redis),当这些组件出现问题时,服务调用很快就会中断
- Dubbo只支持rpc调用。服务提供方和调用方在代码上产生强依赖,服务提供方需要不断将包含公共代码的jar包打包出来共消费方使用.一旦打包出现问题,就会导致服务调用出错
- Dubbo的定位始终是一款rpc框架,而spring cloud 的目标是微服务架构下的一站式解决方案
- Dubbo基于rpc通信,spring cloud 是基于Http的REST方式
boot和cloud的区别
数据库
事务的特征(ACID)
- 原子性(Atomic): 原子性,构成事务的所有操作,要么全部执行,要么全部不执行,不可能出现部分成功部分失败的情况
- 一致性(Consistency): 在事务执行前后,数据库的一致性没有被破坏。比如:张三向李四转100元,转账前和转账后的数据正确状态这叫一致性,如果出现张三账户转出100元,李四账户没有增加100元就没有达到一致性。
- 隔离性(Isolation): 数据库中的事务一般都是并发的,隔离性是指并发的两个事务的执行互不干扰,一个事务不能看到其他事务运行过程的中间状态。通过配置事务隔离级别可以避脏读、重复读等问题。
- 持久性(Durability): 事务完成之后,该事务对数据的更改会被持久化到数据库,且不会被回滚。
事务并发可能会造成的问题
- 脏读: 指一个线程中的事务读取到了另外一个线程中未提交的数据。
- 不可重复度: 指一个线程中的事务读取到了另外一个线程中提交的update的数据。
- 虚读/幻读: 指一个线程中的事务读取到了另外一个线程中提交的insert的数据。
事务的隔离级别
事务的四种隔离级别
- 读未提交:哪个问题都不能解决
- 读已提交:可以解决脏读 —- oracle默认的
- 重复读:可以解决脏读和不可重复读 —mysql默认的
- 序列化:可以解决脏读不可重复读和虚读—相当于锁表
三大范式
关系型数据库设计:三大范式的通俗理解
- 第一范式: 要求数据库表的每一列都是不可分割的原子数据项。
- 第二范式: 确保数据库表中的每一列都和主键相关,而不能只与主键的某一部分相关(主要针对联合主键而言)
- 第三范式: 确保数据表中的每一列数据都和主键直接相关,而不能间接相关。
反范式
- 反范式化指的是通过增加冗余或重复的数据来提高数据库的读性能
Mysql如何监控慢sql
- 修改mysql的配置
- 使用德鲁伊的数据源进行监控
数据库优化
数据库优化方案整理
mysql百万级数据查询优化
表设计
- 设计合理的表合理的冗余字段,减少表的连接
- 使用合理的类型,例如数字类型不要设计成字符类型
- 尽量使用TINYINT、SMALLINT、MEDIUM_INT作为整数类型而非INT,如果非负则加上UNSIGNED
- 使用CHAR不要使用VARCHAR的长度只分配真正需要的空间
- 使用枚举或整数代替字符串类型
- 尽量使用TIMESTAMP而非DATETIME
- 单表不要有太多字段,建议在20以内
- 避免使用NULL字段,很难查询优化且占用额外索引空间
- 合适的数据库存储引擎
代码优化
- 走查代码,减少代码中不必要的查询代码
- for循环中不要使用数据库查询
查询优化
- 创建索引
- 不使用like
- 不使用*,指定查询的字段
- 不使用in查询,使用Exist代替
- 当预期数据只有一条数据时直接使用limit 1 返回一条数据
硬件优化
- 更快的IO、更多的内存
缓存
- Redis
大表优化
限定数据范围: 例如查询的时候只查询一个月的数据
读/写分离 主库复制写,从库负责读
-
垂直拆分
订单基本信息表和订单明细信息拆分成两个表
-
水平拆分
一张表在同一个数据库中拆分成多个相同结构的表,避免单一的表数据量过大对性能的影响
-
分库(数据库分片)
客户端代理: 分片逻辑在应用端,封装在jar包中,通过修改或封装JDBC层来实现,当当网的Sharding-JDBC、阿里的TDDL是两种常见的实现
中间件代理: 在应用和数据中间加了一层代理。分片逻辑统一维护在中间件服务中。常见实现MyCat、360的Atlas、网易的DDB等都是这种架构的实现
索引
索引的分类
- 普通索引
- 唯一索引
- 主键索引
- 组合索引
- 全文索引
造成索引失效的场景
- 索引列中不能存在null
- 左like操作会造成索引失效
- 不在在列上进行运算
- 不使用not in 和<>操作
索引的数据结构
Mysql索引面试题
- hash索引
- B+ Tree 索引 mysql默认使用的innoDB引擎,使用的是B+ Tree索引
索引的最左匹配原则
当我们创建一个联合索引的时候,如(key1,key2,key3),相当于创建了(key1)、(key1,key2)和(key1,key2,key3)三个索引,这就是最左匹配原则
数据库的各种锁
Mysql数据库中的各种锁
表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般
数据库的引擎
史上最全MySQL各种锁详解
分库分表
读写分离
好文
我们为什么要分库分表
Redis
常见的Memcache和redis的区别
什么是redis
redis为什么这么快
10W+QPS (QPS即query per second ,每秒内查询次数)
- 完全基于内存,绝大多数请求基于内存操作,执行效率高
- 数据结构简单,对数据操作也简单
- 采用单线程(避免了多线程之间的上下文切换和竞争关系),单线程也能处理高并发请求,想多核也可以启动多实例
- 使用多路I/O复用模型,非阻塞IO
redis的几种数据类型
- String: 最基本的数据类型,二进制安全.(最大可达512M)
- Hash: String元素组成的字典,适用于存储对象. hmset、hset
- List: 列表,按照String插入顺序排序 lpush、lrange
- Set: String元素组成的无序集合,通过哈希表实现,不允许重复 sadd、smembers
- Sorted Set: 通过分数来为集合中的成员进行从小到大的排序
- HyperLogLong:用户计数的HypeLongLog
- Geo:用于支持存储地址位置信息的Geo
redis的备份策略
Rdb是redis默认的缓存方案,在指定时间内,执行指定次数的操作,则会将内存中的数据写入到磁盘中
默认的修改配置 3600 1 300 100 60 10000 (1 个小时一次修改,5 分钟 100 次修改,1 分钟 10000 次修改)
-
RDB
在指定时间内将内存中的数据及快照写入到磁盘中
优点:
- 只有一份rdb文件,可随时备份
- 比AOF文件小,加载效率高
- 不阻塞进程,io效率高
-
AOF
将每个操作以日志的形式记录在服务器,在redis启动之初读取文件来重新构建数据库,保证启动后数据库中的数据是完整的
优点:
- 每次改动同步数据安全性好,以append的方式追加日志,不会对旧日志文件产生影响
redis使用场景
- 缓存
- 分布式锁
- 消息队列
Redis的业务使用场景
- 热点数据的缓存
- 字典数据的缓存
- 分布式锁
- 位图实现考勤打卡
redis使用过程中会遇到什么问题,及解决方案
-
缓存穿透
概念: 缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求
解决方案: 1. 使用布隆过滤器 2. 将该数据设置为key-null,时间设置短一点比如30秒
-
缓存击穿
概念: 是指一个 key 非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个 key 在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞
解决方案: 1. 热点数据设置为永不过期 2. 加互斥锁
-
缓存雪崩
概念: 缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。
和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。解决方案: 1. 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生 2. 设置热点数据永远不过期
Redis布隆过滤器
缓存穿透、缓存击穿、缓存雪崩区别和解决方案
从海量key里查询出某一固定前缀的key
-
使用keys命令
# 查找所有符合给定模式的pattern的key keys pattern
KEYS 指令一次性返回所有匹配的key
键的数量过大会使服务卡顿 -
使用scan命令
scan cursor [MATCH pattern] [COUNT count] # scan 0 match k1* count 10
基于游标的迭代器,需要基于上一次的游标延续之前的迭代过程
以0作为游标开始一次新的迭代,知道命令返回游标0完成一次遍历
不保证每次执行都返回某个给定数量的元素,支持模糊查询
一次返回的数量不可控,只能是大概率符合count参数
如何解决redis中的数据和数据库中数据不一致的问题
-
双删或者延时双删
Redis与数据库一致性问题分析
Redis的内存淘汰机制
定期删除+惰性删除
-
定期删除
Redis设定每隔100ms随机抽取设置了过期时间的key,并对其进行检查,如果已经过期则删除。
-
惰性删除
每次获取key时会对key进行判断是否存活,如果已经过期了则删除
Redis集群有几种搭建方式
消息中间件(MQ)
几种常见的MQ
RabbitMQ
- RabbitMQ确认机制
mq的使用场景
异步处理、应用解耦、流量削锋、消息通讯
RabbitMQ的序列化方式
RbibitMQ
消息队列之RaibitMQ
RabbitMQ的基本概念
Exchange的类型
RabbitMQ的四种ExChange
direct、fanout、topic、headers
- direct
- fanout
- topic
- Headers
分布式事务
什么是分布式事务
分布式事务顾名思义就是要在分布式系统中实现事务,它其实是由多个本地事务组合而成。
分布式事务的解决方案
七种分布式事务的解决方案,一次讲给你听
分布式事务及其解决方案
- 2阶段提交(2PC)
- 3阶段提交(3PC)
- 补偿事务(TCC)
- 本地消息表
- 消息事务(RocketMQ支持)
- 最大努力通知
- Sagas 事务模型
分布式锁
什么是分布式锁
分布式环境获取共享资源
分布式锁4个要解决的问题
- 互斥性: 如何时候都只能有一个客户端获取到锁,不能有两个客户端同事获取到锁
- 安全性: 锁只能被持有该锁的客户端删除,不应该由其他客户端删除
- 不会死锁: 获取锁的客户端因为某些原因宕机导致未能释放锁,其他客户端再也不能获取该锁,导致死锁
- 容错: 部分节点宕机,客户端仍然可以获取锁和释放锁
分布式锁的使用场景
分布式锁的实现方式
基于数据库实现的排它锁
-
基于redis实现
- LUA脚本实现
- Redisson实现
-
基于zookeeper实现
三种方案实现的区别:
延迟队列
参考: 6个延时队列的实现方案
延迟队列的使用场景
- 订单成功后,在30分钟内没有支付,自动取消订单
- 外卖平台发送订餐通知,下单成功后60s给用户推送短信。
- 如果订单一直处于某一个未完结状态时,及时处理关单,并退还库存
- 淘宝新建商户一个月内还没上传商品信息,将冻结商铺等
延迟队列的实现方式
- DelayQueue延时队列
- Quartz定时任务
- Redis sorted set
- Redis过期回调
- RabbitMQ 延时队列
- 时间轮
通过Redis实现分布式锁
SET key value [EX seconds] [PX milliseconds] [NX|XX]
# set locktarget 12345 ex 10 nx
# EX seconds :设置键的过期时间为seconds秒
# PX milliseconds : 设置键的过期时间为milliseconds毫秒
# NX :只在键不存在时,才对键进行设置操作
# XX :只在键已经存在时,才对键进行设置操作
# SET操作成功完成时,返回OK,否则返回nil
Elasticsearch
算法
排序
-
冒泡
基本思想: 两个数比较大小,比较大的下沉,较小的数冒起来
public static void BubbleSort(int[] arr) { int temp;//临时变量 for (int i = 0; i < arr.length - 1; i++) { //表示趟数,一共arr.length-1次。 for (int j = arr.length - 1; j > i; j--) { if (arr[j] < arr[j - 1]) { temp = arr[j]; arr[j] = arr[j - 1]; arr[j - 1] = temp; } } } }
快排
查找
- 二分查找法
设计模式
动态代理模式
-
动态代理的两种实现方式
JVM实现、CGLib实现
-
两种实现方式的区别
Java动态代理之JDK实现和CGlib实现(简单易懂)
JDK动态代理是通过接口中的方法名,在动态生成的代理类中调用业务实现类的同名方法;
CGlib动态代理是通过继承业务类,生成的动态代理类是业务类的子类,通过重写业务方法进行代理;
AOP的源码中用到了两种动态代理来实现拦截切入功能:jdk动态代理和cglib动态代理。两种方法同时存在,各有优劣。jdk动态代理是由java内部的反射机制来实现的,cglib动态代理底层则是借助asm来实现的。总的来说,反射机制在生成类的过程中比较高效,而asm在生成类之后的相关执行过程中比较高效(可以通过将asm生成的类进行缓存,这样解决asm生成类过程低效问题)。还有一点必须注意:jdk动态代理的应用前提,必须是目标类基于统一的接口。如果没有上述前提,jdk动态代理不能应用。由此可以看出,jdk动态代理有一定的局限性,cglib这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。
策略模式
单例模式
单例模式是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
// 双重锁校验单例模式 这种方式双锁机制,安全且在多线程情况下能保持性能
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
工厂模式
Spring Cloud Alibaba
- Flow control and service degradation(流量控制和流量降级):流量控制、断路、系统自适应保护Alibaba Sentinel
- Service registration and discovery(服务注册和发现):实例可以注册到Alibaba Nacos,客户端可以使用spring管理的bean发现实例。支持Ribbon,通过Spring Cloud Netflix的客户端负载平衡器
- Distributed Configuration(分布式配置中心):将阿里巴巴的Alibaba Nacos作为数据存储
- Event-driven(事件-驱动):构建与Spring Cloud Stream RocketMQ Binder连接的高度可伸缩的事件驱动微服务
- Message Bus(消息总线): 使用Spring Cloud Bus连接分布式系统的节点 RocketMQ
- Distributed Transaction(分布式事务):支持高性能和易于使用的分布式事务解决方案 Seata
- Dubbo RPC:扩展Spring Cloud服务到服务调用的通信协议 Apache Dubbo RPC
Sentinel
Nacos
Seata
Linux
# 根据进程号查询应用
ps aux | grep [端口号]
# 根据应用名查找应用
ps -ef | grep [应用名]
# 根据端口后查询应用进程id
netstat -nlap |grep [端口号]
# 根据进程id结束进程
kill -9 [进程ID]