diff --git a/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/AgentClusterInfo.java b/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/AgentClusterInfo.java new file mode 100644 index 00000000..0efeac84 --- /dev/null +++ b/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/AgentClusterInfo.java @@ -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; + } + +} diff --git a/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/TunnelServer.java b/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/TunnelServer.java index 88437259..9592c411 100644 --- a/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/TunnelServer.java +++ b/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/TunnelServer.java @@ -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 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; } } diff --git a/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/app/configuration/TunnelClusterStoreConfiguration.java b/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/app/configuration/TunnelClusterStoreConfiguration.java new file mode 100644 index 00000000..33adeb5b --- /dev/null +++ b/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/app/configuration/TunnelClusterStoreConfiguration.java @@ -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; + } + +} diff --git a/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/app/configuration/TunnelServerConfiguration.java b/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/app/configuration/TunnelServerConfiguration.java index 16455428..80fe4648 100644 --- a/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/app/configuration/TunnelServerConfiguration.java +++ b/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/app/configuration/TunnelServerConfiguration.java @@ -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) { diff --git a/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/app/web/ClusterController.java b/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/app/web/ClusterController.java index 0309e58c..86a56b57 100644 --- a/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/app/web/ClusterController.java +++ b/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/app/web/ClusterController.java @@ -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) { diff --git a/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/cluster/RedisTunnelClusterStore.java b/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/cluster/RedisTunnelClusterStore.java index 449ea84c..5721bdeb 100644 --- a/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/cluster/RedisTunnelClusterStore.java +++ b/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/cluster/RedisTunnelClusterStore.java @@ -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) { - ValueOperations opsForValue = redisTemplate.opsForValue(); - - return opsForValue.get(prefix + agentId); + public AgentClusterInfo findAgent(String agentId) { + try { + ValueOperations opsForValue = redisTemplate.opsForValue(); + 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) { - ValueOperations opsForValue = redisTemplate.opsForValue(); - - opsForValue.set(prefix + agentId, host, timeout, timeUnit); + public void addAgent(String agentId, AgentClusterInfo info, long timeout, TimeUnit timeUnit) { + try { + ValueOperations opsForValue = redisTemplate.opsForValue(); + 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,31 +85,38 @@ public class RedisTunnelClusterStore implements TunnelClusterStore { } @Override - public Collection> agentInfo(String appName) { - ValueOperations opsForValue = redisTemplate.opsForValue(); + public Map agentInfo(String appName) { + try { - Set keys = new HashSet(); + ValueOperations opsForValue = redisTemplate.opsForValue(); - String prefixWithAppName = prefix + appName + "|"; - - for (String value : opsForValue.getOperations().keys(prefixWithAppName + "*")) { - keys.add(value); + Set keys = new HashSet(); + String prefixWithAppName = prefix + appName + "_"; + + for (String value : opsForValue.getOperations().keys(prefixWithAppName + "*")) { + keys.add(value); + + } + + List values = opsForValue.getOperations().opsForValue().multiGet(keys); + + Map result = new HashMap<>(); + + Iterator iterator = values.iterator(); + + for (String key : keys) { + String infoStr = iterator.next(); + AgentClusterInfo info = MAPPER.readValue(infoStr, AgentClusterInfo.class); + String agentId = key.substring(prefix.length()); + result.put(agentId, info); + } + + return result; + } catch (Throwable e) { + logger.error("try to query agentInfo error. appName:{}", appName, e); + throw new RuntimeException(e); } - - List values = opsForValue.getOperations().opsForValue().multiGet(keys); - - Collection> result = new HashSet<>(); - - Iterator iterator = values.iterator(); - - for (String key : keys) { - String host = iterator.next(); - String agentId = key.substring(prefix.length()); - result.add(Pair.of(agentId, host)); - } - - return result; } } diff --git a/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/cluster/TunnelClusterStore.java b/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/cluster/TunnelClusterStore.java index 21f959f4..e03b31e5 100644 --- a/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/cluster/TunnelClusterStore.java +++ b/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/cluster/TunnelClusterStore.java @@ -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 allAgentIds(); - public Collection> agentInfo(String appName); + public Map agentInfo(String appName); } diff --git a/tunnel-server/src/main/resources/static/web-console.js b/tunnel-server/src/main/resources/static/web-console.js index fb8401c5..31905bd5 100644 --- a/tunnel-server/src/main/resources/static/web-console.js +++ b/tunnel-server/src/main/resources/static/web-console.js @@ -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;