【架构设计(二)】高可用、高并发的 Java 架构设计
在互联网业务爆发式增长的今天,高可用和高并发已成为 Java 系统架构设计的核心目标。本文将围绕负载均衡与高可用架构、缓存设计与优化、数据库读写分离与分库分表三大关键领域,深入剖析其原理,并结合完整的代码示例,帮助开发者构建稳定高效的系统架构。
一、负载均衡与高可用架构
1.1 负载均衡实现方案
负载均衡是高并发系统的基础,它通过将请求均匀分配到多个服务节点,避免单点过载。
1.1.1 Nginx 负载均衡配置示例
Nginx 作为高性能反向代理服务器,常用于 HTTP 层负载均衡。在nginx.conf的核心配置中,upstream模块定义了服务集群,采用least_conn(最少连接数)策略,将请求优先分配给连接数最少的节点。同时配置了健康检查health_check,每 3 秒检查一次节点状态,3 次失败则剔除节点,2 次成功重新启用,确保流量始终导向健康服务。
# nginx.conf 核心配置
upstream java_service {
# 轮询策略
least_conn;
# 服务节点配置
server 192.168.1.101:8080 weight=10;
server 192.168.1.102:8080 weight=10;
server 192.168.1.103:8080 weight=10;
# 健康检查配置
health_check interval=3000 fail=3 pass=2;
}
server {
listen 80;
server_name java_service.com;
location / {
proxy_pass http://java_service;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
总结:Nginx 适用于七层负载均衡,通过简单配置即可实现流量分发与健康管理,是 Web 服务负载均衡的常用方案。
1.1.2 Spring Cloud Ribbon 负载均衡代码
在微服务架构中,Ribbon 提供客户端负载均衡能力。通过LoadBalancerClient接口,服务消费者可从注册中心获取服务实例,并根据配置策略选择节点。示例中配置了RandomRule随机策略,也可切换为RoundRobinRule轮询或WeightedResponseTimeRule权重策略。
Maven依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
配置类
@Configuration
public class RibbonConfig {
// 自定义负载均衡策略
@Bean
public IRule ribbonRule() {
// 随机策略
return new RandomRule();
// 轮询策略:return new RoundRobinRule();
// 权重策略:return new WeightedResponseTimeRule();
}
}
服务调用示例
@Service
public class UserService {
@Autowired
private LoadBalancerClient loadBalancer;
@Autowired
private RestTemplate restTemplate;
public User getUserById(Long id) {
// 从注册中心获取服务实例
ServiceInstance instance = loadBalancer.choose("user-service");
String url = "http://" + instance.getHost() + ":" + instance.getPort() + "/users/" + id;
return restTemplate.getForObject(url, User.class);
}
}
启动类配置
@SpringBootApplication
@EnableDiscoveryClient
public class Application {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
总结:Ribbon 实现了客户端负载均衡,与 Spring Cloud 生态无缝集成,适合微服务内部的服务调用场景。
1.2 高可用架构设计
高可用架构旨在确保系统在部分组件故障时仍能持续提供服务。
1.2.1 服务注册与发现实现(Nacos)
Nacos 作为服务注册中心,服务提供者通过配置spring.cloud.nacos.discovery将自身注册到 Nacos Server,消费者则通过DiscoveryClient从注册中心获取服务实例列表。示例中,order-service在启动时注册自身,调用user-service时先从 Nacos 获取实例,再进行远程调用。
Maven依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
配置文件 application.yml
spring:
application:
name: order-service
cloud:
nacos:
discovery:
server-addr: nacos-server:8848
# 健康检查配置
health-check-enabled: true
# 服务权重
weight: 10
服务提供者示例
@RestController
@RequestMapping("/orders")
public class OrderController {
@GetMapping("/{id}")
public Order getOrderById(@PathVariable Long id) {
// 业务逻辑
return orderService.getOrderById(id);
}
}
服务消费者通过Nacos发现服务
@Service
public class OrderService {
@Autowired
private DiscoveryClient discoveryClient;
public Order getOrderDetail(Long orderId) {
// 获取用户服务实例
List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
if (instances.isEmpty()) {
throw new ServiceNotFoundException("用户服务不可用");
}
// 随机选择一个实例
ServiceInstance instance = instances.get(new Random().nextInt(instances.size()));
// 调用用户服务
String url = "http://" + instance.getHost() + ":" + instance.getPort() + "/users/" + order.getUserId();
User user = restTemplate.getForObject(url, User.class);
// 组装订单详情
return buildOrderDetail(order, user);
}
}
总结:Nacos 简化了服务注册与发现流程,提供健康检查、权重配置等功能,是微服务架构中保障服务高可用的关键组件。
1.2.2 断路器实现(Sentinel)
Sentinel通过熔断、限流机制保护系统。示例中,OrderService的getOrderDetail方法添加了@SentinelResource注解,指定了fallback熔断降级方法和blockHandler限流阻断方法。当服务调用失败或触发限流规则时,自动执行相应逻辑,避免雪崩效应。
Maven依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
配置文件 application.yml
spring:
cloud:
sentinel:
transport:
# Sentinel控制台地址
dashboard: sentinel-dashboard:8080
datasource:
# 限流规则配置(从Nacos获取)
flow-rule:
nacos:
server-addr: nacos-server:8848
dataId: ${spring.application.name}-flow-rules
groupId: SENTINEL_GROUP
data-type: json
服务熔断示例
@Service
public class OrderService {
)
public OrderDetail getOrderDetail(Long orderId) {
// 正常业务逻辑
Order order = orderDao.getOrderById(orderId);
User user = userService.getUserById(order.getUserId());
return new OrderDetail(order, user);
}
// 熔断降级方法
public OrderDetail getOrderDetailFallback(Long orderId, Throwable ex) {
log.error("获取订单详情失败,订单ID:{}", orderId, ex);
// 返回兜底数据
return new OrderDetail();
}
// 限流阻断方法
public OrderDetail getOrderDetailBlockHandler(Long orderId, BlockException ex) {
log.warn("获取订单详情被限流,订单ID:{}", orderId);
// 返回限流提示
throw new ServiceBlockedException("服务繁忙,请稍后再试");
}
}
总结:Sentinel 提供可视化控制台和灵活的规则配置,有效提升系统的容错能力和稳定性。
二、缓存设计与优化
2.1 缓存架构设计
缓存通过存储热点数据减少数据库压力,提升系统响应速度。
2.1.1 多级缓存架构实现
多级缓存结合了本地缓存(Caffeine)的快速访问和分布式缓存(Redis)的共享能力。MultiLevelCacheImpl类实现了三级查询逻辑:优先从本地 Caffeine 缓存获取,未命中则查询 Redis,最后从数据库加载并回写缓存。
多级缓存接口定义
public interface MultiLevelCache {
// 从一级缓存(本地)获取
<T> T getFromFirstLevel(String key, Class<T> type);
// 从二级缓存(分布式)获取
<T> T getFromSecondLevel(String key, Class<T> type);
// 从数据库获取并填充缓存
<T> T getFromDBAndCache(String key, Class<T> type, Function<String, T> dbLoader);
// 清除缓存
void clear(String key);
}
多级缓存实现类
@Service
public class MultiLevelCacheImpl implements MultiLevelCache {
// 再查二级缓存
result = getFromSecondLevel(key, type);
if (result != null) {
// 填充一级缓存
firstLevelCache.put(key, result);
return result;
}
// 查数据库
result = dbLoader.apply(key);
if (result != null) {
// 填充二级缓存
secondLevelCache.put(key, result);
// 填充一级缓存
firstLevelCache.put(key, result);
}
return result;
}
@Override
public void clear(String key) {
firstLevelCache.clear(key);
secondLevelCache.clear(key);
}
}
本地缓存实现(Caffeine)
@Service
public class CaffeineCache {
private final com.github.benmanes.caffeine.cache.Cache<String, Object> cache;
public CaffeineCache() {
this.cache = Caffeine.newBuilder()
.maximumSize(10000) // 最大缓存条目数
.expireAfterWrite(10, TimeUnit.MINUTES) // 写入后过期时间
.weakValues() // 使用弱引用值
.build();
}
public <T> T get(String key, Class<T> type) {
return type.cast(cache.getIfPresent(key));
}
public void put(String key, Object value) {
cache.put(key, value);
}
public void clear(String key) {
cache.invalidate(key);
}
}
分布式缓存实现(Redis)
@Service
public class RedisCache {
return null;
}
try {
return (T) JSON.parseObject(value, type);
} catch (Exception e) {
log.error("Redis缓存反序列化失败,key:{}", key, e);
return null;
}
}
public void put(String key, Object value) {
try {
String jsonValue = JSON.toJSONString(value);
// 缓存10分钟
stringRedisTemplate.opsForValue().set(key, jsonValue, 10, TimeUnit.MINUTES);
} catch (Exception e) {
log.error("Redis缓存序列化失败,key:{}", key, e);
}
}
public void clear(String key) {
stringRedisTemplate.delete(key);
}
}
总结:多级缓存架构平衡了性能与一致性,适用于读多写少的场景,有效降低数据库负载。
2.2 缓存核心策略实现
2.2.1 缓存穿透解决方案(布隆过滤器)
缓存穿透指恶意请求频繁查询不存在的数据,导致请求直达数据库。布隆过滤器通过概率性数据结构快速判断数据是否存在,示例中初始化时加载所有存在的用户 ID,后续请求先经布隆过滤器预检,避免无效数据库查询。
Maven依赖
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
</dependency>
布隆过滤器实现
@Service
public class BloomFilterService {
log.info("开始初始化布隆过滤器...");
// 从数据库获取所有存在的key
List<String> existKeys = userDao.getAllExistUserIds();
for (String key : existKeys) {
bloomFilter.put(key);
}
log.info("布隆过滤器初始化完成,共加载 {} 个键", existKeys.size());
}
// 判断key是否存在
public boolean mightContain(String key) {
return bloomFilter.mightContain(key);
}
// 添加key到布隆过滤器
public void put(String key) {
bloomFilter.put(key);
}
}
应用示例
@Service
public class UserService {
private UserDao userDao;
public User getUserById(String userId) {
// 布隆过滤器预检,防止缓存穿透
if (!bloomFilterService.mightContain(userId)) {
log.warn("检测到缓存穿透攻击,userId:{}", userId);
throw new IllegalArgumentException("用户不存在");
}
// 多级缓存获取
return multiLevelCache.getFromDBAndCache(
"user:" + userId,
User.class,
id -> userDao.getUserById(id)
);
}
// 新增用户时更新布隆过滤器
public void addUser(User user) {
userDao.addUser(user);
bloomFilterService.put(user.getUserId());
// 清除缓存
multiLevelCache.clear("user:" + user.getUserId());
}
}
总结:布隆过滤器以极小的空间代价大幅提升缓存防护能力,但存在误判可能,需结合业务容忍度使用。
2.2.2 缓存雪崩解决方案(Redisson 分布式锁)
缓存雪崩指大量缓存同时失效导致数据库瞬间压力激增。Redisson 分布式锁确保同一时间只有一个线程重建缓存,示例中lockAndExecuteWithRetry方法通过重试机制获取锁,避免线程饥饿。当线程获取锁失败时,会等待一段时间后再次尝试,最多重试指定次数,有效防止因锁竞争导致的请求堆积。
Maven依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.16.5</version>
</dependency>
分布式锁工具类
@Service
public class DistributedLockService {
@Autowired
private RedissonClient redissonClient;
// 加锁方法
public <T> T lockAndExecute(String lockKey, long timeout, TimeUnit unit,
Callable<T> task) throws Exception {
RLock lock = redissonClient.getLock(lockKey);
try {
// 尝试加锁,timeout为等待锁的时间,unit为时间单位
boolean locked = lock.tryLock(500, timeout, unit);
if (locked) {
// 获得锁后执行任务
return task.call();
} else {
// 未获得锁,直接从缓存获取
log.warn("获取锁失败,直接返回缓存数据,lockKey:{}", lockKey);
// 这里可以添加从缓存获取数据的逻辑
return null;
}
} finally {
// 确保释放锁
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
// 带重试的加锁方法
public <T> T lockAndExecuteWithRetry(String lockKey, long timeout, TimeUnit unit,
int retryTimes, long retryInterval, TimeUnit retryUnit,
Callable<T> task) throws Exception {
int attempts = 0;
while (attempts <= retryTimes) {
try {
return lockAndExecute(lockKey, timeout, unit, task);
} catch (Exception e) {
if (attempts >= retryTimes) {
log.error("获取锁重试次数已用完,lockKey:{}", lockKey, e);
throw e;
}
log.warn("获取锁失败,准备重试,attempts:{},lockKey:{}", attempts, lockKey, e);
retryUnit.sleep(retryInterval);
attempts++;
}
}
return null;
}
}
场景:
在电商大促场景下,商品详情页的缓存可能会在同一时间大量失效。此时,多个请求并发访问商品详情接口,如果没有分布式锁控制,这些请求都会直接查询数据库,导致数据库负载过高甚至崩溃。通过 Redisson 分布式锁,只有一个请求能获取到锁并重建缓存,其他请求要么获取缓存数据,要么等待重试,从而避免了缓存雪崩问题。
总结:
Redisson 分布式锁基于 Redis 实现,具有高可靠性和易用性,能有效应对缓存雪崩问题,保障系统在缓存失效时的稳定性。使用时需合理设置锁的超时时间、重试次数和重试间隔等参数,避免出现死锁、性能损耗以及因锁竞争导致的请求长时间等待等问题 。同时,它与 Spring 框架集成方便,适用于分布式环境下的多种并发控制场景,是解决缓存雪崩的重要技术手段之一。
三、数据库读写分离与分库分表
3.1 读写分离实现
读写分离是提升数据库性能的重要手段,通过将读操作和写操作分离到不同数据库,减少主库压力,提高系统吞吐量。
3.1.1 MyBatis Plus 多数据源配置
MyBatis Plus 结合dynamic-datasource-spring-boot-starter实现多数据源切换。在application.yml中配置多个数据源,包括主库master和从库slave1、slave2,通过dynamic.datasource.primary指定主数据源。通过自定义@DataSource注解和切面DataSourceAspect,在方法或类上标注注解即可实现数据源的动态切换。
数据源切换注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
String value() default "";
}
切面实现数据源切换
@Aspect
@Service
public class DataSourceAspect {
@Pointcut("@annotation(com.example.datasource.DataSource)")
public void dataSourcePointCut() {}
@Around("dataSourcePointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
// 获取注解上的数据源名称
DataSource dataSource = method.getAnnotation(DataSource.class);
if (dataSource != null) {
DynamicDataSourceContextHolder.setDataSourceType(dataSource.value());
} else {
// 类上获取数据源
Class<?> declaringClass = method.getDeclaringClass();
dataSource = declaringClass.getAnnotation(DataSource.class);
if (dataSource != null) {
DynamicDataSourceContextHolder.setDataSourceType(dataSource.value());
}
}
try {
return point.proceed();
} finally {
// 清除数据源
DynamicDataSourceContextHolder.clearDataSourceType();
}
}
}
数据源上下文Holder
public class DynamicDataSourceContextHolder {
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
public static void setDataSourceType(String dataSourceType) {
CONTEXT_HOLDER.set(dataSourceType);
}
public static String getDataSourceType() {
return CONTEXT_HOLDER.get();
}
public static void clearDataSourceType() {
CONTEXT_HOLDER.remove();
}
}
在DataSourceAspect切面类中,@Around环绕通知会在目标方法执行前后进行拦截。当检测到方法或类上有@DataSource注解时,会将对应的数据源名称设置到DynamicDataSourceContextHolder中,从而实现数据源的切换。在方法执行完毕后,清除当前线程绑定的数据源,避免影响其他方法的数据源选择。
总结:
MyBatis Plus 的多数据源配置灵活方便,能满足不同业务场景下读写分离需求,但需注意事务管理,避免跨数据源事务问题。在实际应用中,对于写操作频繁的业务方法,应确保使用主数据源;而读操作较多的查询方法,可选择从数据源以减轻主库压力。
3.2 分库分表实践
当数据量和访问量持续增长,单个数据库无法满足性能需求时,分库分表是有效的解决方案。
3.2.1 Sharding-JDBC 分库分表配置
Sharding-JDBC 是一款轻量级 Java 分库分表框架,无需额外中间件。在application.yml中配置数据源、分表规则和读写分离策略。actual-data-nodes定义了逻辑表order与实际数据库表的映射关系,table-strategy和database-strategy分别配置了表分片和库分片算法,示例中采用取模算法,根据order_id和user_id进行分片。
配置文件 application.yml
spring:
shardingsphere:
datasource:
names: master_db,slave_db_0,slave_db_1
master_db:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://master:3306/sharding_master?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: 123456
slave_db_0:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://slave0:3306/sharding_slave_0?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: 123456
slave_db_1:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://slave1:3306/sharding_slave_1?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: 123456
sharding:
tables:
order:
actual-data-nodes: master_db.order_${0..1},slave_db_${0..1}.order_${0..1}
table-strategy:
inline:
sharding-column: order_id
algorithm-expression: order_${order_id % 2}
database-strategy:
inline:
sharding-column: user_id
algorithm-expression: slave_db_${user_id % 2} if user_id is not null else master_db
key-generator:
column: order_id
type: SNOWFLAKE
binding-tables:
- order,order_item
default-database-strategy:
inline:
sharding-column: user_id
algorithm-expression: master_db
default-table-strategy:
none:
masterslave:
load-balance-algorithm-type: round_robin
names: master_db_slave
master_db_slave:
master-data-source-name: master_db
slave-data-source-names: slave_db_0,slave_db_1
以订单表order为例,actual-data-nodes配置指定了逻辑表order对应多个实际数据库表,分布在主库和从库中。当执行插入或查询订单操作时,Sharding-JDBC 会根据order_id对 2 取模来确定具体的表,根据user_id对 2 取模(或默认使用主库)来确定具体的数据库。同时,key-generator配置使用雪花算法生成order_id,保证分布式环境下 ID 的唯一性和有序性。
总结:
Sharding-JDBC 通过配置化方式实现分库分表,对业务代码侵入性小,支持多种分片算法,但分片规则设计需谨慎,避免跨分片查询性能问题。在设计分片策略时,要充分考虑业务查询场景,尽量让常用查询操作能定位到单一分片,以提高查询效率。
3.2.2 分表键设计与分布式 ID 生成
分表键的选择直接影响分表效果,需根据业务查询场景确定。示例中使用雪花算法生成分布式 ID,ID 中可包含分表键信息,如取 ID 低 10 位作为分表键。
SnowflakeIdGenerator类通过位运算生成唯一 ID,确保在分布式环境下的唯一性和有序性。
// 雪花算法ID生成器
public class SnowflakeIdGenerator {
// 起始的时间戳
private final long startEpoch = 1577808000000L; // 2020-01-01 00:00:00
// 服务器ID所占的位数
private final long workerIdBits = 5L;
// 数据中心ID所占的位数
private final long datacenterIdBits = 5L;
// 支持的最大服务器ID,结果为31 (2^5 - 1)
private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
// 支持的最大数据中心ID,结果为31 (2^5 - 1)
private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
// 序列号所占的位数
private final long sequenceBits = 12L;
// 服务器ID左移的位数
private final long workerIdShift = sequenceBits;
// 数据中心ID左移的位数
private final long datacenterIdShift = sequenceBits + workerIdBits;
// 时间戳左移的位数
private final long timestampShift = sequenceBits + workerIdBits + datacenterIdBits;
// 序列号的掩码,用于防止序列号溢出
private final long sequenceMask = -1L ^ (-1L << sequenceBits);
// 工作机器ID
private long workerId;
// 数据中心ID
private long datacenterId;
// 序列号
private long sequence = 0L;
// 上次生成ID的时间戳
private long lastTimestamp = -1L;
public SnowflakeIdGenerator(long workerId, long datacenterId) {
// 校验工作机器ID和数据中心ID是否超出范围
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
}
if (datacenterId > maxDatacenterId || datacenterId < 0) {
throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
}
this.workerId = workerId;
this.datacenterId = datacenterId;
}
// 生成下一个ID
public synchronized long nextId() {
long timestamp = getCurrentTimeStamp();
// 如果当前时间小于上一次ID生成的时间戳,说明系统时钟回拨过,抛出异常
if (timestamp < lastTimestamp) {
throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
}
// 如果是同一时间生成的,则进行序列号自增
if (timestamp == lastTimestamp) {
sequence = (sequence + 1) & sequenceMask;
// 同一毫秒内序列号溢出,等待到下一毫秒
if (sequence == 0) {
timestamp = waitNextMillis(lastTimestamp);
}
} else {
// 不同时间,序列号重置为0
sequence = 0L;
}
// 保存当前时间戳为最后一次生成ID的时间戳
lastTimestamp = timestamp;
// 生成ID:(时间戳-起始时间戳)<<22 | 数据中心ID<<17 | 工作机器ID<<12 | 序列号
return ((timestamp - startEpoch) << timestampShift) |
(datacenterId << datacenterIdShift) |
(workerId << workerIdShift) |
sequence;
}
// 获取当前时间戳
private long getCurrentTimeStamp() {
return System.currentTimeMillis();
}
// 等待到下一毫秒
private long waitNextMillis(long lastTimestamp) {
long timestamp = getCurrentTimeStamp();
while (timestamp <= lastTimestamp) {
timestamp = getCurrentTimeStamp();
}
return timestamp;
}
}
雪花算法生成的 ID 由时间戳、数据中心 ID、工作机器 ID 和序列号四部分组成。在生成 ID 时,首先获取当前时间戳,若与上次生成 ID 的时间戳相同,则递增序列号;若序列号溢出,则等待到下一毫秒重新计数。通过这种方式,保证了生成的 ID 在分布式环境下的唯一性和大致有序性。将 ID 的部分位作为分表键,既能满足分表需求,又能利用 ID 的有序性提高查询效率,例如按时间顺序查询数据时,数据会相对集中在某些分表中。
总结:
合理的分表键设计和分布式 ID 生成策略,能有效提升分库分表后的数据查询效率和系统扩展性,但需考虑 ID 生成的性能和 ID 与分表键的关联逻辑。在实际应用中,要根据业务特点和数据增长趋势,选择合适的分表键和 ID 生成算法,并对 ID 生成器进行性能测试和优化,确保其在高并发场景下稳定可靠运行。
相关资料已更新
关注公众号:回复:资料领取,即可获取全部面试题以及1000+份学习资料