mirror of
https://gitee.com/arthas/arthas.git
synced 2024-11-29 18:58:37 +08:00
tunnel server save AgentClusterInfo into redis; web-console support path/targetServer para. #1558
This commit is contained in:
parent
e3786af5e4
commit
84ebb8959f
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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<String, String> opsForValue = redisTemplate.opsForValue();
|
||||
|
||||
return opsForValue.get(prefix + agentId);
|
||||
public AgentClusterInfo findAgent(String agentId) {
|
||||
try {
|
||||
ValueOperations<String, String> 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<String, String> opsForValue = redisTemplate.opsForValue();
|
||||
|
||||
opsForValue.set(prefix + agentId, host, timeout, timeUnit);
|
||||
public void addAgent(String agentId, AgentClusterInfo info, long timeout, TimeUnit timeUnit) {
|
||||
try {
|
||||
ValueOperations<String, String> 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<Pair<String, String>> agentInfo(String appName) {
|
||||
ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();
|
||||
public Map<String, AgentClusterInfo> agentInfo(String appName) {
|
||||
try {
|
||||
|
||||
Set<String> keys = new HashSet<String>();
|
||||
ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();
|
||||
|
||||
String prefixWithAppName = prefix + appName + "|";
|
||||
|
||||
for (String value : opsForValue.getOperations().keys(prefixWithAppName + "*")) {
|
||||
keys.add(value);
|
||||
Set<String> keys = new HashSet<String>();
|
||||
|
||||
String prefixWithAppName = prefix + appName + "_";
|
||||
|
||||
for (String value : opsForValue.getOperations().keys(prefixWithAppName + "*")) {
|
||||
keys.add(value);
|
||||
|
||||
}
|
||||
|
||||
List<String> values = opsForValue.getOperations().opsForValue().multiGet(keys);
|
||||
|
||||
Map<String, AgentClusterInfo> result = new HashMap<>();
|
||||
|
||||
Iterator<String> 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<String> values = opsForValue.getOperations().opsForValue().multiGet(keys);
|
||||
|
||||
Collection<Pair<String, String>> result = new HashSet<>();
|
||||
|
||||
Iterator<String> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user