" />
文章

应用高可用设计要点

应用高可用设计要点

面试指北 · 通用

我来为你详细介绍高频访问下保证服务高可用的具体方案,结合你的GIS项目特点提供实战建议。

高可用架构设计

graph TB
    A[用户请求] --> B[CDN/反向代理]
    B --> C[负载均衡器<br/>Nginx/HAProxy]

    C --> D1[应用服务器1<br/>Spring Boot]
    C --> D2[应用服务器2<br/>Spring Boot]  
    C --> D3[应用服务器N<br/>Spring Boot]

    D1 --> E[缓存层]
    D2 --> E
    D3 --> E

    E --> F1[Redis主节点]
    E --> F2[Redis从节点]

    D1 --> G[数据库层]
    D2 --> G
    D3 --> G

    G --> H1[主数据库<br/>PostgreSQL Master]
    G --> H2[从数据库1<br/>PostgreSQL Slave]
    G --> H3[从数据库2<br/>PostgreSQL Slave]

    I[监控系统<br/>Prometheus] --> D1
    I --> D2
    I --> D3
    I --> F1
    I --> H1

1. 负载均衡策略

Nginx负载均衡配置

upstream gis_backend {
    # 加权轮询
    server 192.168.1.10:8080 weight=3;
    server 192.168.1.11:8080 weight=2;
    server 192.168.1.12:8080 weight=1;

    # 健康检查
    keepalive 32;
}

server {
    listen 80;
    server_name gis.example.com;

    location / {
        proxy_pass http://gis_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        # 超时设置
        proxy_connect_timeout 5s;
        proxy_send_timeout 10s;
        proxy_read_timeout 10s;

        # 失败重试
        proxy_next_upstream error timeout http_500 http_502 http_503;
    }

    # 静态资源缓存
    location ~* \.(js|css|png|jpg|jpeg|gif|svg)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
}

2. 多层缓存策略

Redis分布式缓存实现

@Configuration
@EnableCaching
public class CacheConfig {

    @Bean
    public LettuceConnectionFactory redisConnectionFactory() {
        // Redis Sentinel 配置 - 高可用
        RedisSentinelConfiguration sentinelConfig = 
            new RedisSentinelConfiguration()
                .master("mymaster")
                .sentinel("192.168.1.20", 26379)
                .sentinel("192.168.1.21", 26379)
                .sentinel("192.168.1.22", 26379);

        return new LettuceConnectionFactory(sentinelConfig);
    }

    @Bean
    public CacheManager cacheManager() {
        RedisCacheManager.Builder builder = RedisCacheManager
            .RedisCacheManagerBuilder
            .fromConnectionFactory(redisConnectionFactory())
            .cacheDefaults(cacheConfiguration());

        return builder.build();
    }

    private RedisCacheConfiguration cacheConfiguration() {
        return RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofMinutes(30))
            .serializeKeysWith(RedisSerializationContext.SerializationPair
                .fromSerializer(new StringRedisSerializer()))
            .serializeValuesWith(RedisSerializationContext.SerializationPair
                .fromSerializer(new GenericJackson2JsonRedisSerializer()));
    }
}

/**
 * GIS数据缓存服务
 */
@Service
public class GeoDataCacheService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    private static final String GEO_CACHE_PREFIX = "geo:data:";
    private static final String TILE_CACHE_PREFIX = "tile:cache:";

    // 缓存地理数据
    @Cacheable(value = "geoData", key = "#layerId + ':' + #bounds")
    public String getCachedGeoData(String layerId, String bounds) {
        // 模拟从数据库获取地理数据
        return queryGeoDataFromDB(layerId, bounds);
    }

    // 缓存瓦片数据
    public void cacheTileData(int z, int x, int y, byte[] tileData) {
        String key = TILE_CACHE_PREFIX + z + ":" + x + ":" + y;
        redisTemplate.opsForValue().set(key, 
            Base64.getEncoder().encodeToString(tileData), 
            Duration.ofHours(24));
    }

    public byte[] getCachedTile(int z, int x, int y) {
        String key = TILE_CACHE_PREFIX + z + ":" + x + ":" + y;
        String cachedData = redisTemplate.opsForValue().get(key);
        return cachedData != null ? Base64.getDecoder().decode(cachedData) : null;
    }

    // 批量预热缓存
    @Async
    public void preloadPopularData() {
        List<String> popularLayers = Arrays.asList("roads", "buildings", "poi");
        popularLayers.forEach(layerId -> {
            // 预加载热点区域数据
            getCachedGeoData(layerId, "hot_area_bounds");
        });
    }

    private String queryGeoDataFromDB(String layerId, String bounds) {
        // 实际数据库查询逻辑
        return "geojson_data_for_" + layerId;
    }
}

3. 数据库读写分离与分片

MyBatis读写分离配置

@Configuration
public class DatabaseConfig {

    // 主数据源 - 写操作
    @Bean
    @Primary
    public DataSource masterDataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:postgresql://master-db:5432/gisdb");
        config.setUsername("gisuser");
        config.setPassword("<your-password>");
        config.setMaximumPoolSize(20);
        config.setMinimumIdle(5);
        config.setConnectionTimeout(30000);
        config.setIdleTimeout(600000);
        config.setMaxLifetime(1800000);
        return new HikariDataSource(config);
    }

    // 从数据源 - 读操作
    @Bean
    public DataSource slaveDataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:postgresql://slave-db:5432/gisdb");
        config.setUsername("gisuser");
        config.setPassword("<your-password>");
        config.setMaximumPoolSize(30);
        config.setMinimumIdle(10);
        return new HikariDataSource(config);
    }

    // 动态数据源切换
    @Bean
    public DataSource dynamicDataSource() {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        Map<Object, Object> dataSourceMap = new HashMap<>();
        dataSourceMap.put("master", masterDataSource());
        dataSourceMap.put("slave", slaveDataSource());

        dynamicDataSource.setTargetDataSources(dataSourceMap);
        dynamicDataSource.setDefaultTargetDataSource(masterDataSource());
        return dynamicDataSource;
    }
}

/**
 * 动态数据源切换
 */
public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSourceType();
    }
}

/**
 * 数据源上下文
 */
public class DataSourceContextHolder {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    public static void setDataSourceType(String dataSourceType) {
        contextHolder.set(dataSourceType);
    }

    public static String getDataSourceType() {
        return contextHolder.get();
    }

    public static void clearDataSourceType() {
        contextHolder.remove();
    }
}

/**
 * 读写分离AOP切面
 */
@Aspect
@Component
public class DataSourceAspect {

    @Before("@annotation(readOnly)")
    public void setReadDataSourceType(ReadOnly readOnly) {
        DataSourceContextHolder.setDataSourceType("slave");
    }

    @Before("@annotation(org.springframework.transaction.annotation.Transactional)")
    public void setWriteDataSourceType() {
        DataSourceContextHolder.setDataSourceType("master");
    }

    @After("@annotation(ReadOnly) || @annotation(org.springframework.transaction.annotation.Transactional)")
    public void restoreDataSourceType() {
        DataSourceContextHolder.clearDataSourceType();
    }
}

// 自定义只读注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ReadOnly {
}

4. 限流与熔断机制

Sentinel限流配置

@RestController
@RequestMapping("/api/geo")
public class GeoDataController {

    @Autowired
    private GeoDataService geoDataService;

    // 限流:每秒最多100个请求
    @SentinelResource(
        value = "getGeoData",
        blockHandler = "handleBlock",
        fallback = "handleFallback"
    )
    @GetMapping("/data/{layerId}")
    public ResponseEntity<?> getGeoData(
            @PathVariable String layerId,
            @RequestParam String bounds) {

        return ResponseEntity.ok(geoDataService.getGeoData(layerId, bounds));
    }

    // 限流降级处理
    public ResponseEntity<?> handleBlock(String layerId, String bounds, BlockException ex) {
        return ResponseEntity.status(429)
            .body(Map.of("error", "请求过于频繁,请稍后重试", "code", "RATE_LIMITED"));
    }

    // 异常降级处理
    public ResponseEntity<?> handleFallback(String layerId, String bounds, Throwable ex) {
        // 返回缓存数据或默认数据
        return ResponseEntity.ok(Map.of(
            "type", "FeatureCollection",
            "features", Collections.emptyList(),
            "message", "服务暂不可用,返回默认数据"
        ));
    }
}

/**
 * Sentinel规则配置
 */
@Configuration
public class SentinelConfig {

    @PostConstruct
    public void initFlowRules() {
        List<FlowRule> rules = new ArrayList<>();

        // 地理数据查询限流
        FlowRule geoDataRule = new FlowRule();
        geoDataRule.setResource("getGeoData");
        geoDataRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        geoDataRule.setCount(100); // 每秒100个请求
        geoDataRule.setStrategy(RuleConstant.STRATEGY_DIRECT);
        rules.add(geoDataRule);

        // 瓦片服务限流
        FlowRule tileRule = new FlowRule();
        tileRule.setResource("getTile");
        tileRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        tileRule.setCount(500); // 瓦片请求更频繁
        rules.add(tileRule);

        FlowRuleManager.loadRules(rules);
    }

    @PostConstruct
    public void initDegradeRules() {
        List<DegradeRule> rules = new ArrayList<>();

        // 熔断规则:异常率超过50%时熔断
        DegradeRule rule = new DegradeRule();
        rule.setResource("getGeoData");
        rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO);
        rule.setCount(0.5); // 50%异常率
        rule.setTimeWindow(10); // 熔断时间窗口10秒
        rule.setMinRequestAmount(5); // 最小请求数
        rules.add(rule);

        DegradeRuleManager.loadRules(rules);
    }
}

5. 异步处理和消息队列

/**
 * 异步任务处理
 */
@Service
public class AsyncGeoProcessingService {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    // 异步处理大数据量请求
    @Async("geoTaskExecutor")
    public CompletableFuture<String> processLargeGeoData(String dataId) {
        try {
            // 模拟耗时的地理数据处理
            Thread.sleep(5000);

            String result = "处理完成: " + dataId;

            // 发送处理完成消息
            rabbitTemplate.convertAndSend("geo.processing.complete", result);

            return CompletableFuture.completedFuture(result);
        } catch (Exception e) {
            return CompletableFuture.failedFuture(e);
        }
    }

    // 批量数据预处理
    @RabbitListener(queues = "geo.batch.processing")
    public void processBatchData(String message) {
        // 批量处理逻辑
        System.out.println("处理批量地理数据: " + message);
    }
}

/**
 * 异步配置
 */
@Configuration
@EnableAsync
public class AsyncConfig {

    @Bean("geoTaskExecutor")
    public TaskExecutor geoTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(50);
        executor.setQueueCapacity(200);
        executor.setThreadNamePrefix("geo-async-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
}

6. 健康检查和监控

/**
 * 健康检查端点
 */
@RestController
@RequestMapping("/actuator")
public class HealthController {

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private DataSource dataSource;

    @GetMapping("/health/detailed")
    public ResponseEntity<Map<String, Object>> detailedHealth() {
        Map<String, Object> health = new HashMap<>();

        // 检查数据库连接
        health.put("database", checkDatabase());

        // 检查Redis连接
        health.put("redis", checkRedis());

        // 检查系统资源
        health.put("system", checkSystemResources());

        boolean allHealthy = health.values().stream()
            .allMatch(status -> "UP".equals(((Map) status).get("status")));

        return ResponseEntity
            .status(allHealthy ? 200 : 503)
            .body(health);
    }

    private Map<String, Object> checkDatabase() {
        try {
            dataSource.getConnection().close();
            return Map.of("status", "UP", "responseTime", "20ms");
        } catch (Exception e) {
            return Map.of("status", "DOWN", "error", e.getMessage());
        }
    }

    private Map<String, Object> checkRedis() {
        try {
            redisTemplate.opsForValue().get("health:check");
            return Map.of("status", "UP", "responseTime", "5ms");
        } catch (Exception e) {
            return Map.of("status", "DOWN", "error", e.getMessage());
        }
    }

    private Map<String, Object> checkSystemResources() {
        Runtime runtime = Runtime.getRuntime();
        long totalMemory = runtime.totalMemory();
        long freeMemory = runtime.freeMemory();
        double memoryUsage = (double)(totalMemory - freeMemory) / totalMemory * 100;

        return Map.of(
            "status", memoryUsage < 90 ? "UP" : "WARNING",
            "memoryUsage", String.format("%.2f%%", memoryUsage),
            "availableProcessors", runtime.availableProcessors()
        );
    }
}

7. 容器化部署策略

Docker Compose高可用配置

version: '3.8'

services:
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
    depends_on:
      - app1
      - app2
    restart: always

  app1:
    build: .
    environment:
      - SPRING_PROFILES_ACTIVE=prod
      - SERVER_PORT=8080
    depends_on:
      - postgres-master
      - redis-master
    restart: always
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  app2:
    build: .
    environment:
      - SPRING_PROFILES_ACTIVE=prod
      - SERVER_PORT=8080
    depends_on:
      - postgres-master
      - redis-master
    restart: always

  postgres-master:
    image: postgres:13
    environment:
      - POSTGRES_DB=gisdb
      - POSTGRES_USER=gisuser
      - POSTGRES_PASSWORD=<your-password>
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./postgres_init_scripts:/docker-entrypoint-initdb.d
    restart: always

  postgres-slave:
    image: postgres:13
    environment:
      - POSTGRES_DB=gisdb
      - POSTGRES_USER=gisuser
      - POSTGRES_PASSWORD=<your-password>
    volumes:
      - postgres_slave_data:/var/lib/postgresql/data
    restart: always

  redis-master:
    image: redis:alpine
    restart: always

  redis-sentinel:
    image: redis:alpine
    command: redis-sentinel /etc/redis/sentinel.conf
    volumes:
      - ./redis-sentinel.conf:/etc/redis/sentinel.conf
    restart: always

volumes:
  postgres_data:
  postgres_slave_data:

关键性能指标

指标 目标值 监控方式
响应时间 P99 < 500ms APM监控
可用性 99.9% 健康检查
吞吐量 > 1000 QPS 压力测试
错误率 < 0.1% 日志分析
缓存命中率 > 80% Redis监控

这套方案能够应对高并发场景,确保你的GIS服务在大流量下保持稳定运行。根据实际业务需求,可以进一步调整各个组件的配置参数。

License:  CC BY 4.0