tunnel server save AgentClusterInfo into redis; web-console support path/targetServer para. #1558

This commit is contained in:
hengyunabc 2020-10-30 19:05:43 +08:00
parent e3786af5e4
commit 84ebb8959f
8 changed files with 184 additions and 61 deletions

View File

@ -0,0 +1,63 @@
package com.alibaba.arthas.tunnel.server;
/**
* @author hengyunabc 2020-10-30
*
*/
public class AgentClusterInfo {
/**
* agent本身以哪个ip连接到 tunnel server
*/
private String host;
private int port;
private String arthasVersion;
/**
* agent 连接到的 tunnel server 的ip
*/
private String clientConnectHost;
public AgentClusterInfo() {
}
public AgentClusterInfo(AgentInfo agentInfo, String clientConnectHost) {
this.host = agentInfo.getHost();
this.port = agentInfo.getPort();
this.arthasVersion = agentInfo.getArthasVersion();
this.clientConnectHost = clientConnectHost;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public String getArthasVersion() {
return arthasVersion;
}
public void setArthasVersion(String arthasVersion) {
this.arthasVersion = arthasVersion;
}
public String getClientConnectHost() {
return clientConnectHost;
}
public void setClientConnectHost(String clientConnectHost) {
this.clientConnectHost = clientConnectHost;
}
}

View File

@ -11,7 +11,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.arthas.tunnel.common.SimpleHttpResponse;
import com.alibaba.arthas.tunnel.server.utils.InetAddressUtil;
import com.alibaba.arthas.tunnel.server.cluster.TunnelClusterStore;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
@ -97,7 +97,7 @@ public class TunnelServer {
if (tunnelClusterStore != null && clientConnectHost != null) {
try {
for (Entry<String, AgentInfo> entry : agentInfoMap.entrySet()) {
tunnelClusterStore.addHost(entry.getKey(), clientConnectHost, 60 * 60, TimeUnit.SECONDS);
tunnelClusterStore.addAgent(entry.getKey(), new AgentClusterInfo(entry.getValue(), clientConnectHost), 60 * 60, TimeUnit.SECONDS);
}
} catch (Throwable t) {
logger.error("update tunnel info error", t);
@ -123,7 +123,7 @@ public class TunnelServer {
public void addAgent(String id, AgentInfo agentInfo) {
agentInfoMap.put(id, agentInfo);
if (this.tunnelClusterStore != null) {
this.tunnelClusterStore.addHost(id, clientConnectHost, 60 * 60, TimeUnit.SECONDS);
this.tunnelClusterStore.addAgent(id, new AgentClusterInfo(agentInfo, clientConnectHost), 60 * 60, TimeUnit.SECONDS);
}
}
@ -229,6 +229,11 @@ public class TunnelServer {
}
public void setPath(String path) {
path = path.trim();
if (!path.startsWith("/")) {
logger.warn("tunnel server path should start with / ! path: {}, try to auto add / .", path);
path = "/" + path;
}
this.path = path;
}
}

View File

@ -0,0 +1,35 @@
package com.alibaba.arthas.tunnel.server.app.configuration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import com.alibaba.arthas.tunnel.server.cluster.RedisTunnelClusterStore;
import com.alibaba.arthas.tunnel.server.cluster.TunnelClusterStore;
/**
*
* @author hengyunabc 2020-10-29
*
*/
@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class TunnelClusterStoreConfiguration {
@Bean
// @ConditionalOnBean(StringRedisTemplate.class)
@ConditionalOnClass(StringRedisTemplate.class)
@ConditionalOnProperty("spring.redis.host")
@ConditionalOnMissingBean
public TunnelClusterStore tunnelClusterStore(@Autowired StringRedisTemplate redisTemplate) {
RedisTunnelClusterStore redisTunnelClusterStore = new RedisTunnelClusterStore();
redisTunnelClusterStore.setRedisTemplate(redisTemplate);
return redisTunnelClusterStore;
}
}

View File

@ -2,18 +2,13 @@ package com.alibaba.arthas.tunnel.server.app.configuration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import com.alibaba.arthas.tunnel.server.RedisTunnelClusterStore;
import com.alibaba.arthas.tunnel.server.TunnelClusterStore;
import com.alibaba.arthas.tunnel.server.TunnelServer;
import com.alibaba.arthas.tunnel.server.cluster.TunnelClusterStore;
/**
*
@ -27,17 +22,6 @@ public class TunnelServerConfiguration {
@Autowired
ArthasProperties arthasProperties;
@Bean
// @ConditionalOnBean(StringRedisTemplate.class)
@ConditionalOnClass(StringRedisTemplate.class)
@ConditionalOnProperty("spring.redis.host")
@ConditionalOnMissingBean
public TunnelClusterStore tunnelClusterStore(@Autowired StringRedisTemplate redisTemplate) {
RedisTunnelClusterStore redisTunnelClusterStore = new RedisTunnelClusterStore();
redisTunnelClusterStore.setRedisTemplate(redisTemplate);
return redisTunnelClusterStore;
}
@Bean(initMethod = "start", destroyMethod = "stop")
@ConditionalOnMissingBean
public TunnelServer tunnelServer(@Autowired(required = false) TunnelClusterStore tunnelClusterStore) {

View File

@ -8,8 +8,9 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.alibaba.arthas.tunnel.server.TunnelClusterStore;
import com.alibaba.arthas.tunnel.server.AgentClusterInfo;
import com.alibaba.arthas.tunnel.server.TunnelServer;
import com.alibaba.arthas.tunnel.server.cluster.TunnelClusterStore;
/**
*
@ -30,7 +31,8 @@ public class ClusterController {
String host = null;
if (tunnelClusterStore != null) {
host = tunnelClusterStore.findHost(agentId);
AgentClusterInfo info = tunnelClusterStore.findAgent(agentId);
host = info.getClientConnectHost();
}
if (host == null) {

View File

@ -1,32 +1,47 @@
package com.alibaba.arthas.tunnel.server;
package com.alibaba.arthas.tunnel.server.cluster;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import com.alibaba.arthas.tunnel.server.AgentClusterInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
*
* @author hengyunabc 2020-10-27
*
*/
public class RedisTunnelClusterStore implements TunnelClusterStore {
private final static Logger logger = LoggerFactory.getLogger(RedisTunnelClusterStore.class);
// 定义jackson对象
private static final ObjectMapper MAPPER = new ObjectMapper();
private String prefix = "arthas-tunnel-agent-";
private StringRedisTemplate redisTemplate;
@Override
public String findHost(String agentId) {
public AgentClusterInfo findAgent(String agentId) {
try {
ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();
return opsForValue.get(prefix + agentId);
String infoStr = opsForValue.get(prefix + agentId);
AgentClusterInfo info = MAPPER.readValue(infoStr, AgentClusterInfo.class);
return info;
} catch (Throwable e) {
logger.error("try to read agentInfo error. agentId:{}", agentId, e);
throw new RuntimeException(e);
}
}
@Override
@ -36,10 +51,15 @@ public class RedisTunnelClusterStore implements TunnelClusterStore {
}
@Override
public void addHost(String agentId, String host, long timeout, TimeUnit timeUnit) {
public void addAgent(String agentId, AgentClusterInfo info, long timeout, TimeUnit timeUnit) {
try {
ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();
opsForValue.set(prefix + agentId, host, timeout, timeUnit);
String infoStr = MAPPER.writeValueAsString(info);
opsForValue.set(prefix + agentId, infoStr, timeout, timeUnit);
} catch (Throwable e) {
logger.error("try to add agentInfo error. agentId:{}", agentId, e);
throw new RuntimeException(e);
}
}
public StringRedisTemplate getRedisTemplate() {
@ -65,12 +85,14 @@ public class RedisTunnelClusterStore implements TunnelClusterStore {
}
@Override
public Collection<Pair<String, String>> agentInfo(String appName) {
public Map<String, AgentClusterInfo> agentInfo(String appName) {
try {
ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();
Set<String> keys = new HashSet<String>();
String prefixWithAppName = prefix + appName + "|";
String prefixWithAppName = prefix + appName + "_";
for (String value : opsForValue.getOperations().keys(prefixWithAppName + "*")) {
keys.add(value);
@ -79,17 +101,22 @@ public class RedisTunnelClusterStore implements TunnelClusterStore {
List<String> values = opsForValue.getOperations().opsForValue().multiGet(keys);
Collection<Pair<String, String>> result = new HashSet<>();
Map<String, AgentClusterInfo> result = new HashMap<>();
Iterator<String> iterator = values.iterator();
for (String key : keys) {
String host = iterator.next();
String infoStr = iterator.next();
AgentClusterInfo info = MAPPER.readValue(infoStr, AgentClusterInfo.class);
String agentId = key.substring(prefix.length());
result.add(Pair.of(agentId, host));
result.put(agentId, info);
}
return result;
} catch (Throwable e) {
logger.error("try to query agentInfo error. appName:{}", appName, e);
throw new RuntimeException(e);
}
}
}

View File

@ -1,9 +1,10 @@
package com.alibaba.arthas.tunnel.server;
package com.alibaba.arthas.tunnel.server.cluster;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.tuple.Pair;
import com.alibaba.arthas.tunnel.server.AgentClusterInfo;
/**
* 保存agentId连接到哪个具体的 tunnel server集群部署时使用
@ -12,13 +13,13 @@ import org.apache.commons.lang3.tuple.Pair;
*
*/
public interface TunnelClusterStore {
public void addHost(String agentId, String host, long expire, TimeUnit timeUnit);
public void addAgent(String agentId, AgentClusterInfo info, long expire, TimeUnit timeUnit);
public String findHost(String agentId);
public AgentClusterInfo findAgent(String agentId);
public void removeAgent(String agentId);
public Collection<String> allAgentIds();
public Collection<Pair<String, String>> agentInfo(String appName);
public Map<String, AgentClusterInfo> agentInfo(String appName);
}

View File

@ -77,9 +77,12 @@ function getTerminalSize () {
}
/** init websocket **/
function initWs (ip, port, path, agentId) {
function initWs (ip, port, path, agentId, targetServer) {
var protocol= location.protocol === 'https:' ? 'wss://' : 'ws://';
var uri = protocol + ip + ':' + port + '/' + path + '?method=connectArthas&id=' + agentId;
var uri = protocol + ip + ':' + port + '/' + encodeURIComponent(path) + '?method=connectArthas&id=' + agentId;
if (targetServer != null) {
uri = uri + '&targetServer=' + encodeURIComponent(targetServer);
}
ws = new WebSocket(uri);
}
@ -119,8 +122,11 @@ function startConnect (silent) {
if (path == null) {
path = "ws";
}
var targetServer = getUrlParam('targetServer');
// init webSocket
initWs(ip, port, path, agentId);
initWs(ip, port, path, agentId, targetServer);
ws.onerror = function () {
ws.close();
ws = null;