Soul 网关源码分析(十六)Upstream 服务探活

372 阅读2分钟

在 Soul 网关中,我们配置 Selector 的时候有时候会发现之前配置的 ip 地址等信息不见了,原因是 soul-admin 中有对 upstream 服务进行探活,如果监听不到服务响应,则会清除相关的配置。下面我们深入源码看看是如何做到的:

soul-admin端 UpstreamCheckService#setup()启动加载后初始时候从db查询出来放入upstreamMap。

/**
 * this is divide  http url upstream.
 *
 * @author xiaoyu
 */
@Slf4j
@Component
public class UpstreamCheckService {

	//用于存放 upstream
    private static final Map<String, List<DivideUpstream>> UPSTREAM_MAP = Maps.newConcurrentMap();

	//是否开启探活开关
    @Value("${soul.upstream.check:true}")
    private boolean check;
	
    //定期探活的时间,默认是10秒
    @Value("${soul.upstream.scheduledTime:10}")
    private int scheduledTime;

    private final SelectorMapper selectorMapper;

    private final ApplicationEventPublisher eventPublisher;

    private final PluginMapper pluginMapper;

    private final SelectorConditionMapper selectorConditionMapper;
    
    //...
    
    /**
     * 装载Selector 配置
     */
    @PostConstruct
    public void setup() {
        PluginDO pluginDO = pluginMapper.selectByName(PluginEnum.DIVIDE.getName());
        if (pluginDO != null) {
            List<SelectorDO> selectorDOList = selectorMapper.findByPluginId(pluginDO.getId());
            //遍历 Selector 记录,把 upstream 放到对应的 key(selector name) 中
            for (SelectorDO selectorDO : selectorDOList) {
                List<DivideUpstream> divideUpstreams = GsonUtils.getInstance().fromList(selectorDO.getHandle(), DivideUpstream.class);
                if (CollectionUtils.isNotEmpty(divideUpstreams)) {
                    UPSTREAM_MAP.put(selectorDO.getName(), divideUpstreams);
                }
            }
        }
        if (check) {
            new ScheduledThreadPoolExecutor(Runtime.getRuntime().availableProcessors(), SoulThreadFactory.create("scheduled-upstream-task", false))
                    .scheduleWithFixedDelay(this::scheduled, 10, scheduledTime, TimeUnit.SECONDS);
        }
    }
    
    //...
    
    /**
     * Submit.
     *
     * @param selectorName   the selector name
     * @param divideUpstream the divide upstream
     */
    public void submit(final String selectorName, final DivideUpstream divideUpstream) {
        if (UPSTREAM_MAP.containsKey(selectorName)) {
            UPSTREAM_MAP.get(selectorName).add(divideUpstream);
        } else {
            UPSTREAM_MAP.put(selectorName, Lists.newArrayList(divideUpstream));
        }
    }

    /**
     * Replace.
     *
     * @param selectorName    the selector name
     * @param divideUpstreams the divide upstream list
     */
    public void replace(final String selectorName, final List<DivideUpstream> divideUpstreams) {
        UPSTREAM_MAP.put(selectorName, divideUpstreams);
    }

	// 使用一个定时的线程池进行探活操作
    private void scheduled() {
        if (UPSTREAM_MAP.size() > 0) {
            UPSTREAM_MAP.forEach(this::check);
        }
    }

	//核心方法,对每一个选择器中的 Upstream 执行 checkUrl 操作
    private void check(final String selectorName, final List<DivideUpstream> upstreamList) {
        List<DivideUpstream> successList = Lists.newArrayListWithCapacity(upstreamList.size());
        for (DivideUpstream divideUpstream : upstreamList) {
            final boolean pass = UpstreamCheckUtils.checkUrl(divideUpstream.getUpstreamUrl());
            if (pass) {
                if (!divideUpstream.isStatus()) {
                    divideUpstream.setTimestamp(System.currentTimeMillis());
                    divideUpstream.setStatus(true);
                    log.info("UpstreamCacheManager check success the url: {}, host: {} ", divideUpstream.getUpstreamUrl(), divideUpstream.getUpstreamHost());
                }
                successList.add(divideUpstream);
            } else {
                divideUpstream.setStatus(false);
                log.error("check the url={} is fail ", divideUpstream.getUpstreamUrl());
            }
        }
        if (successList.size() == upstreamList.size()) {
            return;
        }
        if (successList.size() > 0) {
            UPSTREAM_MAP.put(selectorName, successList);
            updateSelectorHandler(selectorName, successList);
        } else {
            UPSTREAM_MAP.remove(selectorName);
            updateSelectorHandler(selectorName, null);
        }
    }

    //...
}

下面是 UpstreamCheckUtils:

public class UpstreamCheckUtils {

	//正则表达式,筛选出合法的 IP
    private static final Pattern PATTERN = Pattern
            .compile("(http://|https://)?(?:(?:[0,1]?\\d?\\d|2[0-4]\\d|25[0-5])\\.){3}(?:[0,1]?\\d?\\d|2[0-4]\\d|25[0-5]):\\d{0,5}");

    private static final String HTTP = "http";

    /**
     * Check url boolean.
     *
     * @param url the url
     * @return the boolean
     */
    public static boolean checkUrl(final String url) {
        if (StringUtils.isBlank(url)) {
            return false;
        }
        if (checkIP(url)) {
            String[] hostPort;
            //如果是 http 协议
            if (url.startsWith(HTTP)) {
                final String[] http = StringUtils.split(url, "\\/\\/");
                hostPort = StringUtils.split(http[1], Constants.COLONS);
            } else {
                hostPort = StringUtils.split(url, Constants.COLONS);
            }
            return isHostConnector(hostPort[0], Integer.parseInt(hostPort[1]));
        } else {
            return isHostReachable(url);
        }
    }

    private static boolean checkIP(final String url) {
        return PATTERN.matcher(url).matches();
    }

    private static boolean isHostConnector(final String host, final int port) {
        try (Socket socket = new Socket()) {
        	//使用socket客户端链接 upstream
            socket.connect(new InetSocketAddress(host, port));
        } catch (IOException e) {
        	// 抛出异常,则返回 false
            return false;
        }
        return true;
    }
	
    // 检查 域名是否可达
    private static boolean isHostReachable(final String host) {
        try {
            return InetAddress.getByName(host).isReachable(1000);
        } catch (IOException ignored) {
        }
        return false;
    }
}