面试题答案
一键面试架构设计
- 读写分离架构
- 在MySQL方面,采用主从复制的读写分离架构。主库负责写操作,从库负责读操作。这样可以分散读压力,减少主库的负载,避免因大量读操作影响写操作从而间接影响Redis与MySQL的数据同步。
- 在Redis方面,可根据业务需求采用集群模式(如Redis Cluster),通过数据分片将数据分散到多个节点,提高读写性能。
- 异步处理架构 引入消息队列(如Kafka、RabbitMQ),将数据变更操作(如MySQL写操作后的数据同步到Redis)通过消息队列异步处理。这样可以避免直接同步造成的阻塞,提高系统的响应速度。例如,当MySQL数据发生变化时,先将变更消息发送到消息队列,然后由专门的消费者从队列中取出消息进行Redis数据的同步。
- 缓存预热与分级缓存
- 缓存预热:在系统启动时,将热点数据提前加载到Redis中,减少首次请求时的缓存穿透和回源到MySQL的压力。
- 分级缓存:设计多级缓存,如在应用层设置本地缓存(如Guava Cache),减少对Redis的直接访问。对于读多写少且允许一定时间内数据不一致的业务场景,可优先从本地缓存读取数据。
技术选型
- 数据库
- MySQL:选择高性能版本,如MySQL 8.0及以上,其在性能优化、锁机制等方面有较好的表现。同时,合理配置MySQL参数,如innodb_buffer_pool_size、innodb_log_file_size等,以提高数据库的读写性能。
- Redis:选择适合高并发场景的版本,如Redis 6.0及以上,其支持多线程I/O,可显著提高读写性能。根据业务需求选择合适的部署模式,如单机模式、主从模式、Cluster模式等。
- 消息队列
- Kafka:适合大数据量、高吞吐量的场景,具有高容错性和可扩展性。如果业务对数据一致性要求不是极高,Kafka可以快速处理大量的消息,实现数据的异步同步。
- RabbitMQ:可靠性较高,适合对数据一致性和可靠性要求严格的场景。其支持多种消息协议和灵活的路由机制,可满足复杂业务逻辑下的数据同步需求。
- 缓存
- Guava Cache:作为应用层的本地缓存,具有简单易用、高效的特点。它适合存储一些访问频率高、生命周期短的数据,减轻Redis的压力。
代码实现
- MySQL写操作与消息队列集成 以Java为例,使用Spring Boot和MyBatis进行数据库操作,结合Kafka发送消息。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Insert;
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private KafkaTemplate<String, String> kafkaTemplate;
public void saveUser(User user) {
userMapper.saveUser(user);
// 发送数据变更消息到Kafka
kafkaTemplate.send("user - change - topic", user.toString());
}
}
@Mapper
public interface UserMapper {
@Insert("INSERT INTO users (name, age) VALUES (#{name}, #{age})")
void saveUser(User user);
}
- Kafka消费者与Redis同步
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;
@Component
public class UserKafkaConsumer {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@KafkaListener(topics = "user - change - topic", groupId = "user - sync - group")
public void handleUserChange(ConsumerRecord<String, String> record) {
String userData = record.value();
// 解析userData并更新Redis
// 例如,假设userData格式为"name:age"
String[] parts = userData.split(":");
String key = "user:" + parts[0];
redisTemplate.opsForValue().set(key, parts[1]);
}
}
- 缓存预热与分级缓存 缓存预热:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
@Component
public class CachePreloader {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@PostConstruct
public void preloadCache() {
// 假设从数据库查询热点数据
// 这里简单示例,实际应从数据库查询
String hotData = "default - hot - data";
redisTemplate.opsForValue().set("hot - key", hotData);
}
}
分级缓存:
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class UserServiceWithCache {
private final Cache<String, String> localCache = CacheBuilder.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(1000)
.build();
@Autowired
private RedisTemplate<String, String> redisTemplate;
public String getUser(String key) {
String value = localCache.getIfPresent(key);
if (value == null) {
value = (String) redisTemplate.opsForValue().get(key);
if (value != null) {
localCache.put(key, value);
}
}
return value;
}
}