java中异常有哪些?
| 异常类型 | 说明 |
|---|---|
| ArithmeticException | 算术错误异常,如以零做除数 |
| ArraylndexOutOfBoundException | 数组索引越界 |
| ArrayStoreException | 向类型不兼容的数组元素赋值 |
| ClassCastException | 类型转换异常 |
| IllegalArgumentException | 使用非法实参调用方法 |
| lIIegalStateException | 环境或应用程序处于不正确的状态 |
| lIIegalThreadStateException | 被请求的操作与当前线程状态不兼容 |
| IndexOutOfBoundsException | 某种类型的索引越界 |
| NullPointerException | 尝试访问 null 对象成员,空指针异常 |
| NumberFormatException | 数字转化格式异常,比如字符串到 float 型数字的转换无效 |
| TypeNotPresentException | 类型未找到 |
| ClassNotFoundException | 没有找到类 |
| IllegalAccessException | 访问类被拒绝 |
| InstantiationException | 试图创建抽象类或接口的对象 |
| InterruptedException | 线程被另一个线程中断 |
2 git用来干嘛的,有什么命令.
git是一个开源的版本控制系统,可以有效、高速地进行项目版本管理
- git 常用命令 git config --global user.name xxx
git config --global user.email xxx@qq.com
git init (初始化一个仓库)
git add [文件名] (添加新的文件)
git commit -m [关于本次提交的相关说明] (提交)
git status (查看文件状态)
git diff (如果文件改变,比较两个文件内容)
git log (查看提交说明)
git reset --hard head~[N] (返回到前N个版本)
git reset --hard [commit id] (回到commit id对应的版本)
git checkout --[文件名] (--很重要,没有--,就变成了“切换到另一个分支”的命令)
git rm [文件名]
git checkout --[文件名] (因为版本库里还有,所以可以把误删的文件恢复到最新版本)
git push -u [远程仓库名] [分支名] 把本地库的内容推送到远程 git push -u origin master
git push [远程仓库名] [分支名]
git remote -v 查看远程仓储名称 (分支)
git branch [分支名称](创建新的分支)
git checkout [分支名称] (切换到不同分支)
git checkout -b [分支名称] (创建并切换到不同的分支)
git branch (查看当前所在的分支)
git merge [分支名称] (合并指定分支到当前分支)
git branch -d [分支名称] (删除分支)
3.基础集合有哪些
集合相关类和接口都在 java.util 中,主要分为 3 种,分别是:List、Map、Set
4.hashmap底层是如何实现的?
HashMap的底层数据结构由数组、链表和红黑树组成,核心是基于数组实现的,为了解决哈希冲突,采用拉链法,于是引入了链表结构。为了解决链表过长,造成的查询性能下降,又引入了红黑树结构。
5 如何使用反射
- 什么是反射
Java的反射(reflection)机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性,既然能拿到那么,我们就可以修改部分类型信息;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射(reflection)机制。
获取Class类对象三种方法:
- Class.forNam(全类名) 静态方法。源代码阶段(.java -> .class)
- 类名.class() 方法(基本类型只可该方法获得Class对象)。加载阶段(.class加载到内存中)
- 对象.getClass() 方法。运行阶段(内存中运行 A a = new A();)
通过反射创建类对象
1.通过 Class对象的newInstance() 方法(只能使用默认的无参数构造方法)。
2.通过 Constructor对象的newInstance() 方法(可以选择特定构造方法)。
//获取class对象 Class clazz = Class.forName("reflect.Person");
//如果有默认构造方法,就使用这个,先通过Class对象获取指定的Constructor对象
Person p1 = (Person) clazz.getConstructor().newInstance();
//指定构造方法
Person p2 = (Personclazz.getDeclaredConstructor(String.class,int.class).newInstance("dr", 1);
//获取指定变量
name Field nameField = clazz.getField("name");
//修改变量的值
nameField.set(p1,"张三");
//获取指定方法
"addFavorite" Method method = clazz.getMethod("addFavorite", String.class);
//调用对象方法
method.invoke(p, "篮球");
6 乐观锁和悲观锁
悲观锁(Pessimistic Lock): 就是很悲观,每次去拿数据的时候都认为别人会修改。所以每次在拿数据的时候都会上锁。这样别人想拿数据就被挡住,直到悲观锁被释放,悲观锁中的共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程 乐观锁(Optimistic Lock): 就是很乐观,每次去拿数据的时候都认为别人不会修改。所以不会上锁,但是如果想要更新数据,则会在更新前检查在读取至更新这段时间别人有没有修改过这个数据。如果修改过,则重新读取,再次尝试更新,循环上述步骤直到更新成功(当然也允许更新失败的线程放弃操作),乐观锁适用于多读的应用类型,这样可以提高吞吐量。
7 线程有哪些的状态
线程的生命周期主要有以下六种状态:
- New(新创建)
- Runnable(可运行)
- Blocked(被阻塞)
- Waiting(等待)
- Timed Waiting(计时等待)
- Terminated(被终止)
8 wait和sleep的区别
sleep会让线程进入TIMED_WAITING 状态,而wait方法通过是否传入参数来决定是转为WAITING状态还是TIMED_WAITING状态。 他们俩都是让线程不会再被分配cpu.
- sleep是Thread的静态方法,wait是Object对象中的方法。
- sleep不需要其他方法对他进行唤醒,只需要当时间到了,自动由TIMED_WAITING状态转为RUNNABLE状态。 而wait方法要配合notify或者notifyAll方法进行唤醒。唤醒操作也需要在Syn块儿中进行调用。
- sleep是不会释放锁的。当时间一到,就会重新将状态转为RUNNABLE。因为一直持有锁,所以也不会去和阻塞队列中进行竞争。wait会释放当前锁,并且当被唤醒时,并不会立即获得锁,还要去阻塞队列进行争抢锁,争抢到了才可以变为RUNNABLE状态。
9 线程池的状态,怎么使用?
线程池一共有五种状态, 分别是:
- RUNNING :能接受新提交的任务,并且也能处理阻塞队列中的任务;
- SHUTDOWN:关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务。在线程池处于 RUNNING 状态时,调用 shutdown()方法会使线程池进入到该状态。(finalize() 方法在执行过程中也会调用shutdown()方法进入该状态);
- STOP:不能接受新任务,也不处理队列中的任务,会中断正在处理任务的线程。在线程池处于 RUNNING 或 SHUTDOWN 状态时,调用 shutdownNow() 方法会使线程池进入到该状态;
- TIDYING:如果所有的任务都已终止了,workerCount (有效线程数) 为0,线程池进入该状态后会调用 terminated() 方法进入TERMINATED 状态。
- TERMINATED:在terminated() 方法执行完后进入该状态,默认terminated()方法中什么也没有做。
10 jvm是什么
JVM 是 Java Virtual Machine 的缩写,它是一个虚构出来的计算机,一种规范。通过在实际的计算机上仿真模拟各类计算机功能实现。
11 垃圾回收处理机制
垃圾判断算法
既然 JVM 要做垃圾回收,就要搞清楚什么是垃圾,什么不是垃圾。通常会有这么几种算法来确定一个对象是否是垃圾,这块也是面试当中常考的一个知识点,大家一定要掌握。
- 引用计数算法
- 可达性分析算法
垃圾收集算法
标记清除算法
- 标记清除算法(Mark-Sweep)是最基础的一种垃圾回收算法,它分为 2 部分,先把内存区域中的这些对象进行标记,哪些属于可回收的标记出来(用前面提到的可达性分析法),然后把这些垃圾拎出来清理掉。
- 复制算法(Copying)是在标记清除算法上演化而来的,用于解决标记清除算法的内存碎片问题。它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。
- 标记整理算法(Mark-Compact),标记过程仍然与标记清除算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,再清理掉端边界以外的内存区域。
- 分代收集算法(Generational Collection)严格来说并不是一种思想或理论,而是融合上述 3 种基础的算法思想,而产生的针对不同情况所采用不同算法的一套组合拳。
12 tcp与udp的区别?
TCP(传输控制协议)和UDP(用户数据报协议)是互联网中两个核心的传输层协议,它们各自采用不同的方式来确保数据从源头传输到目的地.
12.1. 连接的建立
- TCP:TCP (Transmission Control Protocol,传输控制协议) 是一种面向连接的协议。这意味着在数据传输开始之前,必须先建立连接。TCP通过三次握手过程来建立连接,确保两端的通信是同步的。
- UDP:UDP (User Datagram Protocol,用户数据报协议) 是一种面向数据报的协议。UDP发送数据之前不需要建立连接,它直接将数据包发送给接收方,不保证数据包的顺序、完整性或可靠性。
12.2. 数据传输的可靠性
- TCP:提供高可靠性的数据传输。它通过序列号、确认应答、重传机制、流量控制和拥塞控制等技术来确保数据完整无误地传输到接收方。
- UDP:不保证数据传输的可靠性。它发送的数据包可能会丢失、重复或到达顺序错乱,不提供错误恢复功能。
12.3. 数据传输的速度和效率
- TCP:由于需要进行连接管理、错误检测和恢复,以及维持连接状态,其数据传输速度相对较慢,协议开销较大。
- UDP:由于不需要建立连接,且几乎没有错误恢复机制,使得UDP的数据传输速度较快,协议开销小,效率较高。
13 mysql有哪些索引?
按照索引逻辑分类:
- (1) 主键索引 PRIMARY KEY
- (2) 唯一索引 UNIQUE
- (3)普通索引 INDEX
- (4) 组合索引 INDEX:组合索引,即一个索引包含多个列。可以在创建表的时候指定,也可以修改表结构,如:ALTER TABLE
table_nameADD INDEX index_name(column1,column2,column3)
按照 物理实现方式 ,索引可以分为 2 种:
- 聚簇索引和非聚簇索引。
14 聚簇索引和非聚簇索引区别
drop table if exists student;
create table student( id int primary key, name varchar(16), class_id int not null, index (class_id) )engine=InnoDB;
-- 添加测试数据 insert into student(id,name,class_id) values(1,'张三',100), (2,'李四',200),(3,'王五',300);
- 聚集索引与非聚集索引的区别是:叶节点是否存放一整行记录
- 在非聚簇索引的叶子节点上存储的并不是真正的行数据,而是主键 ID,所以当我们使用非聚簇索引进行查询时,首先会得到一个主键 ID,然后再使用主键 ID 去聚簇索引上找到真正的行数据,我们把这个过程称之为回表查询。
15 事务是什么
事务是数据库操作的最基本单元,是逻辑上的一组操作,要么都成功,要么都失败;是一个不可分割的工作单元。
15.1 ACID主要涵盖四条原则,即:
A/Atomicity:原子性C/Consistency:一致性I/Isolation:独立性/隔离性D/Durability:持久性
15.2 在MySQL中,提供了一系列事务相关的命令,如下:
start transaction | begin | begin work:开启一个事务commit:提交一个事务rollback:回滚一个事务
15.3 事务隔离机制
一个连接的事务并不会影响其他连接,当时也稍微的提过一嘴:这是基于事务隔离机制实现的,那接下来重点聊一聊MySQL的事务隔离机制。其实在MySQL中,事务隔离机制分为了四个级别:
- ①
Read uncommitted/RU:读未提交 - ②
Read committed/RC:读已提交 - ③
Repeatable read/RR:可重复读 - ④
Serializable:序列化/串行化
15.4 脏读、幻读、不可重复读问题
在上面连续讲了脏读、不可重复读以及幻读三个问题,那这些问题该怎么解决呢?其实四个事务隔离级别,解决的实际问题就是这三个,因此一起来看看各级别分别解决了什么问题:
- ①读未提交:处于该隔离级别的数据库,脏读、不可重复读、幻读问题都有可能发生。
- ②读已提交:处于该隔离级别的数据库,解决了脏读问题,不可重复读、幻读问题依旧存在。
- ③可重复读:处于该隔离级别的数据库,解决了脏读、不可重复读问题,幻读问题依旧存在。
- ④序列化/串行化:处于该隔离级别的数据库,解决了脏读、不可重复读、幻读问题都不存在。
16 如何控制锁的力度
- 并发需求:首先要考虑的是并发需求。你需要确定是需要保护共享资源的互斥访问,还是允许多个线程同时访问资源。
- 如果需要保护共享资源的互斥访问,可以选择使用互斥锁,如synchronized关键字或ReentrantLock类。这些锁是独占锁,一次只允许一个线程访问被保护的代码块或资源。
- 如果允许多个线程同时访问资源,可以考虑使用读写锁,如ReentrantReadWriteLock类。读写锁允许多个线程同时读取共享资源,但在写操作时需要互斥访问。 2.性能需求:考虑你的程序对性能的要求。
- synchronized关键字是Java语言提供的内置锁,使用简单,但可伸缩性较差。它适用于低竞争情况和简单的同步需求。
- ReentrantLock类相对于synchronized提供了更好的可伸缩性和性能。它适用于高竞争情况和复杂的同步需求。但需要注意,使用ReentrantLock时需要显式地获取和释放锁,使用不当可能会导致死锁等问题。
17 索引底层用的是什么?
索引是一种优化查询的数据结构
17.1 为什么哈希表、完全平衡二叉树、B树、B+树都可以优化查询,为何Mysql独独喜欢B+树?
- 哈希表的做法其实很简单,就是把Key通过一个固定的算法函数即所谓的哈希函数转换成一个整型数字,然后就将该数字对数组长度进行取余,取余结果就当作数组的下标,将value存储在以该数字为下标的数组空间里。
select * from sanguo where name >'周瑜'
则无能为力,因为哈希表的特点就是可以快速地精确查询,但是不支持范围查询。
- Mysql选用B+树这种数据结构作为索引,可以提高查询索引时的磁盘IO效率,并且可以提高范围查询的效率,并且B+树里的元素也是有序的。
18 ioc是什么
Ioc—Inversion of Control,即“控制反转”,它是一种设计思想,并不是什么技术;在 Java 中,IOC 意味着将我们设计好的对象交给容器控制,而不是传统的需要时在内部构造直接控制;
什么为正转?什么为反转?
- 正转: 在我们需要某个对象的时候,需要自己主动的去构建对象以及其所依赖的对象;
- 反转: 在我们需要某个对象的时候,只需要在 IoC 容器中获取所需对象,无需关心创建过程以及其中的依赖对象;全盘由 IoC 容器帮我们创建对象以及注入其所依赖的对象,在这里我们把对象的控制权反转给了 IoC 容器,所以称为反转; DI-Dependency Injection,即"依赖注入",就是由容器动态的将某个依赖注入到组件中。通过依赖注入机制,我们只需要简单的配置,无需任何代码就可以指定目标所需要的资源,从而完成自身的业务逻辑;我们无需关心具体的资源来自何处,提升了系统灵活性和可扩展性。
IOC和DI的关系
DI 可以看作是 IoC 的一种实现方式,IoC 是一种思想,而 DI 是一种设计模式,是一种实现 IoC 的模式。
19 spring常用注解?
-
@Component
-
@Service
-
@Repository
-
@Controller
-
@Autowired
-
@Qualifier可以被用在单个构造器或者方法的参数上。当上下文有几个相同类型的bean, 使用@Autowired则无法区分要绑定的bean,此时可以使用@Qualifier来指定名称。
-
@Configuration
-
@ComponentScan
-
@Lazy
-
@Value
- @Resource
- @Scope
@PostConstruct:被用于在依赖注入完成后执行初始化方法。@PreDestroy:在Bean销毁前执行清理工作。
19 aop是什么
- AOP引入了一种新的模块化方式,通过将横切关注点分离出来,并将它们称为切面(Aspect),可以将它们独立地应用于核心业务逻辑中。切面通过定义切点(Pointcut)来选择在哪些位置应用横切逻辑,然后定义通知(Advice)来指定在切点处执行的操作。
AOP提供了以下关键概念:
- 切面(Aspect):包含了横切关注点的模块。它定义了在哪些位置应用横切逻辑。
- 切点(Pointcut):定义了在哪些位置应用切面。它是一个表达式,用于选择符合特定条件的连接点(Join Point)。
- 连接点(Join Point):在应用程序执行过程中可以插入切面的点,比如方法调用、方法执行、异常抛出等。
- 通知(Advice):定义了在切点处执行的操作,例如在方法调用前执行、方法调用后执行等。
- 织入(Weaving):将切面应用到目标对象中的过程。可以通过编译时织入、加载时织入或运行时织入来实现。
AOP的典型应用场景包括日志记录、性能监控、事务管理、安全性控制等。它可以帮助我们更好地分离关注点,减少重复代码,并提高代码的可维护性和可重用性。
20 mybatis怎么解决sql注入的问题?
什么是SQL注入?
所谓SQL注入,就是通过把SQL命令插入到Web表单提交,它可以通过在Web表单中输入(恶意)SQL语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行SQL语句。 看看与上面SQL组合,成了如下:
复制代码
SELECT * From table_name WHERE name=’’ and password=’’ and corporate=’’ or 1=1-’
从代码可以看出,前一半单引号被闭合,后一半单引号被 “–”给注释掉,中间多了一个永远成立的条件“1=1”,这就造成任何字符都能成功登录的结果。
mybatis规避sql注入
使用参数化查询(Prepared Statements):MyBatis 推荐使用参数化查询来构建 SQL 语句。在参数化查询中,使用占位符(?)代替实际的参数值,并将参数的值通过参数绑定的方式传递给查询语句。这样可以确保参数值不会被误解为 SQL 代码的一部分,从而防止注入攻击。
例如,在 MyBatis 中使用参数化查询:
<select id="getBlogById" resultType="Blog" parameterType=”int”>
SELECT id,title,author,content
FROM blog
WHERE id=${id}
</select>
仔细观察,内联参数的格式由“#{xxx}”变为了“${xxx}”。如果我们给参数“id”赋值为“3”,将SQL打印出来是这样的:
复制代码
SELECT id,title,author,content FROM blog WHERE id = 3
21 spring缓存
当使用缓存时,Spring 提供了三个常用的注解:@Cacheable、@CachePut 和 @CacheEvict。
自定义RedisCacheManager 重写createRedisCache方法实现@Cacheable注解可以设置自定义过期时间
/**java
* 自定义cacheNames方式 RedisCacheManager 自定义cacheNames方式可以设置过期时间 格式 name#12s 标识12秒过期(d=天,h=小时,m=分钟,s秒)
* @author wenyuan
* @date 2021/7/27
*/
public class MyRedisCacheManager extends RedisCacheManager {
public MyRedisCacheManager(RedisCacheWriter cacheWriter,
RedisCacheConfiguration defaultCacheConfiguration) {
super(cacheWriter, defaultCacheConfiguration);
}
@Override
protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
String[] array = StringUtils.delimitedListToStringArray(name, "#");
name = array[0];
if (array.length > 1) { // 解析TTL
// 例如 12 默认12秒, 12d=12天
String ttlStr = array[1];
Duration duration = convertDuration(ttlStr);
cacheConfig = cacheConfig.entryTtl(duration);
}
return super.createRedisCache(name, cacheConfig);
}
private Duration convertDuration(String ttlStr) {
if (NumberUtil.isLong(ttlStr)) {
return Duration.ofSeconds(Long.parseLong(ttlStr));
}
ttlStr = ttlStr.toUpperCase();
if (ttlStr.lastIndexOf("D") != -1) {
return Duration.parse("P" + ttlStr);
}
return Duration.parse("PT" + ttlStr);
}
}
@Cacheable(cacheNames = "getMyOrg#6h", key = "#empNo")
public List<String> getMyOrg(String empNo) {
List<String> orgs = new ArrayList<>();
String orgIDPath = getMyOrgPath(empNo);
if (ObjectUtil.isEmpty(orgIDPath)){
return orgs;
}
orgs = Arrays.asList(orgIDPath.split(ORG_PATH_SEPARATOR));
return orgs;
}
22 mq相关知识
22.1 为什么使用mq
- 先说一下消息队列常见的使用场景吧,其实场景有很多,但是比较核心的有 3 个:解耦、异步、削峰。(然后结合自己项目场景,说一下使用的mq类型,以及场景)
22.2 息队列有什么优缺点?
-
系统可用性降低
-
系统复杂度提高
-
数据一致性问题
22.3 rocketMQ使用问题
核心概念
- NameServer:可以理解为是一个注册中心,主要是用来保存topic路由信息,管理Broker。在NameServer的集群中,NameServer与NameServer之间是没有任何通信的。
- Broker:核心的一个角色,主要是用来保存topic的信息,接受生产者产生的消息,持久化消息。在一个Broker集群中,相同的BrokerName可以称为一个Broker组,一个Broker组中,BrokerId为0的为主节点,其它的为从节点。BrokerName和BrokerId是可以在Broker启动时通过配置文件配置的。每个Broker组只存放一部分消息。
- 生产者:生产消息的一方就是生产者
- 生产者组:一个生产者组可以有很多生产者,只需要在创建生产者的时候指定生产者组,那么这个生产者就在那个生产者组
- 消费者:用来消费生产者消息的一方
- 消费者组:跟生产者一样,每个消费者都有所在的消费者组,一个消费者组可以有很多的消费者,不同的消费者组消费消息是互不影响的。
- topic(主题) :可以理解为一个消息的集合的名字,生产者在发送消息的时候需要指定发到哪个topic下,消费者消费消息的时候也需要知道自己消费的是哪些topic底下的消息。
- Tag(子主题) :比topic低一级,可以用来区分同一topic下的不同业务类型的消息,发送消息的时候也需要指定。
22.3.1 ## RocketMQ如何保证消费幂等?
当出现消费者对某条消息重复消费的情况时,重复消费的结果与消费一次的结果是相同,并且多次消费并未对业务系统产生任何负面影响,那么这个消费者的处理过程就是幂等的。
处理方法
因为不同的Message ID对应的消息内容可能相同,有可能出现冲突(重复)的情况,所以真正安全的幂等处理,不建议以Message ID作为处理依据。 最好的方式是以业务唯一标识作为幂等处理的关键依据,而业务的唯一标识可以通过消息Key设置。 以支付场景为例,可以将消息的Key设置为订单号,作为幂等处理的依据。具体代码示例如下:
java
复制代码
Message message = new Message();
message.setKey("ORDERID_100");
SendResult sendResult = producer.send(message);
消费者收到消息时可以根据消息的Key,即订单号来实现消息幂等:
java
复制代码
consumer.subscribe("ons_test", "*", new MessageListener() {
public Action consume(Message message, ConsumeContext context) {
String key = message.getKey()
// 根据业务唯一标识的Key做幂等处理。
}
});
如何保证消息不丢失/可靠性?
生产阶段
概述:通过请求确认机制保证消息传递可靠性。如果消息发送失败,可以尝试以下操作:
- 直接捕获异常重试(同步发送,直接捕获重发;异步发送,重写回调函数,处理发送异常)
- 将消息存储到db,然后由后台线程定时重试,确保消息一定到达Broker
存储阶段
同步刷盘:只有在消息真正持久化至磁盘后 RocketMQ 的 Broker 端才会真正返回给 Producer 端一个成功的 ACK 响应。同步刷盘对 MQ 消息可靠性来说是一种不错的保障,但是性能上会有较大影响,一般适用于金融业务应用该模式较多。
异步刷盘(默认) :能够充分利用 OS 的 PageCache 的优势,只要消息写入 PageCache 即可将成功的 ACK 返回给 Producer 端。消息刷盘采用后台异步线程提交的方式进行,降低了读写延迟,提高了 MQ 的性能和吞吐量。
所以,为了保证 Broker 端不丢消息,可以设置为同步刷盘:
java
复制代码
## 默认情况为 ASYNC_FLUSH
flushDiskType = SYNC_FLUSH
当Broker服务器未在同步刷盘时间内(默认为5s)完成刷盘,则将返回该状态——刷盘超时。
集群部署:
为了保证可用性,Broker 通常采用一主(master)多从(slave)部署方式。为了保证消息不丢失,消息还需要复制到 slave 节点。
默认方式下,消息写入 master 成功,就可以返回确认响应给生产者,接着消息将会异步复制到 slave 节点。
flushDiskType 默认值是ASYNC_FLUSH(异步刷盘)
若 master 突然宕机且不可恢复,那么还未复制到 slave 的消息将会丢失。
所以为了提高消息的可靠性,采用同步刷盘方式,master 节点将会同步等待 slave 节点复制完成,才会返回确认响应。
消费阶段
消费者从 broker 拉取消息,然后执行相应的业务逻辑。一旦执行成功,将会返回 ConsumeConcurrentlyStatus.CONSUME_SUCCESS 状态给 Broker。
如果 Broker 未收到消费确认响应或收到其他状态,消费者下次还会再次拉取到该条消息,进行重试。这样的方式有效避免了消费者消费过程发生异常,或者消息在网络传输中丢失的情况。但业务方需要考虑是否保证消息幂等。具体方案见上文。
如何处理消息堆积?
消费逻辑无问题并且消费耗时正常情况下,可以考虑下面方案:
-
消费者扩容
Topic 中 MessageQueue 的数量大于 Comsumer 实例数量时,可以增加 Consumer 实例数量。MessageQueue 会进行 Rebalance重新分配给 Consumer 实例。但要考虑 DB 读写压力。
-
消息迁移到新 Topic,然后扩容 MessageQueue
Topic 的 MessageQueue 的数量小于或者等于消费者数量,这种情况,再扩容消费者就没什么用,就得考虑扩容 MessageQueue。可以新建一个临时的 Topic,临时的Topic多设置一些 MessageQueue,然后先用一些消费者把消费的数据丢到临时的Topic,因为不用业务处理,只是转发一下消息,速度很快。接下来用扩容的消费者去消费新的 Topic 里的数据,消费完了之后,恢复原状。
-
根据具体业务能否限制消息发送速率 结合具体业务,使用令牌桶等限流算法限制消息发送速率。
如何实现顺序消息?
- 保证一组消息发送到同一分区队列,Consumer 保证同一队列只有一个线程消费。
22.4 kafka使用问题
什么是主题和分区? Kafka的消息用主题进行分类,主题下可以被分为若干个分区。分区本质上是个提交日志,有新消息,这个消息就会以追加的方式写入分区,然后用先入先出的顺序读取。
23 分库分表之后出现分布式事务问题如何解决?
2PC-二阶段提交
二阶段提交(Two-phase Commit),为了使基于分布式系统架构下的所有节点在进行事务提交时保持一致性而设计的一种算法。当一个事务跨多个节点时,为了保持事务的ACID特性,需要引入一个作为协调者的组件来掌握所有节点(称为参与者)的操作结果并最终指示这些节点是否要把操作结果进行真正的提交。
因此,二阶段提交的算法思路可以概括为:参与者将操作成败通知协调者,再由协调者根据所有参与者的反馈觉得各参与者是否要提交操作还是终止操作。
第一阶段(提交请求阶段)
- 协调者节点向所有参与者节点询问是否可以执行提交操作,并开始等待各参与者节点的响应。
- 参与者执行询问发起为止的所有事务操作,并将Undo信息和Redo信息写入日志。
- 各参与者节点响应协调者节点发起的询问。如果参与者节点的事务操作实际执行成功,则它返回一个“同意”消息;如果参与者的事务操作执行失败,则返回一个“终止”消息。
第二阶段(提交执行阶段)
成功(所有参与者都发送“同意”):
- 协调者节点向所有节点发出“正式提交”的请求。
- 参与者节点正式完成操作,并释放在整个事务期间内占用的资源。
- 参与者节点向协调者节点发送“完成”消息。
- 协调节点收到所有参与者节点反馈的“完成”消息后,完成事务。
失败(只要有一个参与者发送“终止”):
- 协调者向所有参与者节点发送“回滚操作”的请求。
- 参与者利用之前写入的Undo信息执行回滚,并释放整个事务期间内占用的资源。
- 参与者节点向协调者节点发送“回滚完成”消息。
- 协调者节点收到所有参与者节点反馈的“回滚完成”消息后,取消事务。
缺点
可以看到第二阶段需要等待所有参与者都返回消息,如果网络延迟呢?如果参与者出现故障呢?参与者持有的资源一直得不到释放。如果一个参与者一直没返回,协调者会超时后重新发送commit命令因为其他节点已经提交了事务不可能再回滚,只能利用重试实现最终一致。
3PC-二阶段提交
事务消息
就是利用消息队列比如rocketmq。
Redis的淘汰策略和删除策略
Redis的Key过期策略
Redis为Key设置过期时间,当到达过期时间后,Redis要将这个key删除掉,Redis过期Key提供了三种策略,分别为:定时删除、定期删除、惰性删除。