【架构设计(二)】高可用、高并发的 Java 架构设计

【架构设计(二)】高可用、高并发的 Java 架构设计

在互联网业务爆发式增长的今天,高可用和高并发已成为 Java 系统架构设计的核心目标。本文将围绕负载均衡与高可用架构、缓存设计与优化、数据库读写分离与分库分表三大关键领域,深入剖析其原理,并结合完整的代码示例,帮助开发者构建稳定高效的系统架构。

无套路、关注即可领。持续更新中
关注公众号:搜 【架构研究站】 回复:资料领取,即可获取全部面试题以及1000+份学习资料

一、负载均衡与高可用架构

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.cloudgroupId><artifactId>spring-cloud-starter-netflix-ribbonartifactId>dependency>
配置类
@Configurationpublic class RibbonConfig {// 自定义负载均衡策略​
    @Beanpublic IRule ribbonRule() {// 随机策略​
        return new RandomRule();// 轮询策略:return new RoundRobinRule();​
        // 权重策略:return new WeightedResponseTimeRule();​
    }}
服务调用示例
@Servicepublic class UserService {@Autowiredprivate LoadBalancerClient loadBalancer;​
    ​
    @Autowiredprivate 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@EnableDiscoveryClientpublic class Application {@Bean@LoadBalancedpublic 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.cloudgroupId><artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>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发现服务
@Servicepublic class OrderService {@Autowiredprivate 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.cloudgroupId><artifactId>spring-cloud-starter-alibaba-sentinelartifactId>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
服务熔断示例
@Servicepublic 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);}
多级缓存实现类
@Servicepublic 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;}​
    ​
    @Overridepublic void clear(String key) {​
        firstLevelCache.clear(key);​
        secondLevelCache.clear(key);}}
本地缓存实现(Caffeine)
@Servicepublic 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)
@Servicepublic 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.guavagroupId><artifactId>guavaartifactId><version>31.1-jreversion>dependency>
布隆过滤器实现
@Servicepublic 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);}}
应用示例
@Servicepublic 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.redissongroupId><artifactId>redisson-spring-boot-starterartifactId><version>3.16.5version>dependency>
分布式锁工具类
@Servicepublic class DistributedLockService {@Autowiredprivate 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+份学习资料【架构设计(二)】高可用、高并发的 Java 架构设计_第1张图片

你可能感兴趣的:(Java成神之路-架构师进阶,java,架构,开发语言)