面试题答案
一键面试Redis分布式锁设计
- 锁的获取:
- 使用
SETNX
(SET if Not eXists)命令获取锁。例如,SETNX lock_key unique_value
,unique_value
通常使用UUID生成,用于标识锁的持有者,以便在释放锁时验证。这样可以确保在高并发下只有一个客户端能获取到锁。 - 为锁设置合理的过期时间,防止死锁。可以使用
SET key value EX seconds NX
的形式,在获取锁的同时设置过期时间。
- 使用
- 锁的续期:
- 在业务执行时间较长时,为防止锁提前过期,采用
Redisson
等框架提供的锁续期机制。它会在锁过期时间的一半时,开启一个后台线程自动续期。
- 在业务执行时间较长时,为防止锁提前过期,采用
- 锁的释放:
- 释放锁时,要验证当前客户端是锁的持有者。可以通过Lua脚本来实现,例如:
if redis.call("GET",KEYS[1]) == ARGV[1] then
return redis.call("DEL",KEYS[1])
else
return 0
end
这里KEYS[1]
是锁的键,ARGV[1]
是获取锁时设置的unique_value
。
MySQL事务管理
- 事务隔离级别:
- 选择合适的事务隔离级别,对于电商场景,推荐使用
可重复读(Repeatable Read)
级别。它可以避免幻读,在高并发下保证数据一致性。例如,在Java中通过JDBC设置:
- 选择合适的事务隔离级别,对于电商场景,推荐使用
Connection conn = DriverManager.getConnection(url, username, password);
conn.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
- 事务边界控制:
- 在获取Redis分布式锁后,开始MySQL事务。在库存扣减、订单生成、用户积分计算等操作全部完成后,提交事务。如果其中任何一步出现异常,回滚事务。例如:
try {
Connection conn = DriverManager.getConnection(url, username, password);
conn.setAutoCommit(false);
// 库存扣减操作
PreparedStatement stockStmt = conn.prepareStatement("UPDATE stock_table SET stock = stock -? WHERE product_id =? AND stock >=?");
stockStmt.setInt(1, quantity);
stockStmt.setInt(2, productId);
stockStmt.setInt(3, quantity);
stockStmt.executeUpdate();
// 订单生成操作
PreparedStatement orderStmt = conn.prepareStatement("INSERT INTO order_table (order_id, user_id, product_id, quantity) VALUES (?,?,?,?)");
orderStmt.setString(1, orderId);
orderStmt.setInt(2, userId);
orderStmt.setInt(3, productId);
orderStmt.setInt(4, quantity);
orderStmt.executeUpdate();
// 用户积分计算操作
PreparedStatement pointStmt = conn.prepareStatement("UPDATE user_table SET points = points +? WHERE user_id =?");
pointStmt.setInt(1, points);
pointStmt.setInt(2, userId);
pointStmt.executeUpdate();
conn.commit();
} catch (SQLException e) {
if (conn!= null) {
try {
conn.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
e.printStackTrace();
}
性能调优策略
- Redis性能优化:
- 连接池:使用连接池管理Redis连接,减少连接创建和销毁的开销。例如在Java中使用
JedisPool
。
- 连接池:使用连接池管理Redis连接,减少连接创建和销毁的开销。例如在Java中使用
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(100);
JedisPool jedisPool = new JedisPool(config, "redis_host", 6379);
- **批量操作**:对于多个Redis操作,尽量合并为一个批量操作。例如,使用`MSET`代替多个`SET`操作。
2. MySQL性能优化:
- 索引优化:为库存表、订单表、用户表等相关表的常用查询字段创建索引。例如,在库存表的product_id
字段上创建索引:
CREATE INDEX idx_product_id ON stock_table (product_id);
- **分库分表**:随着数据量增长,对数据库进行分库分表。可以按照用户ID、订单时间等维度进行分片,减轻单库单表压力。
- **SQL优化**:对涉及库存扣减、订单生成、用户积分计算的SQL语句进行优化,避免全表扫描,使用执行计划分析工具(如`EXPLAIN`)查看SQL执行情况并调整。
3. 整体架构优化: - 异步处理:对于一些非关键的操作,如用户积分计算,可以采用异步方式处理,减少主业务流程的等待时间。可以使用消息队列(如Kafka、RabbitMQ),在订单生成成功后发送消息,由专门的消费者进行积分计算。 - 缓存优化:除了使用Redis作为分布式锁,还可以利用它缓存一些常用数据,如商品信息、用户基本信息等,减少对MySQL的查询压力。