文章目录
- 面试题(一天五道)
- 第一天
- 1.redis五种类型
- 2.redis持久化方式有哪些
- 3.缓存雪崩是什么,如何解决
- 4 缓存击穿是什么,如何解决
- 5 缓存穿透是什么,如何解决
- 第二天
- 1.redis中的hash类型如何进行存储
- 1. 使用 `HSET` 命令设置单个字段和值:
- 2. 使用 `HMSET` 命令设置多个字段和值:
- 3. 使用 `HGET` 命令获取单个字段的值:
- 4. 使用 `HMGET` 命令获取多个字段的值:
- 5. 使用 `HDEL` 命令删除指定字段:
- 6. 使用 `HGETALL` 命令获取所有字段和值:
- 7. 使用 `HKEYS` 和 `HVALS` 命令获取所有字段和所有值:
- 2.redis两种持久化数据有什么区别 RDB与AOF
- RDB(Redis DataBase)
- AOF(Append Only File)
- 如何选择
- 3.介绍下redis分布式锁
- 4.项目中哪些功能用到过redis,具体说说怎么实现的
- 5.redis常用命令有哪些
- 数据类型相关命令
- 服务器管理命令
- 事务相关命令
- 其他常用命令
- 第三天
- 1.Redis有哪些优缺点
- 2.如何保证Redis与数据库的数据一致性
- 3.Redis的过期键的删除策略
- 4.Redis哨兵机是什么?有什么作用
- 5.制作消息队列的思路
- 第四天
- 1. 什么是AOP和IOC?
- 2. Spring常用注解(10个以上)
- 3. Spring Bean的生命周期与作用域
- 4. Spring的自动装配
- 5. Spring框架中都用到了哪些设计模式?
- 第五天
- 1. MyBatis中#{}和${}的区别
- 2. MyBatis的优缺点
- 3. Spring MVC的常用注解
- 4. Spring MVC执行流程
- 5. @SpringBootApplication介绍
- 第六天
- 1. Spring、Spring MVC、Spring Boot的区别
- 2. Spring Boot常用注解
- 3. 数据库设计三大范式
- 4. MySQL存储引擎MyISAM与InnoDB的区别
- 5. 什么是索引?索引的优缺点?
- 第七天
- 1. 索引的类型与种类
- 2. 项目中如何使用索引
- 3. 如何查看索引是否生效
- 4. 索引生效规则与避免失效
- 5. 唯一索引和主键索引的区别
- 第八天
- 1. 大表数据查询如何进行优化?
- 2. MySQL和Oracle的区别?
- 3. UNION与UNION ALL 的区别
- 4. 什么情况下会用到联合索引,以及生效规则
- 5. SQL优化的一般步骤,如何看执行计划(EXPLAIN),如何理解其中各个字段的含义。
- 第九天
- 1. 四大隔离级别
- 2. 事务的四大特性(ACID)
- 3. 脏读、幻读、不可重复读
- 4. 乐观锁和悲观锁
- 5. 死锁及避免方法
- 第十天
- 1. MySQL的多表查询(笛卡尔积原理)
- 2. 数据库表的拆分规则,你们项目数据库怎么拆分的?
- 3. 一条SQL语句在MySQL中如何执行的?
- 4. 什么是数据库连接池?为什么需要数据库连接池呢?
- 5. MySQL的内连接、左连接、右连接有什么区别?
- 第十一天
- 1. 进程和线程的区别
- 2. 为什么要使用多线程?
- 3. 如何创建一个多线程?
- 4. Runnable和Callable的区别
- 5. 线程状态有哪些?
- 第十二天
- 1. 如何让线程休眠?
- 2. 什么是死锁?如何避免死锁?
- 3. 什么是线程池?优点是什么?
- 4. 如何获得线程返回值?
- 5. String 为什么是常量,buffer 和 builder 的区别
- 第十三天
- 1. Controller 是单例的吗?是单线程的吗?线程是否安全,如何保证线程安全?
- 2. 程序开多少线程合适?
- 3. 描述一下 synchronized 和 Lock 区别
- 4. wait 和 sleep 的区别
- 5. 多线程之间是如何通信的?
- 第十四天
- 1. 多线程应用场景有哪些?
- 2. 项目当中哪些地方用到过线程?
- 3. 项目中的多线程怎么用,请具体说说(Spring 如何开启一个多线程)
- 4. 什么是 IO 流?
- 5. 流按照传输的单位怎么分类?分成哪两种流,并且它们的父类叫什么?说一下常用的 IO 流?
- 第十五天
- 1. 为什么要使用 RabbitMQ?
- 2. 使用 RabbitMQ 的场景?
- 3. Kafka、ActiveMQ、RabbitMQ、RocketMQ 有什么优缺点?
- 4. 如何确保消息正确地发送至 RabbitMQ? 如何确保消息接收方消费了消息?
- 5. 如何避免消息重复投递或重复消费?
- 第十六天
- 1. 消息基于什么传输?
- 2. 如何保证 RabbitMQ 消息的可靠传输?
- 3. 持久化怎么做?
- 4. 说说你项目中那个功能或模块使用了 MQ,如何做的,使用那种模式?
- 5. 怎么避免 RabbitMQ 发送消息时宕机的情况?
- 2. 如何保证 RabbitMQ 消息的可靠传输?
- 3. 持久化怎么做?
- 4. 说说你项目中那个功能或模块使用了 MQ,如何做的,使用那种模式?
- 5. 怎么避免 RabbitMQ 发送消息时宕机的情况?
面试题(一天五道)
第一天
1.redis五种类型
Redis支持五种主要的数据类型,它们分别是:
-
字符串(String): 最简单的数据类型,可以包含任何数据,比如文本、数字等。
-
哈希表(Hash): 用于存储对象,类似于其他编程语言中的关联数组或者对象。每个哈希表可以存储多个键值对。
-
列表(List): 一个有序的字符串元素集合,支持从两端压入和弹出元素,可以用作栈(stack)或队列(queue)。
-
集合(Set): 无序的字符串元素集合,每个元素都是唯一的。可以执行集合操作,如并集、交集、差集等。
-
有序集合(Sorted Set): 类似于集合,但每个元素都关联一个分数,根据分数进行排序。可以用于实现排行榜等功能。
这些数据类型的灵活性使得Redis在不同场景下都能提供高效的数据存储和检索。
2.redis持久化方式有哪些
Redis支持两种主要的持久化方式,分别是:
-
RDB(Redis DataBase): RDB 是一种快照(snapshot)持久化方式,通过将当前数据集的状态保存到磁盘上的二进制文件中。这个文件包含了某个时间点上所有键的值。RDB 是一种紧凑且经济的持久化方式,适用于数据备份和恢复。
- 触发条件:管理员手动执行 SAVE 或者 BGSAVE 命令,或者根据配置文件中的自动保存规则(save 指令)进行定期触发。
- 文件格式:默认以
dump.rdb
命名,可以通过配置文件指定其他名称。
-
AOF(Append Only File): AOF 是一种追加日志文件方式,记录每个写操作的命令,以追加的形式写入到磁盘文件中。通过重放这些命令,可以恢复数据集的状态。AOF 提供了更好的持久化实时性,适用于要求更小数据丢失的场景。
- 触发条件:每个写操作都会被追加到 AOF 文件。
- 文件格式:默认以
appendonly.aof
命名,可以通过配置文件指定其他名称。
- 文件格式:默认以
在实际使用中,可以根据应用的要求选择合适的持久化方式,也可以同时使用两种方式,以提高数据的安全性。如果同时启用了两种方式,Redis在恢复时通常会选择AOF文件进行恢复,因为AOF文件包含了更详细的操作日志。
3.缓存雪崩是什么,如何解决
缓存雪崩是指在缓存中的大量数据同时失效或者同时被清除,导致大量的请求直接访问数据库或其他数据源,引发瞬时的请求量激增,给系统带来过载的情况。通常,缓存雪崩发生在缓存中的数据具有相同的过期时间,或者在某个时刻被同时清理导致。
解决缓存雪崩的一些方法包括:
-
设置不同的过期时间: 如果所有缓存数据的过期时间相同,它们很可能在同一时刻失效,引发雪崩。通过给不同的缓存数据设置不同的过期时间,可以减少同时失效的概率。
-
使用互斥锁: 在缓存失效的时候,通过加锁的方式保证只有一个线程去重新生成缓存。其他线程等待缓存生成完成后再获取数据。
-
采用缓存预热: 在系统启动或者低峰期,提前加载缓存数据,防止在高峰期间大量缓存同时失效。
-
使用备份缓存: 在主缓存失效的情况下,可以通过备份缓存(例如另一个独立的缓存系统)来提供数据,避免直接请求数据库。
-
限流和降级: 在缓存失效导致请求量激增时,可以通过限制并发请求的数量,或者降低某些非关键服务的优先级,来减轻系统压力。
-
监控和报警: 设置合适的监控和报警机制,及时发现缓存失效问题,以便及时采取手段解决。
通过采取这些方法,可以有效地减缓或避免缓存雪崩带来的系统问题。
4 缓存击穿是什么,如何解决
缓存击穿是指一个存在的缓存因为在缓存过期的时刻,同时有大量的请求访问,而这些请求发现缓存过期后,无法获取到缓存,只能绕过缓存直接访问数据库,导致数据库负载剧增的情况。
解决缓存击穿的方法主要包括:
-
使用互斥锁或分布式锁: 在缓存失效的时候,可以使用互斥锁来保证只有一个线程去查询数据库,其他线程等待缓存的生成。分布式锁可以用于多台服务器之间的协调。
-
提前进行缓存预热: 在系统启动或者低峰期,提前加载缓存数据,防止在高峰期间缓存失效导致的问题。
-
设置短暂的缓存过期时间: 缓存过期时间设置为一个较短的时间,避免大量请求同时涌入。在查询数据库时,如果发现缓存失效,可以延长缓存过期时间,并重新加载缓存。
-
使用缓存穿透保护机制: 在查询数据库之前,可以通过布隆过滤器等机制判断请求的 key 是否有效,如果无效可以拒绝请求,避免对数据库的不必要访问。
-
采用二级缓存: 在缓存失效时,先从一个本地缓存或者内存数据库等二级缓存中获取数据,如果二级缓存中也没有,则再访问主缓存或者数据库。
-
使用缓存锁定: 在缓存失效时,可以通过一种缓存锁定机制,使只有一个请求去访问数据库,其他请求等待该请求的结果。
这些方法可以根据具体情况进行选择和组合,以有效地解决缓存击穿问题。
5 缓存穿透是什么,如何解决
缓存穿透是指查询一个不存在的数据,由于缓存没有命中,每次请求都直接访问数据库,导致大量的无效请求穿透缓存,给数据库带来不必要的压力。缓存穿透通常是由恶意攻击、恶意查询或者系统错误引起的。
解决缓存穿透的方法主要包括:
-
使用布隆过滤器: 布隆过滤器是一种数据结构,用于判断一个元素是否可能在集合中。在缓存层加上布隆过滤器,用于快速判断请求的 key 是否有效,如果无效就直接拒绝请求,避免对数据库的查询。
-
缓存空对象: 当查询数据库发现某个 key 对应的数据不存在时,仍然将这个 key 存入缓存,并设置一个较短的过期时间。这样可以避免相同的无效请求多次访问数据库。
-
使用互斥锁: 在查询数据库的时候,可以使用互斥锁来防止多个线程同时查询同一个不存在的 key,只有一个线程去查询数据库,其他线程等待查询结果。
-
设置空结果缓存: 如果数据库中查询结果为空,也将这个空结果缓存起来,设置一个较短的过期时间。这样可以防止频繁查询相同不存在的 key。
-
限制请求频率: 对于相同的查询请求,可以在缓存层设置一个频率限制,当达到一定频率时直接拒绝请求,防止攻击。
-
使用二级缓存: 在缓存失效时,先从一个本地缓存或者内存数据库等二级缓存中获取数据,如果二级缓存中也没有,则再访问主缓存或者数据库。
选择合适的方法取决于具体的业务场景和需求,可以根据实际情况进行灵活配置和组合。
第二天
1.redis中的hash类型如何进行存储
在Redis中,Hash 类型是一种用于存储字段和与字段关联的值的数据结构。以下是在Redis中存储 Hash 类型的基本方式:
1. 使用 HSET
命令设置单个字段和值:
HSET key field value
这个命令将在指定的 Hash 中设置一个字段和对应的值。如果该字段已经存在,它将被更新;如果不存在,将创建新的字段。
2. 使用 HMSET
命令设置多个字段和值:
HMSET key field1 value1 field2 value2 ...
HMSET
命令一次性设置多个字段和对应的值。
3. 使用 HGET
命令获取单个字段的值:
HGET key field
这个命令用于获取指定 Hash 中的字段的值。
4. 使用 HMGET
命令获取多个字段的值:
HMGET key field1 field2 ...
HMGET
命令一次性获取多个字段的值。
5. 使用 HDEL
命令删除指定字段:
HDEL key field1 field2 ...
HDEL
命令用于从 Hash 中删除指定的字段。
6. 使用 HGETALL
命令获取所有字段和值:
HGETALL key
HGETALL
命令返回指定 Hash 中所有字段和值,以列表形式返回。
7. 使用 HKEYS
和 HVALS
命令获取所有字段和所有值:
HKEYS key
返回指定 Hash 中所有字段的列表。
HVALS key
返回指定 Hash 中所有值的列表。
这些命令提供了对 Redis 中 Hash 类型数据的基本操作。使用这些命令,你可以方便地进行字段的设置、获取、更新和删除操作。
2.redis两种持久化数据有什么区别 RDB与AOF
Redis支持两种主要的持久化方式,分别是RDB(Redis DataBase)和AOF(Append Only File)。这两种方式在实现和使用上有一些区别:
RDB(Redis DataBase)
-
触发机制: RDB 是通过定时快照或手动触发保存的方式进行持久化。管理员可以手动执行
SAVE
或者BGSAVE
命令,也可以根据配置文件中的自动保存规则(save
指令)进行定时触发。 -
文件格式: RDB 以二进制文件形式保存在磁盘上,通常以
dump.rdb
命名。 -
数据恢复: 在恢复时,Redis 会读取 RDB 文件,将其中的数据加载到内存中。RDB 文件是一个快照,恢复速度较快。
-
适用场景: RDB 适合用于备份和恢复,以及对数据一定程度的容忍度,因为 RDB 是定时触发的,可能导致在两次快照之间的数据丢失。
AOF(Append Only File)
-
触发机制: AOF 以追加日志的方式记录每个写操作的命令,是实时记录的。每个写操作都会追加到 AOF 文件中。
-
文件格式: AOF 以文本格式保存在磁盘上,通常以
appendonly.aof
命名。 -
数据恢复: 在恢复时,Redis 会重新执行 AOF 文件中的命令,将数据还原到内存中。AOF 文件包含了操作的历史记录,因此恢复过程相对较慢。
-
适用场景: AOF 适合对数据一定程度的实时性要求,因为每个写操作都会即时记录。由于 AOF 文件是追加的,相较于 RDB,它对数据的丢失更少。
如何选择
- 如果对于数据的实时性要求较高,可以选择使用 AOF。
- 如果对于数据备份和恢复速度要求较高,可以选择使用 RDB。
- 通常情况下,也可以同时开启 AOF 和 RDB,以提供更好的数据安全性和灾难恢复能力。
综合考虑业务需求、数据安全性和性能等因素,选择适合自己场景的持久化方式。
3.介绍下redis分布式锁
在Redis中,分布式锁是一种用于在分布式环境中实现互斥访问共享资源的机制。由于分布式系统的特性,传统的单节点锁机制无法满足多节点的分布式环境需求。Redis提供了一种简单而有效的分布式锁实现,通常使用SET命令结合NX(Not eXists)参数或者SET命令结合EX(EXpire)参数来实现。
下面是一种使用SET和NX参数实现的分布式锁的基本示例:
SET lock_key "some_unique_value" NX EX max_lock_time
lock_key
是用于标识锁的键。"some_unique_value"
是一个唯一的值,通常可以使用UUID或者其他唯一标识。NX
表示只有在lock_key
不存在时才设置成功,用于确保只有一个客户端能够获得锁。EX max_lock_time
表示设置锁的过期时间,以防止锁因为某些原因没有被释放而一直存在。这个时间应该设置为足够长,以确保在持有锁的客户端处理完任务后能够及时释放锁。
如果SET命令执行成功,说明当前客户端成功获取了锁,否则表示锁已经被其他客户端占用。
释放锁的操作可以通过DEL命令或者Lua脚本来实现,确保只有持有锁的客户端才能够释放锁,避免误释放。
DEL lock_key
使用Redis分布式锁时需要注意一些问题:
-
死锁: 确保在获取锁之后,客户端能够正常执行释放锁的操作,避免因异常情况而导致死锁。
-
锁过期时间: 设置合适的锁过期时间,以避免锁长时间被占用而不释放。
-
唯一标识: 保证锁的唯一性,避免不同业务使用相同的锁键。
分布式锁在分布式系统中广泛应用,用于保证多个节点间的协同工作,确保共享资源的同步和互斥。
4.项目中哪些功能用到过redis,具体说说怎么实现的
我无法直接访问个别用户的项目信息,但我可以提供一些常见的使用场景,这些场景可能涉及到在项目中使用 Redis 的功能。
-
会话管理: Redis 可以用于存储用户会话信息,例如用户登录状态、权限信息等。通过在 Redis 中存储会话数据,可以实现分布式系统中的会话共享。
-
缓存加速: 将热门的、频繁访问的数据缓存到 Redis 中,从而减轻数据库负载,提高系统的访问速度。
-
分布式锁: Redis 可以用于实现分布式锁,确保在分布式环境中对某个资源或操作的互斥访问。通过 Redis 提供的原子性操作,可以有效避免竞争条件。
-
消息队列: Redis 的发布订阅机制可以用于实现简单的消息队列,用于解耦系统中不同组件之间的通信。
-
计数器和统计信息: Redis 提供的原子性操作使其成为实现计数器和统计信息的理想选择。比如,可以用 Redis 实现网站访问次数计数。
-
实时数据分析: 将实时产生的数据存储在 Redis 中,可以实现实时数据分析和监控,例如实时用户在线人数、实时日志分析等。
以下是一个简单的例子,演示如何在 Python 中使用 Redis 进行会话管理:
import redis
from flask import Flask, request, session, redirect, url_forapp = Flask(__name__)
app.secret_key = 'your_secret_key' # Flask 需要设置一个 secret_key 来加密会话数据
redis_client = redis.StrictRedis(host='localhost', port=6379, decode_responses=True)@app.route('/')
def index():if 'username' in session:return f'Hello, {session["username"]}!'return 'You are not logged in.'@app.route('/login', methods=['POST'])
def login():username = request.form['username']password = request.form['password']# 检查用户名和密码,假设验证通过# ...# 将用户信息存储在 Redis 中,模拟会话管理session['username'] = usernamereturn redirect(url_for('index'))@app.route('/logout')
def logout():session.pop('username', None)return redirect(url_for('index'))if __name__ == '__main__':app.run(debug=True)
在这个简单的例子中,Flask 使用 Redis 存储会话信息,当用户登录时,将用户名存储在 Redis 中。这只是一个示例,实际项目中可能涉及更多的场景和功能。
5.redis常用命令有哪些
Redis 提供了丰富的命令集,包括数据操作、事务、持久化、复制、发布订阅等方面的命令。以下是一些常用的 Redis 命令:
数据类型相关命令
-
字符串(String):
SET key value
:设置指定键的值。GET key
:获取指定键的值。INCR key
:将指定键的值加1。
-
哈希表(Hash):
HSET key field value
:设置哈希表中指定字段的值。HGET key field
:获取哈希表中指定字段的值。HGETALL key
:获取哈希表中所有字段和值。
-
列表(List):
LPUSH key value
:将一个值插入到列表头部。RPUSH key value
:将一个值插入到列表尾部。LPOP key
:移除并返回列表的第一个元素。
-
集合(Set):
SADD key member
:向集合中添加一个或多个成员。SMEMBERS key
:获取集合中的所有成员。SINTER key1 key2
:返回两个集合的交集。
-
有序集合(Sorted Set):
ZADD key score member
:将一个成员的分数加到有序集合中。ZRANGE key start stop
:通过索引区间返回有序集合指定区间内的成员。
服务器管理命令
-
信息命令:
INFO
:获取关于 Redis 服务器的各种信息和统计数值。PING
:检测服务器是否可用。
-
持久化:
SAVE
:同步保存数据到硬盘。BGSAVE
:异步保存数据到硬盘。
-
复制:
SLAVEOF host port
:将当前服务器设置为指定服务器的从服务器。
事务相关命令
- 事务:
MULTI
:标记一个事务块的开始。EXEC
:执行所有事务块命令。DISCARD
:取消事务,放弃执行事务块内的所有命令。
其他常用命令
-
键操作:
DEL key
:删除一个键。EXISTS key
:检查键是否存在。KEYS pattern
:查找所有符合给定模式的键。
-
过期时间:
EXPIRE key seconds
:为键设置过期时间。TTL key
:获取键的剩余过期时间。
-
发布与订阅:
PUBLISH channel message
:将消息发送到指定频道。SUBSCRIBE channel
:订阅一个或多个频道。
这只是 Redis 命令的一小部分,实际应用中可能会根据具体场景使用更多的命令。不同版本的 Redis 可能会有新增或废弃的命令,建议查阅官方文档获取详细信息。
第三天
1.Redis有哪些优缺点
Redis是一种开源的内存数据库,它具有许多优点和一些缺点。
优点:
-
性能卓越: Redis的主要优势之一是其卓越的性能。由于数据存储在内存中,它可以提供非常快速的读写操作,适用于对速度要求较高的应用。
-
支持丰富的数据结构: Redis支持多种数据结构,包括字符串、列表、集合、散列表等。这使得它非常适用于各种用途,如缓存、消息队列等。
-
持久化选项: Redis支持不同的持久化选项,可以将数据保存到磁盘上,确保在重启时不会丢失数据。
-
原子操作: Redis支持原子操作,这意味着可以在单个操作中执行多个命令,确保数据的一致性。
-
社区活跃: Redis有一个庞大而活跃的社区,提供了广泛的文档和支持。
缺点:
-
内存消耗较高: 由于数据存储在内存中,对于大规模数据集,Redis的内存消耗可能较高。这可能导致需要更多的硬件资源。
-
持久化性能开销: 当启用持久化时,会对性能产生一定的开销。根据不同的持久化选项,性能影响可能不同。
-
单线程模型: Redis采用单线程模型,这对于一些高并发写入的场景可能是一个瓶颈。虽然可以通过多实例来横向扩展,但对于某些应用可能不够理想。
-
缺乏复杂查询语言: Redis的查询能力相对较弱,不如一些关系型数据库提供的复杂查询语言。
总体而言,Redis是一个强大的工具,特别适用于需要快速读写操作和对性能要求较高的场景。选择是否使用Redis应该根据具体的应用需求来决定。
2.如何保证Redis与数据库的数据一致性
保持Redis与数据库的数据一致性是一个挑战,特别是在涉及到数据更新和持久性方面。以下是一些常见的方法来保证Redis与数据库的数据一致性:
-
写入时更新数据库: 在更新Redis中的数据时,确保同时更新数据库。这可以通过在业务逻辑中使用事务或其他方式来实现。这样可以避免Redis和数据库中的数据不一致。
-
使用队列: 将数据变更操作放入一个消息队列中,然后异步处理这些消息来更新数据库。这种方式可以提高性能,同时保持数据的一致性。在消息队列中,你可以使用类似Redis的数据结构来存储消息,确保消息的持久性。
-
使用数据库的事务: 如果数据库支持事务,可以在更新Redis的同时使用数据库事务,以确保数据的原子性操作。这可以避免在更新Redis时发生错误导致数据不一致的问题。
-
定期同步: 定期检查Redis和数据库之间的数据差异,并进行同步。这可能涉及到全量或增量的同步,具体取决于应用的需求。这种方法可能会增加系统的负担,但可以确保数据的一致性。
-
使用外部工具: 有一些专门用于缓存和数据库之间数据同步的工具,例如Redis的模块或第三方工具。这些工具可以简化同步过程,并提供一些额外的功能,如冲突解决和错误处理。
-
利用数据库触发器: 在数据库中设置触发器,以在数据发生更改时通知Redis。这需要确保触发器能够异步地更新Redis,以避免对数据库操作的影响。
选择哪种方法取决于你的具体应用场景和需求。通常,结合多种方法可以提供更为强健的一致性保障。需要根据应用的特点权衡性能和一致性之间的取舍。
3.Redis的过期键的删除策略
Redis使用一种惰性删除和定期删除的策略来处理过期键的删除。以下是Redis中过期键删除的详细说明:
-
惰性删除(Lazy Expiration):
- 当访问一个带有过期时间的键时,Redis首先检查键是否过期。
- 如果键过期,则会被标记为删除,但并不会立即从内存中删除。
- 当客户端再次尝试访问该键时,Redis会检查它是否过期并删除它,这样可以确保在需要时才进行删除操作,而不是在键过期时立即删除。
-
定期删除(定时任务):
- Redis使用一个定时任务,每隔一段时间(以秒为单位,默认为每100毫秒检查一次)检查一批随机选择的过期键。
- 该任务负责删除那些已过期但尚未被惰性删除的键。
- 定期删除是为了处理那些可能因为一些原因(例如,客户端没有再次访问该键)而没有被惰性删除的过期键。
这两种删除策略的结合,确保了在键过期时尽可能快速地释放内存。这种方式允许Redis保持高性能,避免在每次访问键时都进行过期键检查,而同时也能确保过期键最终会被删除。
值得注意的是,Redis的过期键删除策略是近似的,不是精确的。这是因为定期删除任务只检查一小部分过期键,而不是所有过期键。这种近似策略是为了保持性能,因为精确检查所有过期键可能会对性能产生显著影响。
4.Redis哨兵机是什么?有什么作用
Redis哨兵(Redis Sentinel)是一个用于监控和管理Redis实例的分布式系统。它的主要目的是提供高可用性(High Availability)和故障转移(Failover)的支持。以下是Redis哨兵的主要作用:
-
监控: 哨兵系统定期检查Redis主从节点的健康状态。它可以检测主节点是否宕机、从节点是否同步,以及哨兵自身的健康状态。
-
故障检测: 当一个主节点宕机或处于不可用状态时,哨兵可以自动检测到并触发故障转移操作。
-
故障转移: 当主节点宕机时,哨兵会从已经存在的从节点中选择一个升级为新的主节点,确保系统的高可用性。这个过程是自动的,无需人工干预。
-
配置提供者: 哨兵还充当配置提供者的角色,它可以通知客户端关于Redis集群中哪个节点是主节点、哪些节点是从节点等信息,帮助客户端发现和连接到可用的节点。
-
自动服务恢复: 当主节点重新可用时,哨兵可以将它重新加入集群,进行自动的服务恢复。
通过使用Redis哨兵,可以建立一个具有高可用性的Redis集群,确保在主节点发生故障时自动切换到备用节点,减小系统的停机时间。这对于对数据可用性要求较高的应用非常重要,例如缓存系统和实时数据处理系统。哨兵系统可以配置为监视多个Redis实例,提供灵活且可靠的高可用性解决方案。
5.制作消息队列的思路
制作消息队列通常涉及以下几个关键思路:
-
明确定义消息格式: 在使用消息队列之前,首先需要明确定义你的消息格式。消息格式应包括必要的信息,以便生产者和消费者能够正确地理解和处理消息。这可能包括标识符、数据、元数据等。
-
选择合适的消息队列系统: 选择适合你需求的消息队列系统。一些常见的消息队列系统包括RabbitMQ、Apache Kafka、ActiveMQ、Redis等。不同的消息队列系统有不同的特性,例如持久性、吞吐量、可靠性等,根据你的需求选择合适的系统。
-
设计生产者和消费者: 将系统分为生产者和消费者两个部分。生产者负责产生消息并将其发送到消息队列中,而消费者则负责从队列中获取消息并进行处理。确保生产者和消费者之间的接口清晰,并考虑可能的异常情况。
-
实现消息确认机制: 引入消息确认机制以确保消息能够被可靠地传递。这可能涉及到消息的持久性,消费者接收消息后发送确认,以及处理消息处理失败的情况。
-
考虑消息的顺序性: 某些应用场景可能需要消息的顺序性。在设计中要考虑如何维护消息的顺序,以及是否需要对消息进行分区和分组。
-
监控和管理: 实现监控和管理机制,以便能够追踪消息队列中的消息状态,监控生产者和消费者的性能,并在需要时进行扩展或调整。
-
处理重复消息: 考虑在系统中处理可能的重复消息,以确保消息不会被重复处理。
-
安全性考虑: 对于一些敏感信息,需要考虑消息队列的安全性,例如消息加密、访问控制等。
-
容错和恢复机制: 在设计中引入容错和恢复机制,以处理可能的故障,例如消息队列的宕机、网络故障等。
-
合理设置消息队列参数: 根据实际需求,合理设置消息队列的参数,例如队列大小、消息过期时间、优先级等。
以上思路提供了一个制作消息队列的基本指导,具体的实现方式会依赖于具体的需求和选用的消息队列系统。
第四天
1. 什么是AOP和IOC?
-
AOP(Aspect-Oriented Programming): AOP是一种编程范式,它的目的是通过在程序中插入特定切面(Aspect)的方式,将横切关注点(cross-cutting concerns)从主业务逻辑中分离出来。AOP通过在程序运行时动态地将切面织入到代码中,实现了模块化的横切关注点。
-
IOC(Inversion of Control): IOC是一种设计思想,它将对象的创建、依赖关系的管理交由容器来完成,而不是由对象自己来管理。Spring框架是一个典型的IOC容器,通过依赖注入(Dependency Injection)的方式实现对象之间的解耦,降低组件之间的耦合度。
2. Spring常用注解(10个以上)
@Component
:用于标识一个类为Spring的组件。@Repository
:用于标识一个类为数据访问组件。@Service
:用于标识一个类为服务组件。@Controller
:用于标识一个类为Spring MVC中的控制器。@Autowired
:用于自动装配Bean。@Qualifier
:与@Autowired
一同使用,指定注入的Bean的名称。@Value
:用于注入属性值。@Configuration
:标识一个类为配置类。@Bean
:在配置类中使用,用于声明一个Bean。@Scope
:用于指定Bean的作用域。@PostConstruct
:在Bean初始化完成后执行的方法。@PreDestroy
:在Bean销毁之前执行的方法。
3. Spring Bean的生命周期与作用域
-
生命周期:
- 实例化:通过构造方法或工厂方法创建Bean实例。
- 属性赋值:通过setter方法或直接赋值将属性注入。
- 初始化:调用
@PostConstruct
注解的方法或实现InitializingBean
接口的afterPropertiesSet
方法。 - 使用:Bean可以被应用程序使用。
- 销毁:调用
@PreDestroy
注解的方法或实现DisposableBean
接口的destroy
方法。
-
作用域:
- singleton(默认): 在整个Spring容器中只有一个Bean实例。
- prototype: 每次注入或者通过
getBean
方法获取Bean时,都会创建一个新的实例。 - request: 每个HTTP请求都会创建一个新的实例,仅在Web应用中有效。
- session: 每个HTTP Session都会创建一个新的实例,仅在Web应用中有效。
- global-session: 类似于
session
作用域,但在整个应用的全局范围内有效,仅在Web应用中有效。
4. Spring的自动装配
Spring的自动装配是通过@Autowired
注解实现的。它可以自动将符合类型的Bean注入到需要的地方。有三种自动装配的方式:
- 按类型自动装配(byType): 通过属性的数据类型自动匹配并注入。
- 按名称自动装配(byName): 通过属性的名称与Bean的名称匹配并注入。
- 构造器自动装配(constructor): 类似于按类型自动装配,但是应用于构造方法。
5. Spring框架中都用到了哪些设计模式?
在Spring框架中,常见的设计模式包括:
-
单例模式(Singleton): Spring默认情况下使用单例模式,保证一个Bean在容器中只有一个实例。
-
工厂模式(Factory): Spring使用工厂模式通过
ApplicationContext
来创建和管理Bean。 -
代理模式(Proxy): Spring AOP的实现就使用了代理模式,包括静态代理和动态代理。
-
观察者模式(Observer): Spring中的事件机制就是观察者模式的一种实现。
-
策略模式(Strategy): Spring中的
@Qualifier
注解用于解决自动装配时的歧义性,可以看作是策略模式的一种应用。
这些设计模式有助于提高代码的可维护性、可扩展性,并降低组件之间的耦合度。
第五天
1. MyBatis中#{}和${}的区别
-
#{}: 用于预编译,将参数以占位符的形式传入SQL语句,MyBatis会使用PreparedStatement预编译SQL,可以有效防止SQL注入攻击。
SELECT * FROM users WHERE id = #{userId}
-
${}: 直接将参数值拼接在SQL语句中,不进行预编译。存在SQL注入的风险,应该避免直接使用用户输入的值。
SELECT * FROM users WHERE id = ${userId}
使用#{}
更安全,能够防止SQL注入,同时也能防止一些特殊字符引起的问题。
2. MyBatis的优缺点
优点:
-
简化数据库操作: MyBatis能够通过映射文件将Java对象与数据库表进行关联,提供了简单的CRUD操作。
-
动态SQL: MyBatis支持动态SQL,可以根据不同的条件生成不同的SQL语句,提高了灵活性。
-
缓存机制: MyBatis提供了一级缓存和二级缓存,能够提高查询性能。
-
灵活的映射: 提供了强大的结果映射功能,支持复杂对象的映射。
缺点:
-
学习成本: 对于初学者来说,学习MyBatis需要掌握XML配置以及一定的SQL知识,相对于使用ORM框架的学习曲线可能较陡峭。
-
繁琐的XML配置: 需要进行大量的XML配置,可能会显得繁琐,尤其是对于简单的CRUD操作。
-
与数据库的耦合: 在使用MyBatis时,需要编写SQL语句,这使得应用与特定的数据库有一定的耦合性。
3. Spring MVC的常用注解
Spring MVC中的常用注解包括:
-
@Controller
:用于标识一个类为Spring MVC的Controller。 -
@RequestMapping
:用于映射请求的URL到具体的处理方法。 -
@RequestParam
:用于从请求中获取参数值。 -
@PathVariable
:用于将URL中的模板变量映射到方法参数。 -
@ModelAttribute
:用于在请求处理方法执行前将一些预处理的操作放在model中,通常用于数据绑定。 -
@ResponseBody
:用于将方法返回的对象转换为JSON格式或其他格式的响应体。 -
@ResponseStatus
:用于指定方法返回的HTTP状态码和原因。 -
@SessionAttributes
:用于声明哪些模型属性应该存储在会话中。 -
@ExceptionHandler
:用于定义在Controller内部处理异常的方法。 -
@InitBinder
:用于定义初始化数据绑定器的方法。
4. Spring MVC执行流程
Spring MVC的执行流程包括:
-
请求的到达: 客户端发送HTTP请求到达前端控制器(DispatcherServlet)。
-
处理器映射器匹配处理器: DispatcherServlet调用处理器映射器(HandlerMapping)来匹配请求到一个处理器(Controller)。
-
处理器执行: 处理器执行,进行业务逻辑处理。
-
视图解析器解析视图: 处理器返回一个逻辑视图名,DispatcherServlet调用视图解析器(ViewResolver)解析为具体的视图。
-
视图渲染: 视图渲染将模型数据填充到视图中。
-
响应返回: 渲染后的视图返回给前端控制器,前端控制器将响应返回给客户端。
5. @SpringBootApplication介绍
@SpringBootApplication
是一个组合注解,用于标注一个主配置类,通常是Spring Boot应用的入口类。它包含了以下三个注解的组合:
-
@Configuration
:标识该类是一个配置类,可以被AnnotationConfigApplicationContext或ClassPathXmlApplicationContext扫描并用于构建Bean定义。 -
@EnableAutoConfiguration
:开启Spring Boot的自动配置功能,根据类路径下的依赖库自动配置Spring框架所需的Bean。 -
@ComponentScan
:自动扫描并加载符合条件的组件(包括@Controller、@Service等),使其成为Spring容器中的Bean。
这一个注解的使用简化了配置,使得开发者可以更加专注于应用的业务逻辑而不用过多关心框架的配置。
第六天
1. Spring、Spring MVC、Spring Boot的区别
-
Spring: 是一个全功能的企业级框架,提供了大量的功能,包括依赖注入、AOP、事务管理、数据访问等。Spring是一个容器,负责管理和组织各个组件。
-
Spring MVC: 是Spring框架的一部分,用于构建Web应用程序的MVC框架。它提供了一个基于注解的模型,支持灵活的URL映射、视图解析、数据绑定等,用于处理Web请求和响应。
-
Spring Boot: 是Spring团队提供的用于简化和加速Spring应用程序开发的框架。Spring Boot通过约定大于配置的方式,提供了一套开发规范,可以快速搭建、开发和部署Spring应用,使得开发者能够更专注于业务逻辑。
2. Spring Boot常用注解
-
@SpringBootApplication
:标记主程序类,表示这是一个Spring Boot应用。 -
@Controller
:用于标识一个类为Spring MVC的Controller。 -
@RestController
:结合@Controller
和@ResponseBody
,用于标识RESTful风格的Controller。 -
@RequestMapping
:用于映射请求的URL到具体的处理方法。 -
@Autowired
:用于自动装配Bean。 -
@Value
:用于注入配置文件中的值。 -
@Configuration
:标识一个类为配置类。 -
@Bean
:在配置类中使用,用于声明一个Bean。 -
@Service
:用于标识一个类为服务组件。 -
@ComponentScan
:用于指定要扫描的包。
3. 数据库设计三大范式
-
第一范式(1NF): 数据表中的每一列都是不可拆分的基本数据项,不包含重复的列。确保每个字段的原子性。
-
第二范式(2NF): 在满足1NF的基础上,非主键属性完全依赖于主键,而不是部分依赖。表中不存在部分依赖关系。
-
第三范式(3NF): 在满足2NF的基础上,消除传递依赖。即非主键属性不依赖于其他非主键属性。
4. MySQL存储引擎MyISAM与InnoDB的区别
-
MyISAM:
- 不支持事务,不支持外键。
- 表级锁定,对于读操作效率较高,但在写操作并发性能较差。
- 支持全文本索引。
-
InnoDB:
- 支持事务和外键。
- 行级锁定,对于写操作并发性能较好。
- 支持事务的ACID特性。
- 支持外键约束。
选择存储引擎要根据应用场景和需求进行权衡,MyISAM适合读密集型操作,而InnoDB适合写密集型操作和要求事务支持的场景。
5. 什么是索引?索引的优缺点?
-
索引: 索引是数据库表中一列或多列的值,用于提高查询速度。通过索引,数据库可以快速定位到表中的特定行,而不必扫描整个表。
-
索引的优点:
- 提高检索速度,加速数据查询。
- 加速表与表之间的连接。
- 通过唯一索引强制数据完整性。
-
索引的缺点:
- 占用额外的存储空间。
- 对数据的插入、删除和更新操作会有一定的性能影响。
- 创建和维护索引需要额外的时间和资源。
- 不当使用索引可能导致查询性能下降,例如过多的索引或者不合适的索引选择。
在设计索引时需要根据实际的查询场景和数据特点进行综合考虑,合理使用索引以提高查询性能。
第七天
1. 索引的类型与种类
主要的索引类型有:
- 单列索引: 对表中的单个列创建的索引。
- 唯一索引: 与普通索引类似,但要求所有的索引列的值都是唯一的。
- 复合索引: 对表中的多个列创建的索引,可以包含多个列的值。
- 全文本索引: 用于全文搜索的索引,适用于文本字段。
- 空间索引: 用于空间数据类型(GIS数据类型)的索引。
2. 项目中如何使用索引
在项目中使用索引时,需要注意以下几点:
-
选择合适的字段: 根据查询的需求选择合适的字段创建索引,避免过多或不必要的索引。
-
避免过多的索引: 过多的索引不仅占用存储空间,而且在更新操作时会增加维护成本,同时可能引发查询优化器选择不合适的索引。
-
避免在小表上创建索引: 对于小型表,全表扫描可能更为高效,不需要额外的索引。
-
定期维护和优化索引: 定期检查和优化索引,删除不再使用的索引,重新组织索引以提高性能。
3. 如何查看索引是否生效
可以通过执行数据库的性能优化工具或者使用数据库管理工具进行查看。在MySQL中,可以使用EXPLAIN
关键字来查看查询的执行计划,观察是否使用了索引。
EXPLAIN SELECT * FROM your_table WHERE your_condition;
4. 索引生效规则与避免失效
索引生效规则:
- 尽量使用覆盖索引,即索引包含了查询所需的所有列。
- 索引列的数据类型要匹配,避免类型转换。
- 在多表连接时,连接条件上的字段应该创建索引。
- 在使用范围查询时,确保被查询的字段上有索引。
避免索引失效:
- 避免在索引列上使用函数,这会导致索引失效。
- 尽量避免使用
OR
条件,可以改写为UNION
。 - 谨慎使用通配符(例如
%
)开头的LIKE
查询。 - 避免在索引列上进行计算或使用表达式。
- 注意不要过度索引,过多的索引可能导致优化器选择不合适的索引。
5. 唯一索引和主键索引的区别
-
主键索引: 主键索引是一种特殊的唯一索引,它要求索引列的值唯一且不为空。一个表只能有一个主键索引,主键索引通常用于唯一标识表中的每一行记录。
-
唯一索引: 唯一索引要求索引列的值在整个表中唯一,但允许有空值。一个表可以有多个唯一索引。
在使用上,主键索引通常用于标识一条记录,而唯一索引用于保证某一列或多列的值的唯一性。主键索引自带聚簇索引的特性,而唯一索引在MySQL中可以选择是否使用聚簇索引。
第八天
1. 大表数据查询如何进行优化?
针对大表数据查询的优化,可以考虑以下方面:
-
合理使用索引: 确保查询的字段上有合适的索引,避免全表扫描。
-
分区表: 使用分区表将表数据分散存储,可以加速查询操作。
-
分页查询: 对于用户界面展示,使用分页查询,限制每次查询的返回行数。
-
定期维护统计信息: 定期分析表的统计信息,确保优化器能够选择最优的执行计划。
-
垂直拆分和水平拆分: 将大表拆分成多个小表,或者将表按列进行拆分,减小单表的数据量。
-
使用缓存: 对于一些静态数据,可以考虑使用缓存来减轻数据库的压力。
2. MySQL和Oracle的区别?
MySQL和Oracle的主要区别包括:
-
开发公司: MySQL由瑞典MySQL AB公司开发,现在属于Oracle公司;Oracle是由美国Oracle公司开发。
-
许可方式: MySQL使用GPL许可证,免费开源;Oracle数据库是商业数据库,需要购买许可证。
-
性能和规模: Oracle通常被认为适用于大型企业级应用,而MySQL适用于中小型应用。
-
事务处理: Oracle对事务处理和并发性能有更好的支持,支持更高级的事务隔离级别。
-
存储过程和触发器: Oracle的存储过程和触发器支持更丰富,拥有更多的功能。
3. UNION与UNION ALL 的区别
UNION和UNION ALL的主要区别:
-
UNION: 对两个或多个查询的结果集进行合并,同时去除重复的行。即,如果两个查询的结果集中存在相同的行,则只保留一次。
-
UNION ALL: 也对两个或多个查询的结果集进行合并,但不去除重复的行,即保留所有行,包括重复的行。
区别总结:
- UNION会对结果集进行去重,性能较差,但返回的结果集不包含重复行。
- UNION ALL直接合并结果集,性能较好,但返回的结果集可能包含重复行。
4. 什么情况下会用到联合索引,以及生效规则
联合索引(Composite Index): 是指对表的多个列进行组合的索引,用于加速查询涉及这些列的条件。
使用场景:
- 当查询语句中涉及多个列的条件,并且这些列组合在一起是唯一的,可以考虑使用联合索引。
生效规则:
-
联合索引的生效规则和查询条件的顺序有关,通常来说,如果查询条件中使用了索引的左侧列,索引会被用到。例如,对于
(a, b, c)
的联合索引,查询条件WHERE a = 1 AND b = 2
可以使用该索引,而WHERE b = 2 AND c = 3
无法使用。 -
联合索引在满足最左匹配原则的情况下能够提供索引的优化。
5. SQL优化的一般步骤,如何看执行计划(EXPLAIN),如何理解其中各个字段的含义。
SQL优化一般步骤:
-
分析SQL语句: 仔细阅读和分析SQL语句,理解其逻辑。
-
使用索引: 确保查询的字段上有合适的索引。
-
避免全表扫描: 尽量避免在大表上进行全表扫描,考虑使用合适的索引。
-
优化查询条件: 确保查询条件能够充分利用索引,避免使用不必要的函数包装字段。
-
使用合适的连接方式: 根据实际需求选择合适的连接方式,如INNER JOIN、LEFT JOIN等。
-
限制返回行数: 对于用户界面展示,使用分页查询限制每次查询的返回行数。
执行计划(EXPLAIN):
在MySQL中,可以使用EXPLAIN
关键字查看SQL语句的执行计划,例如:
EXPLAIN SELECT * FROM your_table WHERE your_condition;
各个字段的含义:
- id: 查询标识,表示查询的序列号,可以忽略。
- select_type: 查询类型,如SIMPLE、PRIMARY等,表示查询的复杂度。
- table: 表名,表示哪个表参与查询。
- type: 访问类型,表示查询时使用了何种访问方法,如ALL、INDEX等。
- possible_keys: 显示可能应用在这张表中的索引,但不一定被查询使用。
- key: 实际使用的索引。
- key_len: 表示索引中被选择的长度。
- ref: 显示索引的哪一列被使用了,如果可能的话,是一个常数。
- rows: 表示MySQL认为必须检查的用来返回请求数据的行数。
- Extra: 包含MySQL解决查询的一些额外信息,如使用了临时表、使用了文件排序等。
第九天
1. 四大隔离级别
在数据库中,事务的隔离级别指的是多个事务之间的隔离程度。SQL标准定义了四个隔离级别,由低到高分别是:
-
READ UNCOMMITTED(读取未提交): 允许一个事务读取另一个事务未提交的数据,可能导致脏读、幻读和不可重复读。
-
READ COMMITTED(读取已提交): 一个事务只能读取另一个事务已经提交的数据,避免了脏读,但仍可能出现幻读和不可重复读。
-
REPEATABLE READ(可重复读): 在一个事务中,多次读取相同的数据会保持一致性,避免了脏读和不可重复读,但仍可能出现幻读。
-
SERIALIZABLE(可串行化): 最高的隔离级别,确保事务的串行执行。避免了脏读、不可重复读和幻读,但性能较差。
2. 事务的四大特性(ACID)
事务的四大特性,通常称为ACID:
-
原子性(Atomicity): 事务是一个不可分割的工作单位,要么全部执行,要么全部回滚。
-
一致性(Consistency): 事务执行前后,数据库的状态应保持一致,即从一个一致性状态到另一个一致性状态。
-
隔离性(Isolation): 多个事务并发执行时,每个事务都应该感觉不到其他事务的存在,每个事务应该是相互隔离的。
-
持久性(Durability): 一旦事务提交,其结果应该是永久性的,即使系统崩溃,数据库也能够恢复到事务提交的状态。
3. 脏读、幻读、不可重复读
-
脏读(Dirty Read): 一个事务读取了另一个事务未提交的数据,如果另一个事务回滚,则读到的数据是无效的。
-
幻读(Phantom Read): 一个事务读取了另一个事务已提交的数据,但由于另一个事务插入或删除了数据,导致读到的数据不一致。
-
不可重复读(Non-repeatable Read): 在一个事务中,多次读取相同的数据,但由于其他事务的更新导致数据发生变化,产生不一致的现象。
4. 乐观锁和悲观锁
-
悲观锁: 在事务开始前,通过数据库的锁机制锁定数据,使其他事务无法修改,事务结束后释放锁。悲观锁的代表是数据库中的行级锁和表级锁。
-
乐观锁: 假设并发冲突的概率很小,事务在读取数据时不加锁,而是在更新时检查数据是否被其他事务修改,如果被修改则回滚。乐观锁的代表是版本控制,通过版本号或时间戳进行控制。
5. 死锁及避免方法
-
死锁: 两个或多个事务互相等待对方释放锁,导致所有事务都无法继续执行。
-
避免死锁的方法:
- 加锁顺序: 事务按照相同的顺序获取锁,降低死锁的概率。
- 加锁时限: 设置事务的锁等待时限,超过时限则放弃锁。
- 死锁检测: 定期检测死锁并主动回滚某些事务,释放资源。
第十天
1. MySQL的多表查询(笛卡尔积原理)
MySQL的多表查询涉及到多个表的联合操作。如果没有明确指定连接条件,MySQL默认采用笛卡尔积(Cartesian Product)原理。笛卡尔积是指两个集合中的每个元素与另一个集合中的每个元素组合,得到的结果集的行数是两个表行数的乘积。
-- 例子:两个表的笛卡尔积
SELECT * FROM table1, table2;
上述查询将返回两个表的所有可能的组合,即笛卡尔积。
2. 数据库表的拆分规则,你们项目数据库怎么拆分的?
数据库表的拆分可以按照不同的规则进行,常见的拆分方式有:
-
垂直拆分: 将表按列进行拆分,将一张表的列分散到多个表中。
-
水平拆分: 将表按行进行拆分,将表中的数据按照某个条件划分到多个表中。
-
分区表: 按照某个规则将表划分为多个分区,提高查询性能。
具体的拆分规则取决于业务需求和性能优化的考虑。在项目中,拆分可能根据数据的访问频率、业务模块等因素进行规划,以提高系统的可维护性和性能。
3. 一条SQL语句在MySQL中如何执行的?
MySQL中一条SQL语句的执行过程可以简要概括为:
-
SQL解析器: 将SQL语句解析成解析树。
-
优化器: 对解析树进行优化,选择最优的执行计划。
-
执行引擎: 执行计划,获取数据,进行相应的操作。
-
返回结果: 将结果返回给客户端。
在执行计划中,MySQL会选择合适的索引、表连接方式等,以提高查询性能。可以使用EXPLAIN
关键字查看SQL的执行计划,帮助优化查询性能。
EXPLAIN SELECT * FROM your_table WHERE your_condition;
4. 什么是数据库连接池?为什么需要数据库连接池呢?
数据库连接池: 数据库连接池是一种管理数据库连接的技术,它通过维护一定数量的数据库连接,实现了数据库连接的复用和管理。连接池中的连接可以被多个线程共享,避免了频繁地创建和销毁数据库连接,提高了系统的性能和资源利用率。
为什么需要数据库连接池:
-
性能提升: 数据库连接的创建和关闭是比较耗时的操作,连接池可以重用连接,减少了这些开销。
-
资源管理: 连接池能够有效地管理数据库连接的数量,防止过多的连接导致系统资源不足。
-
连接复用: 连接池可以使得连接被复用,避免了频繁地创建新的连接,提高了系统的响应速度。
5. MySQL的内连接、左连接、右连接有什么区别?
在MySQL中,连接操作主要包括内连接(INNER JOIN)、左连接(LEFT JOIN或LEFT OUTER JOIN)、右连接(RIGHT JOIN或RIGHT OUTER JOIN)。
- 内连接(INNER JOIN): 只返回两个表中匹配的行。即,只返回两个表中连接条件满足的行。
SELECT * FROM table1 INNER JOIN table2 ON table1.id = table2.id;
- 左连接(LEFT JOIN): 返回左表的所有行,以及右表中与左表匹配的行。如果右表中没有匹配的行,则用NULL填充右表的列。
SELECT * FROM table1 LEFT JOIN table2 ON table1.id = table2.id;
- 右连接(RIGHT JOIN): 返回右表的所有行,以及左表中与右表匹配的行。如果左表中没有匹配的行,则用NULL填充左表的列。
SELECT * FROM table1 RIGHT JOIN table2 ON table1.id = table2.id;
这些连接操作用于将两个或多个表的数据合并,根据连接条件匹配相应的行。
第十一天
1. 进程和线程的区别
进程(Process):
- 进程是操作系统分配资源的基本单位,拥有独立的内存空间。
- 进程之间通信较为复杂,需要采用进程间通信(IPC)机制,如管道、消息队列、共享内存等。
- 进程的切换开销相对较大。
线程(Thread):
- 线程是进程的一部分,共享进程的内存空间。
- 线程之间通信相对简单,可以直接通过共享内存进行通信。
- 线程的切换开销相对较小。
2. 为什么要使用多线程?
使用多线程有以下主要优势:
-
并发执行: 多线程可以使程序中的多个部分同时执行,提高程序的并发性,加速程序的运行速度。
-
资源共享: 多线程可以共享相同进程的资源,如内存空间,避免了进程间的独立性带来的资源浪费。
-
简化编程模型: 多线程可以将复杂的任务拆分成多个较小的线程,简化编程模型,提高程序的可维护性。
3. 如何创建一个多线程?
在Java中,创建多线程有两种常见的方式:
- 继承Thread类:
java">class MyThread extends Thread {public void run() {// 线程执行的代码}
}// 创建线程对象
MyThread myThread = new MyThread();
// 启动线程
myThread.start();
- 实现Runnable接口:
java">class MyRunnable implements Runnable {public void run() {// 线程执行的代码}
}// 创建线程对象
Thread thread = new Thread(new MyRunnable());
// 启动线程
thread.start();
4. Runnable和Callable的区别
- Runnable: Runnable接口是Java中用于定义线程任务的接口,它只有一个
run
方法,不能返回结果或抛出受检异常。
java">class MyRunnable implements Runnable {public void run() {// 线程执行的代码}
}
- Callable: Callable接口是Java 5引入的,它允许线程返回执行结果,并且可以抛出受检异常。它是一个泛型接口,可以通过
Future
来获取线程的执行结果。
java">import java.util.concurrent.Callable;class MyCallable implements Callable<Integer> {public Integer call() throws Exception {// 线程执行的代码,可以返回结果return 42;}
}
5. 线程状态有哪些?
在Java中,线程有以下几种状态:
-
新建(NEW): 线程被创建但还未启动。
-
就绪(RUNNABLE): 线程已经在JVM中执行,但可能正在等待操作系统的资源。
-
阻塞(BLOCKED): 线程被阻塞,正在等待锁的释放。
-
等待(WAITING): 线程正在等待某个条件的发生,需要其他线程的通知唤醒。
-
超时等待(TIMED_WAITING): 类似于等待状态,但有一个超时时间。
-
终止(TERMINATED): 线程执行完成或因异常退出,处于终止状态。
第十二天
1. 如何让线程休眠?
在Java中,可以使用Thread.sleep()
方法来使线程休眠。Thread.sleep()
方法接受一个以毫秒为单位的时间参数,用于指定线程休眠的时长。
java">try {// 使当前线程休眠500毫秒Thread.sleep(500);
} catch (InterruptedException e) {e.printStackTrace();
}
2. 什么是死锁?如何避免死锁?
死锁: 死锁是指两个或多个线程在执行过程中因争夺资源而造成的一种互相等待的现象,导致所有参与的线程无法继续执行。
避免死锁的方法:
-
加锁顺序: 约定所有线程按照相同的顺序获取锁,降低死锁的概率。
-
加锁时限: 设置每个锁的获取时限,超过时限则放弃锁。
-
死锁检测: 定期检测死锁,主动回滚某些事务,释放资源。
-
使用Lock: 使用
java.util.concurrent.locks
包中的Lock
接口及其实现类,具有更灵活的加锁和释放锁的方式,以及更好的死锁避免机制。
3. 什么是线程池?优点是什么?
线程池: 线程池是一组预先创建的线程,可用于执行异步任务。线程池中的线程可以被重复使用,减少了线程的创建和销毁开销,提高了系统性能。
线程池的优点:
-
提高性能: 减少了线程的创建和销毁开销,重复利用线程,提高了系统性能。
-
统一管理: 可以统一管理线程的数量、状态、执行过程,方便监控和调优。
-
避免资源耗尽: 控制并发线程的数量,避免因创建过多线程导致系统资源耗尽的问题。
-
提高响应速度: 可以更及时地响应任务的到来,减少任务等待的时间。
4. 如何获得线程返回值?
在Java中,线程的run
方法是void
类型的,不能直接返回值。但是可以通过其他方式获取线程执行的结果:
- 使用共享变量: 在线程中定义共享变量,通过设置和获取变量值来传递结果。
java">class MyThread extends Thread {private int result;public void run() {// 执行任务,设置result的值result = someOperation();}public int getResult() {return result;}
}
- 使用Callable和Future: 可以通过
Callable
接口的call
方法返回结果,并通过Future
获取线程执行的结果。
java">import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;class MyCallable implements Callable<Integer> {public Integer call() throws Exception {// 执行任务,返回结果return someOperation();}
}// 创建Callable任务
Callable<Integer> callable = new MyCallable();
// 将Callable任务封装成FutureTask
FutureTask<Integer> futureTask = new FutureTask<>(callable);
// 创建线程并启动
Thread thread = new Thread(futureTask);
thread.start();
// 获取线程执行的结果
int result = futureTask.get();
5. String 为什么是常量,buffer 和 builder 的区别
String 是常量: 在Java中,String是不可变类,一旦创建就不能被修改。这是因为String对象在创建后被放入字符串池中,任何对String的操作都会创建一个新的String对象。这样的设计使得String在多线程环境中更为安全。
StringBuffer 和 StringBuilder 的区别:
- StringBuffer: 线程安全的可变字符序列,适用于多线程环境。所有对于字符串的修改操作都是同步的,即每次只能有一个线程操作。
java">StringBuffer buffer = new StringBuffer();
buffer.append("Hello");
buffer.append(" ");
buffer.append("World");
String result = buffer.toString();
- StringBuilder: 线程不安全的可变字符序列,适用于单线程环境。相比StringBuffer,StringBuilder的性能更好,因为不需要进行同步操作。
java">StringBuilder builder = new StringBuilder();
builder.append("Hello");
builder.append(" ");
builder.append("World");
String result = builder.toString();
总的来说,如果在单线程环境下,建议使用StringBuilder,而在多线程环境下,使用StringBuffer。
第十三天
1. Controller 是单例的吗?是单线程的吗?线程是否安全,如何保证线程安全?
在Java的Spring框架中,通常情况下,Controller 是单例的。这意味着在应用程序的整个生命周期内,只会创建一个 Controller 实例,以提高性能和资源利用率。
虽然 Controller 是单例的,但它并不是单线程的。在Web应用中,每个请求通常都会由一个独立的线程来处理,因此多个线程可能同时访问同一个 Controller 实例。因此,需要确保 Controller 的线程安全性。
线程安全可以通过以下方式来保证:
-
不使用实例变量: 避免在 Controller 中使用实例变量,或者确保实例变量的访问是线程安全的。
-
使用局部变量: 尽量使用局部变量而不是实例变量,因为局部变量在方法内部是线程安全的。
-
使用 synchronized 关键字: 在需要保护共享资源的情况下,可以使用 synchronized 关键字确保方法或代码块的互斥访问。
2. 程序开多少线程合适?
程序开启多少线程合适取决于应用的性质和硬件条件。开启太多线程可能导致系统资源耗尽,影响性能,而过少的线程可能无法充分利用多核处理器的优势。
一些考虑因素包括:
-
CPU核心数: 通常情况下,线程数不应该超过 CPU 的核心数。过多的线程可能导致上下文切换增多,性能下降。
-
任务类型: 如果任务是I/O密集型,可以使用更多的线程,因为这样可以充分利用等待 I/O 操作的时间。对于计算密集型任务,线程数可能需要适度减少。
-
内存消耗: 每个线程都会占用一定的内存,过多的线程可能导致内存不足。
-
任务分解: 如果任务可以分解成独立的子任务,可以考虑使用线程池来管理线程数量。
3. 描述一下 synchronized 和 Lock 区别
synchronized:
- Java 中的关键字,用于实现线程的同步。
- 隐式锁,通过对象的内置监视器(或称为锁)来实现同步。
- 不能中断一个在等待获取锁的线程。
- 程序员不需要手动释放锁,锁的释放由 JVM 自动管理。
java">synchronized (lock) {// 代码块
}
Lock:
- Java 中的接口,提供了比 synchronized 更灵活的锁定操作。
- 显示锁,需要程序员手动获取和释放锁。
- 可以中断一个在等待获取锁的线程。
- 可以尝试非阻塞地获取锁,或者在一定时间内等待锁。
java">Lock lock = new ReentrantLock();
try {lock.lock();// 代码块
} finally {lock.unlock();
}
区别:
-
可重入性: synchronized 是可重入的,同一个线程可以重复获取同一个锁。而 Lock 需要手动实现可重入性,例如 ReentrantLock 可以重复获取。
-
灵活性: Lock 提供了更灵活的锁定操作,如可以尝试非阻塞地获取锁、可中断地获取锁等。
-
等待可中断: Lock 可以中断等待获取锁的线程,而 synchronized 不支持中断。
4. wait 和 sleep 的区别
wait:
- 是 Object 类中的方法,用于线程间的通信。
- 必须在同步块或同步方法中调用,否则会抛出
IllegalMonitorStateException
。 - 释放持有的锁,进入等待状态,直到被其他线程唤醒或等待时间到。
java">synchronized (obj) {obj.wait();
}
sleep:
- 是 Thread 类的静态方法,用于让当前线程休眠。
- 不会释放持有的锁。
- 不依赖于同步。
java">Thread.sleep(1000);
区别:
-
释放锁: wait 会释放锁,而 sleep 不会释放锁。
-
使用范围: wait 必须在同步块或同步方法中使用,而 sleep 可以在任何地方使用。
-
唤醒方式: wait 需要被其他线程显式地唤醒,而 sleep 可以在指定的时间后自动唤醒。
5. 多线程之间是如何通信的?
多线程之间通信的方式有多种,常见的包括:
- 共享变量: 多个线程共享同一变量,通过对变量的读写实现通信。需要注意线程安全的问题。
java">// 共享变量
int sharedVariable;// 线程1
sharedVariable = 1;// 线程2
int value = sharedVariable;
- wait 和 notify/notifyAll: 使用
wait
和notify
或notifyAll
方法进行线程间的等待和唤醒操作,通常在同步块中使用。
java">// 线程1
synchronized (lock) {lock.wait();
}// 线程2
synchronized (lock) {lock.notify();
}
- 管道通信: 使用管道流进行线程间的通信,一个线程的输出流连接到另一个线程的输入流。
java">PipedInputStream
第十四天
1. 多线程应用场景有哪些?
多线程常用于以下应用场景:
-
提高程序性能: 并行处理可以加速任务的执行,提高程序的响应速度。
-
异步任务: 在需要等待外部资源的操作中,可以通过多线程使得程序在等待的同时继续执行其他任务。
-
并发访问: 多线程可以提供更好的并发访问能力,多个线程可以同时处理不同的请求。
-
定时任务: 可以通过多线程实现定时任务,定期执行特定的操作。
2. 项目当中哪些地方用到过线程?
线程在项目中可以用于各种目的,包括但不限于:
-
后台任务: 处理一些后台任务,如日志处理、定时任务等。
-
并发处理: 处理并发请求,提高系统的并发处理能力。
-
异步处理: 在需要等待的操作中,如异步调用其他服务、异步通知等。
-
定时任务: 执行定时的数据备份、清理等操作。
3. 项目中的多线程怎么用,请具体说说(Spring 如何开启一个多线程)
在 Spring 中,可以通过使用 @Async
注解来实现多线程。具体步骤如下:
- 在配置类上添加
@EnableAsync
注解开启异步任务支持。
java">@Configuration
@EnableAsync
public class AsyncConfig {// 配置其他相关的异步任务参数
}
- 在需要异步执行的方法上添加
@Async
注解。
java">@Service
public class MyService {@Asyncpublic void asyncMethod() {// 异步执行的方法体}
}
- 在调用异步方法的地方,确保被调用的方法所在的类被 Spring 管理,以便 Spring 能够拦截异步方法的调用。
java">@Service
public class AnotherService {@Autowiredprivate MyService myService;public void callAsyncMethod() {myService.asyncMethod();}
}
4. 什么是 IO 流?
I/O(Input/Output)指的是程序与外部世界(如文件、网络、数据库等)进行数据交换的过程。I/O 流就是用于处理这种数据流的机制,用于读取和写入数据。
在 Java 中,I/O 流分为输入流和输出流,同时又根据数据传输单位的不同分为字节流和字符流。
5. 流按照传输的单位怎么分类?分成哪两种流,并且它们的父类叫什么?说一下常用的 IO 流?
流按照传输的单位可以分为两种:
-
字节流(Byte Stream): 以字节为单位进行数据传输,适用于二进制数据的输入输出。
-
InputStream
和OutputStream
是字节流的父类。 -
常用的字节流包括
FileInputStream
、FileOutputStream
、BufferedInputStream
、BufferedOutputStream
等。
-
-
字符流(Character Stream): 以字符为单位进行数据传输,适用于文本数据的输入输出。
-
Reader
和Writer
是字符流的父类。 -
常用的字符流包括
FileReader
、FileWriter
、BufferedReader
、BufferedWriter
等。
-
这两种流的区别在于处理的数据类型不同,字节流适用于二进制数据,而字符流适用于文本数据。在实际应用中,根据处理的数据类型选择合适的流来进行操作。
第十五天
1. 为什么要使用 RabbitMQ?
RabbitMQ是一个开源的消息代理软件,用于支持分布式系统中的消息传递。使用RabbitMQ有以下优点:
-
消息队列: RabbitMQ实现了高效的消息队列,支持发布/订阅模式,点对点模式等。
-
解耦: RabbitMQ能够实现解耦,发送者和接收者之间不直接通信,降低了系统的耦合度。
-
异步通信: RabbitMQ支持异步通信,提高了系统的并发性和性能。
-
可靠性: RabbitMQ提供消息持久化、高可用性等特性,确保消息的可靠性。
-
消息确认机制: RabbitMQ支持消息的确认机制,可以确保消息被成功处理。
2. 使用 RabbitMQ 的场景?
RabbitMQ适用于许多场景,包括但不限于:
-
微服务架构: 用于微服务之间的通信,实现解耦和异步通信。
-
任务队列: 用于异步任务处理,提高系统的响应速度。
-
日志处理: 用于日志的收集和处理,支持日志的异步处理。
-
消息通知: 用于事件驱动架构,实现各个组件之间的消息通知。
-
分布式系统: 用于分布式系统中的消息传递,实现系统之间的数据交换。
3. Kafka、ActiveMQ、RabbitMQ、RocketMQ 有什么优缺点?
Kafka:
- 优点:高吞吐量、持久性、水平扩展性强。
- 缺点:不适用于小规模的数据传输,配置较为复杂。
ActiveMQ:
- 优点:易于使用,支持多种协议和消息模型。
- 缺点:性能较低,不适用于高吞吐量的场景。
RabbitMQ:
- 优点:稳定、高可靠性、支持多种消息模型,易于使用。
- 缺点:相对于Kafka,吞吐量较低。
RocketMQ:
- 优点:高吞吐量、可靠性强,支持分布式事务。
- 缺点:社区相对较小,生态系统相对不如Kafka和RabbitMQ丰富。
4. 如何确保消息正确地发送至 RabbitMQ? 如何确保消息接收方消费了消息?
确保消息正确地发送至 RabbitMQ 可以通过以下方式:
-
消息确认机制: 发送者发送消息后,等待 RabbitMQ 的确认消息,确保消息已经被正确接收。
-
持久化: 将消息标记为持久化,确保即使 RabbitMQ 重启,消息也不会丢失。
确保消息接收方消费了消息:
-
消息确认机制: 消费者收到消息后,向 RabbitMQ 发送确认消息,告诉它消息已经被正确消费。
-
幂等性处理: 确保消费端的处理是幂等的,即多次处理相同的消息不会产生不同的结果。
5. 如何避免消息重复投递或重复消费?
避免消息重复投递或重复消费可以采取以下措施:
- 消息去重: 在消息发送和消费端,使用唯一标识来标记消息,避免重复处理相同的消息。
- 消息幂等性: 确保消费端的处理是幂等的,即多次处理相同的消息不会产生不同的结果。
- 消息过期时间: 设置消息的过期时间,确保消息在一定时间内被处理,过期后不再重复处理。
- 幂等消费端: 在消费端设计时考虑幂等性,使得多次消费相同的消息不会导致问题。
- 消息状态记录: 记录消息的处理状态,避免重复处理已经成功处理过的消息。
第十六天
1. 消息基于什么传输?
消息传输可以基于不同的协议和中间件,常见的消息传输方式有:
-
Message Queues: 通过消息队列实现消息的异步传输,例如 RabbitMQ、Kafka、ActiveMQ 等。
-
HTTP/HTTPS: 通过 HTTP 或 HTTPS 协议进行消息传输,例如 RESTful API。
-
WebSocket: 提供双向通信的协议,可用于实时消息传输。
-
AMQP(Advanced Message Queuing Protocol): 一种面向消息的网络协议,RabbitMQ 使用的就是 AMQP。
2. 如何保证 RabbitMQ 消息的可靠传输?
要确保 RabbitMQ 消息的可靠传输,可以采取以下步骤:
-
消息持久化: 将消息标记为持久化,确保即使 RabbitMQ 重启,消息也不会丢失。
-
生产者确认模式: 在消息生产者中开启生产者确认模式,确保消息被正确发送到 RabbitMQ。
-
消费者确认模式: 在消息消费者中开启消费者确认模式,确保消息被正确消费。
-
消息重试机制: 设置消息的重试次数和重试间隔,确保在消息处理失败时能够进行重试。
-
备份队列: 设置备份队列,确保消息在主队列无法正常处理时能够进入备份队列。
3. 持久化怎么做?
RabbitMQ 中的消息持久化可以通过以下方式实现:
- 队列持久化: 在声明队列时,通过
durable
参数设置队列为持久化队列。
java">channel.queueDeclare("myQueue", true, false, false, null);
- 消息持久化: 在发布消息时,通过
deliveryMode
参数设置消息为持久化消息。
java">channel.basicPublish("", "myQueue", MessageProperties.PERSISTENT_TEXT_PLAIN, "Hello, RabbitMQ!".getBytes());
通过以上设置,即使 RabbitMQ 服务重启,持久化的队列和消息也会被恢复。
4. 说说你项目中那个功能或模块使用了 MQ,如何做的,使用那种模式?
在项目中,可能使用消息队列来处理异步任务、日志记录、事件驱动等场景。具体使用的模式取决于业务需求。
例如,可以使用发布/订阅模式,将生产者产生的消息广播给多个消费者,实现一对多的消息通知。也可以使用点对点模式,一个生产者将消息发送给一个具体的消费者,实现点对点的消息传递。
在 Spring 中,可以通过 RabbitTemplate
和 @RabbitListener
注解来方便地实现对 RabbitMQ 的集成。
5. 怎么避免 RabbitMQ 发送消息时宕机的情况?
要避免 RabbitMQ 发送消息时宕机的情况,可以考虑以下措施:
-
消息确认机制: 使用生产者确认模式,确保消息被正确发送到 RabbitMQ。
-
消息重试机制: 在发送消息时,设置消息的重试次数和重试间隔,确保在发送失败时能够进行重试。
-
备份队列: 设置备份队列,确保消息在主队列无法正常处理时能够进入备份队列。
-
事务模式: 使用事务模式,确保在消息发送失败时进行回滚,避免消息被丢失。
-
监控和报警: 设置监控和报警机制,及时发现 RabbitMQ 的异常状态并进行处理。
ues:** 通过消息队列实现消息的异步传输,例如 RabbitMQ、Kafka、ActiveMQ 等。
-
HTTP/HTTPS: 通过 HTTP 或 HTTPS 协议进行消息传输,例如 RESTful API。
-
WebSocket: 提供双向通信的协议,可用于实时消息传输。
-
AMQP(Advanced Message Queuing Protocol): 一种面向消息的网络协议,RabbitMQ 使用的就是 AMQP。
2. 如何保证 RabbitMQ 消息的可靠传输?
要确保 RabbitMQ 消息的可靠传输,可以采取以下步骤:
-
消息持久化: 将消息标记为持久化,确保即使 RabbitMQ 重启,消息也不会丢失。
-
生产者确认模式: 在消息生产者中开启生产者确认模式,确保消息被正确发送到 RabbitMQ。
-
消费者确认模式: 在消息消费者中开启消费者确认模式,确保消息被正确消费。
-
消息重试机制: 设置消息的重试次数和重试间隔,确保在消息处理失败时能够进行重试。
-
备份队列: 设置备份队列,确保消息在主队列无法正常处理时能够进入备份队列。
3. 持久化怎么做?
RabbitMQ 中的消息持久化可以通过以下方式实现:
- 队列持久化: 在声明队列时,通过
durable
参数设置队列为持久化队列。
java">channel.queueDeclare("myQueue", true, false, false, null);
- 消息持久化: 在发布消息时,通过
deliveryMode
参数设置消息为持久化消息。
java">channel.basicPublish("", "myQueue", MessageProperties.PERSISTENT_TEXT_PLAIN, "Hello, RabbitMQ!".getBytes());
通过以上设置,即使 RabbitMQ 服务重启,持久化的队列和消息也会被恢复。
4. 说说你项目中那个功能或模块使用了 MQ,如何做的,使用那种模式?
在项目中,可能使用消息队列来处理异步任务、日志记录、事件驱动等场景。具体使用的模式取决于业务需求。
例如,可以使用发布/订阅模式,将生产者产生的消息广播给多个消费者,实现一对多的消息通知。也可以使用点对点模式,一个生产者将消息发送给一个具体的消费者,实现点对点的消息传递。
在 Spring 中,可以通过 RabbitTemplate
和 @RabbitListener
注解来方便地实现对 RabbitMQ 的集成。
5. 怎么避免 RabbitMQ 发送消息时宕机的情况?
要避免 RabbitMQ 发送消息时宕机的情况,可以考虑以下措施:
-
消息确认机制: 使用生产者确认模式,确保消息被正确发送到 RabbitMQ。
-
消息重试机制: 在发送消息时,设置消息的重试次数和重试间隔,确保在发送失败时能够进行重试。
-
备份队列: 设置备份队列,确保消息在主队列无法正常处理时能够进入备份队列。
-
事务模式: 使用事务模式,确保在消息发送失败时进行回滚,避免消息被丢失。
-
监控和报警: 设置监控和报警机制,及时发现 RabbitMQ 的异常状态并进行处理。