Java后端服务在对接全国性霸王餐API时的多数据中心部署与就近调用策略

Java后端服务在对接全国性霸王餐API时的多数据中心部署与就近调用策略

随着“霸王餐”类营销活动覆盖全国,用户分布广泛,若所有请求集中到单一数据中心处理,将导致高延迟、跨运营商丢包及单点故障风险。为此,需采用多数据中心(Multi-DC)部署架构,并结合智能路由策略实现API就近调用。本文基于baodanbao.com.cn.*包结构,展示如何通过IP地域识别、动态服务发现与客户端负载均衡,构建低延迟、高可用的全国性霸王餐系统。

1. 架构设计概览

在全国部署3个核心节点:北京(华北)、上海(华东)、广州(华南)。每个节点包含完整的服务栈(Web层、业务逻辑层、缓存、数据库从库),主库集中于上海,通过异步复制同步数据。霸王餐核销回调、用户参与等写操作走主库,读操作优先本地DC。

关键挑战:如何让来自成都的用户请求被路由至广州或上海节点,而非北京?
在这里插入图片描述

2. 基于IP的地域识别

使用开源IP库(如ip2region)识别请求来源:

package baodanbao.com.cn.util;

import org.lionsoul.ip2region.DataBlock;
import org.lionsoul.ip2region.DbConfig;
import org.lionsoul.ip2region.DbSearcher;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.IOException;

public class IpRegionResolver {

    private DbSearcher searcher;

    @PostConstruct
    public void init() throws IOException {
        String dbPath = IpRegionResolver.class.getResource("/ip2region.xdb").getPath();
        searcher = new DbSearcher(new DbConfig(), dbPath);
    }

    public String getCity(String ip) {
        try {
            DataBlock block = searcher.memorySearch(ip);
            if (block != null) {
                String region = block.getRegion();
                // 格式如:中国|华南|广东省|广州市|电信
                String[] parts = region.split("\\|");
                return parts.length >= 4 ? parts[3] : "未知";
            }
        } catch (Exception e) {
            // ignore
        }
        return "未知";
    }

    @PreDestroy
    public void close() {
        if (searcher != null) {
            searcher.close();
        }
    }
}

3. 数据中心映射策略

定义城市到DC的映射规则:

package baodanbao.com.cn.config;

import org.springframework.stereotype.Component;

import java.util.Map;

@Component
public class DataCenterRouter {

    private static final Map<String, String> CITY_TO_DC = Map.of(
        "北京市", "bj",
        "天津市", "bj",
        "上海市", "sh",
        "南京市", "sh",
        "广州市", "gz",
        "深圳市", "gz",
        "成都市", "gz",
        "重庆市", "gz"
        // 可扩展为配置中心动态加载
    );

    public String getNearestDc(String city) {
        return CITY_TO_DC.getOrDefault(city, "sh"); // 默认华东
    }
}

4. 动态API网关路由(示例:Feign Client)

在调用第三方霸王餐API时,根据当前节点所属DC选择最优出口IP(部分第三方平台支持白名单绑定多IP);但更常见的是内部服务间调用需就近访问。例如,核销服务需调用本地DC的用户服务:

package baodanbao.com.cn.service;

import baodanbao.com.cn.config.DataCenterConfig;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@Component
public class DcAwareRequestInterceptor implements RequestInterceptor {

    private final String currentDc;

    public DcAwareRequestInterceptor(DataCenterConfig config) {
        this.currentDc = config.getCurrentDc();
    }

    @Override
    public void apply(RequestTemplate template) {
        // 在Header中透传当前DC,供下游服务识别
        template.header("X-Current-DC", currentDc);
    }
}

配合Nacos或Eureka的元数据过滤:

# application-bj.yml
spring:
  cloud:
    nacos:
      discovery:
        metadata:
          dc: bj

在Feign调用时只选择同DC实例:

@FeignClient(name = "user-service")
public interface UserServiceClient {

    @GetMapping("/user/{id}")
    User getUser(@PathVariable String id);
}

配合Ribbon规则(Spring Cloud LoadBalancer):

package baodanbao.com.cn.loadbalance;

import baodanbao.com.cn.config.DataCenterConfig;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.Request;
import org.springframework.cloud.loadbalancer.core.RequestData;
import org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer;
import org.springframework.cloud.loadbalancer.core.SelectedInstanceCallback;
import reactor.core.publisher.Mono;

import java.util.List;

public class SameDcLoadBalancer extends RoundRobinLoadBalancer {

    private final String currentDc;

    public SameDcLoadBalancer(String serviceId, List<ServiceInstance> serviceInstances, DataCenterConfig config) {
        super(serviceId, serviceInstances);
        this.currentDc = config.getCurrentDc();
    }

    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        List<ServiceInstance> sameDcInstances = getSameDcInstances(getServiceInstances());
        if (!sameDcInstances.isEmpty()) {
            return super.choose(request); // 实际应重写轮询逻辑
        }
        return Mono.justOrEmpty(getServiceInstances().stream().findFirst());
    }

    private List<ServiceInstance> getSameDcInstances(List<ServiceInstance> instances) {
        return instances.stream()
                .filter(instance -> currentDc.equals(instance.getMetadata().get("dc")))
                .toList();
    }
}

5. 多活写一致性保障

对于跨DC写操作(如核销记录),采用最终一致性

  • 写入本地DC数据库,并发送MQ消息;
  • 消息消费者在其他DC回放,更新本地副本;
  • 查询时优先读本地,容忍短暂不一致。
package baodanbao.com.cn.service;

import baodanbao.com.cn.model.RedeemRecord;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class RedeemService {

    private final RedeemRepository redeemRepository;
    private final KafkaTemplate<String, Object> kafkaTemplate;

    @Transactional
    public void handleRedeem(RedeemRecord record) {
        redeemRepository.save(record);
        // 异步同步至其他DC
        kafkaTemplate.send("redeem-sync-topic", record);
    }
}

6. 配置管理

各DC启动时指定自身标识:

# application.yml
baodanbao:
  datacenter:
    current: gz  # bj / sh / gz

对应配置类:

package baodanbao.com.cn.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "baodanbao.datacenter")
public class DataCenterConfig {
    private String current = "sh";
    public String getCurrentDc() { return current; }
    public void setCurrent(String current) { this.current = current; }
}

本文著作权归吃喝不愁app开发者团队,转载请注明出处!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值