应用高可用设计要点
应用高可用设计要点
面试指北 · 通用
我来为你详细介绍高频访问下保证服务高可用的具体方案,结合你的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