Skip to content

Commit

Permalink
增加基于tomcat的应用的流量优雅上下线功能
Browse files Browse the repository at this point in the history
  • Loading branch information
4fool committed Jun 28, 2023
1 parent d080a30 commit 1a1a727
Show file tree
Hide file tree
Showing 9 changed files with 456 additions and 9 deletions.
8 changes: 8 additions & 0 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,14 @@
<optional>true</optional>
</dependency>

<!--
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
-->

</dependencies>

</project>
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
import com.taobao.arthas.core.command.monitor200.WatchCommand;
import com.taobao.arthas.core.command.monitor200.bigkey.BigKeyCommand;
import com.taobao.arthas.core.command.monitor200.blocked.BlockedThreadCommand;
import com.taobao.arthas.core.command.monitor200.offline.OffLineCommand;
import com.taobao.arthas.core.command.monitor200.tomcat.KeepAliveCommand;
import com.taobao.arthas.core.command.monitor200.toomany.TooManyResultCommand;
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
import com.taobao.arthas.core.shell.command.Command;
Expand Down Expand Up @@ -113,6 +115,9 @@ private void initCommands(List<String> disabledCommands) {
commandClassList.add(BlockedThreadCommand.class);
commandClassList.add(BigKeyCommand.class);

commandClassList.add(OffLineCommand.class);
commandClassList.add(KeepAliveCommand.class);

try {
if (ClassLoader.getSystemClassLoader().getResource("jdk/jfr/Recording.class") != null) {
commandClassList.add(JFRCommand.class);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.taobao.arthas.core.command.model;

import javax.management.MBeanAttributeInfo;

/**
* MBean attribute
*
Expand All @@ -9,6 +11,7 @@ public class MBeanAttributeVO {
private String name;
private Object value;
private String error;
private MBeanAttributeInfo attribute;

public MBeanAttributeVO(String name, Object value) {
this.name = name;
Expand Down Expand Up @@ -44,4 +47,12 @@ public String getError() {
public void setError(String error) {
this.error = error;
}

public MBeanAttributeInfo getAttribute() {
return attribute;
}

public void setAttribute(MBeanAttributeInfo attribute) {
this.attribute = attribute;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,7 @@
import java.util.Timer;
import java.util.TimerTask;

import javax.management.MBeanAttributeInfo;
import javax.management.MBeanInfo;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.*;
import javax.management.openmbean.CompositeData;
import javax.management.openmbean.TabularData;

Expand Down Expand Up @@ -75,6 +71,8 @@ public class MBeanCommand extends AnnotatedCommand {
private Timer timer;
private long count = 0;

private int newValue = Integer.MIN_VALUE;

@Argument(argName = "name-pattern", index = 0, required = false)
@Description("ObjectName pattern, see javax.management.ObjectName for more detail. \n" +
"It looks like this: \n" +
Expand Down Expand Up @@ -116,6 +114,12 @@ public void setNumOfExecutions(int numOfExecutions) {
this.numOfExecutions = numOfExecutions;
}

@Option(shortName = "s", longName = "newValue", flag = true)
@Description("set newValue to attribute.")
public void setNewValue(int newValue) {
this.newValue = newValue;
}

public String getName() {
return name;
}
Expand Down Expand Up @@ -143,7 +147,27 @@ public void process(CommandProcess process) {
listMBean(process);
} else if (isMetaData()) {
listMetaData(process);
} else {
} else if(this.newValue != Integer.MIN_VALUE){
Object oldValue = null;
try{
boolean success = false;
ObjectName objectName = new ObjectName(name);
MBeanServer platformMBeanServer = ManagementFactory.getPlatformMBeanServer();
oldValue = platformMBeanServer.getAttribute(objectName,attribute);
if(oldValue instanceof Integer){
platformMBeanServer.setAttribute(objectName,new Attribute(attribute,newValue));
success = true;
}

logger.info("{} {} oldValue is {},set newValue {},success {}",
name,attribute,oldValue,newValue,success);
}catch (Exception e){
logger.error("set {} {} from {} to {} error",name,attribute,oldValue,newValue,e);
}

this.newValue = Integer.MIN_VALUE;
listAttribute(process);
}else {
listAttribute(process);
}
}
Expand Down Expand Up @@ -412,7 +436,9 @@ public void run() {
} else {
try {
Object attributeObj = platformMBeanServer.getAttribute(objectName, attributeName);
attributeVOs.add(createMBeanAttributeVO(attributeName, attributeObj));
MBeanAttributeVO mBeanAttributeVO = createMBeanAttributeVO(attributeName, attributeObj);
mBeanAttributeVO.setAttribute(attribute);
attributeVOs.add(mBeanAttributeVO);
} catch (Throwable e) {
logger.error("read mbean attribute failed: objectName={}, attributeName={}", objectName, attributeName, e);
String errorStr;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package com.taobao.arthas.core.command.monitor200.offline;

import com.alibaba.arthas.deps.org.slf4j.Logger;
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON;
import com.taobao.arthas.core.GlobalOptions;
import com.taobao.arthas.core.advisor.AdviceListener;
import com.taobao.arthas.core.command.Constants;
import com.taobao.arthas.core.command.monitor200.EnhancerCommand;
import com.taobao.arthas.core.shell.cli.Completion;
import com.taobao.arthas.core.shell.cli.CompletionUtils;
import com.taobao.arthas.core.shell.command.CommandProcess;
import com.taobao.arthas.core.util.SearchUtils;
import com.taobao.arthas.core.util.matcher.Matcher;
import com.taobao.middleware.cli.annotations.Description;
import com.taobao.middleware.cli.annotations.Name;
import com.taobao.middleware.cli.annotations.Option;
import com.taobao.middleware.cli.annotations.Summary;

import java.util.Arrays;
import java.util.concurrent.ConcurrentHashMap;

@Name("offline")
@Summary("offline http connection for cxf")
@Description(Constants.EXPRESS_DESCRIPTION + "\nExamples:\n" +
" offline -host 127.0.0.1 -off true -close false\n" +
Constants.WIKI + Constants.WIKI_HOME + "offline")
public class OffLineCommand extends EnhancerCommand {
private static final Logger logger = LoggerFactory.getLogger(OffLineCommand.class);
private static String className;
private static String methodName;
static {
className = "org.apache.cxf.transport.servlet.AbstractHTTPServlet";
methodName = "service";
}

protected ConcurrentHashMap<String,String> hosts = new ConcurrentHashMap<>();
@Option(shortName = "host", longName = "host")
@Description("host ip address")
public void setHost(String host) {
this.hosts.put(host,host);
logger.info("{}", JSON.toJSONString(hosts));
}

@Option(shortName = "off", longName = "off")
@Description("offline or online")
public void setOff(boolean off) {
if(!off){
hosts.clear();
}
}

@Option(shortName = "className", longName = "className")
@Description("className")
public void setClassName(String cls) {
className = cls;
}

@Option(shortName = "method", longName = "method")
@Description("method")
public void setMethod(String method) {
methodName = method;
}

private boolean close;
@Option(shortName = "close", longName = "close")
@Description("close socket")
public void setClose(boolean close) {
this.close = close;
}

public boolean isClose() {
return close;
}

@Override
protected Matcher getClassNameMatcher() {
if (classNameMatcher == null) {
classNameMatcher = SearchUtils.classNameMatcher(className, false);
}
return classNameMatcher;
}

@Override
protected Matcher getClassNameExcludeMatcher() {
return classNameExcludeMatcher;
}

@Override
protected Matcher getMethodNameMatcher() {
if (methodNameMatcher == null) {
methodNameMatcher = SearchUtils.classNameMatcher(methodName, false);
}
return methodNameMatcher;
}

@Override
protected AdviceListener getAdviceListener(CommandProcess process) {
return new OfflineAdviceListener(this, process, GlobalOptions.verbose || this.verbose);
}

@Override
protected void completeArgument3(Completion completion) {
CompletionUtils.complete(completion, Arrays.asList(EXPRESS_EXAMPLES));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package com.taobao.arthas.core.command.monitor200.offline;

import com.alibaba.arthas.deps.org.slf4j.Logger;
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
import com.taobao.arthas.core.advisor.AdviceListenerAdapter;
import com.taobao.arthas.core.advisor.ArthasMethod;
import com.taobao.arthas.core.shell.command.CommandProcess;

import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Iterator;

class OfflineAdviceListener extends AdviceListenerAdapter {
private static final Logger logger = LoggerFactory.getLogger(OfflineAdviceListener.class);
private OffLineCommand command;
private CommandProcess process;

public OfflineAdviceListener(OffLineCommand command, CommandProcess process, boolean verbose) {
this.command = command;
this.process = process;
super.setVerbose(verbose);
}

@Override
public void before(ClassLoader loader, Class<?> clazz, ArthasMethod method, Object target, Object[] args)
throws Throwable {
//logger.info("{} {} {}",clazz.getName(),method.getName(),target);
try {
if(args.length < 2){
return;
}
Method method1 = method(args[0].getClass(),"getRemoteHost");
method1.setAccessible(true);
String host = (String) method1.invoke(args[0]);
if (command.hosts.containsKey(host)) {
Object response = args[1];
Class cls = response.getClass();
Method setHeader = method(cls,"setHeader",String.class,String.class);
setHeader.invoke(response,"Connection","close");

logger.info("before remote host is {} ,setHeader Connection close,isClose {}",host,command.isClose());
}
}catch (Throwable t){
logger.error("{} {}",args[0],args[1],t);
}
}

@Override
public void afterReturning(ClassLoader loader, Class<?> clazz, ArthasMethod method, Object target, Object[] args,
Object returnObject) throws Throwable {
//logger.info("{} {} {} {}",clazz.getName(),method.getName(),target,args.length);
try {
if(args.length < 2){
return;
}
Method method1 = method(args[0].getClass(),"getRemoteHost");
method1.setAccessible(true);
String host = (String) method1.invoke(args[0]);
if (command.hosts.containsKey(host)) {
Object response = args[1];
Class cls = response.getClass();

Method getHeader = method(cls,"getHeader",String.class);

Method getHeaderNames = method(cls,"getHeaderNames");
Collection<String> collection = (Collection<String>)getHeaderNames.invoke(response);

StringBuilder stringBuilder = new StringBuilder();
if(collection != null){
Iterator<String> iterator = collection.iterator();
while (iterator.hasNext()){
String header = iterator.next();
String value = (String) getHeader.invoke(response,header);
if(stringBuilder.length() == 0){
stringBuilder.append(header).append(":").append(value);
}else {
stringBuilder.append(",").append(header).append(":").append(value);
}
}
}

if(command.isClose()){
Method getWriter = method(cls,"getWriter");
Object printWriter = getWriter.invoke(response);

Method close = method(printWriter.getClass(),"close");
close.invoke(printWriter);
}
logger.info("after remote host is {} ,setHeader Connection close,isClose {},headers {}",
host,command.isClose(),stringBuilder.toString());
}
}catch (Throwable t){
logger.error("{} {}",args[0],args[1],t);
}
}

@Override
public void afterThrowing(ClassLoader loader, Class<?> clazz, ArthasMethod method, Object target, Object[] args,
Throwable throwable) {
logger.info("{} {} {} {}",clazz.getName(),method.getName(),target,args.length);
}

private static Method method(Class<?> clazz, String methodName,Class<?>... parameterTypes){
if(clazz == null){
return null;
}
try{
return clazz.getDeclaredMethod(methodName,parameterTypes);
} catch (NoSuchMethodException e) {
return method(clazz.getSuperclass(),methodName);
}
}
}
Loading

0 comments on commit 1a1a727

Please sign in to comment.