在 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;
}
}