生产环境上问题排查的方法-远程调试-热部署-不重启加代码

我们在生产环境上遇到问题,解决方法 我目前知道的有三种:

1. 远程调试。

优点:像本地开发一样,一步一步调试,调试方便。

缺点:需要本地代码跟远程代码,一致;编译的时候,需要有行号;生产环境启动的时候,需配置了需要的参数:例如 -Xdebug -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=y  , 没有这些参数,可以添加参数,然后重启即可。

2. 热部署。

热部署,即 修改了class文件,可以立即加载。与重启差不多,只是不重启,可以保留运行中的一些环境。

优点:修改文件立即生效。

缺点:改动了服务。

3. 使用自定义的类加载器,加载特定class文件。

如果需要我们不动任何的生产环境代码,不能重启呢?或者 我们只是增加一个类,查询下某些信息即可,要以重启环境为代价吗?

第三种方法,即可解决此问题。

自定义一个类加载器,继承自 ClassLoader,根据传入的class文件字节流,加载类。

public class HotSwapClassLoader extends ClassLoader {
	public HotSwapClassLoader(){
		super(HotSwapClassLoader.class.getClassLoader());
	}

	public Class loadByte(byte[] classByte){
		return defineClass(null,classByte,0,classByte.length);
	}
}
JavaClassExecuter类 根据传入的字节流,替换输入输出的打印位置,方便将输出结果打印到自己需要的位置,而不在项目日志中打印。
public class JavaClassExecuter {
	public static String execute(byte[] classByte){
		HackSystem.clearBuffer();
		ClassModifier cm = new ClassModifier(classByte);
		Package pack =  HackSystem.class.getPackage();
		String packName=pack.getName().replaceAll("\\.","/");
		byte[] modiBytes = cm.modifyUTF8Constant("java/lang/System",packName +"/HackSystem");
		HotSwapClassLoader loader = new HotSwapClassLoader();
		String errmsg = "no error";
		try {
			Class clazz = loader.loadByte(modiBytes);
			Method method =clazz.getMethod("main",new Class[] {String[].class});
			method.invoke(null,new String[]{null});
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
			errmsg = e.getMessage();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
			errmsg = e.getMessage();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
			errmsg = e.getMessage();
		}
		if("".equals(errmsg)){
			return "error:" + errmsg;
		} else {
			return HackSystem.getBufferString();
		}
	}

}
HackSystem类,输入输出流方法。
public class HackSystem {
	public final static InputStream in = System.in;
	private static ByteArrayOutputStream buffer = new ByteArrayOutputStream();
	public final static PrintStream out =new PrintStream(buffer);
	public final  static PrintStream err = out;

	public static String getBufferString(){
		return buffer.toString();
	}

	public static void clearBuffer(){
		buffer.reset();
	}

	public static void setSecurityManager(final SecurityManager s){
		System.setSecurityManager(s);
	}

	public static SecurityManager getSecurityManager(){
		return System.getSecurityManager();
	}

	public static long currentTimeMillis(){
		return System.currentTimeMillis();
	}

	public static  void arraycopy(Object src,int srcPos,Object dest,int destPos,int length){
		System.arraycopy(src,srcPos,dest,destPos,length);
	}

	public static int identityHashCode(Object x){
		return System.identityHashCode(x);
	}
}

 ClassModifier类,替换输入输出流类

public class ClassModifier {
	/**
	 * class文件中,常量池的起始偏移
	 */
	private static final int CONSTANT_POOL_COUNT_INDEX=8;

	/**
	 * CONSTANT_UTF-8_INFO 常量的tag标志
	 */
	private  static  final int CONSTANT_Utf8_info=1;
	/**
	 * 常量池中11种常量所占的长度,CONSTANT_UTF8_INFO 型常量除外,因为它不是定长的
	 */
	private static final int[] CONSTANT_ITEM_LENGTH = {-1,-1,-1,5,5,9,9,3,3,5,5,5,5};

	private static final int u1=1;
	private static final int u2=2;

	private byte[] classByte;

	public ClassModifier(byte[] classByte){
		this.classByte = classByte;
	}

	/**
	 * 修改常量池中CONSTANT_UTF8_INFO常量的内容
	 */
	public byte[] modifyUTF8Constant(String oldStr,String newStr){
		int cpc = getConstantPoolCount();
		int offset=CONSTANT_POOL_COUNT_INDEX+u2;
		for (int i = 0; i<cpc;i++){
			int tag =ByteUtils.bytes2Int(classByte,offset,u1);
			if(tag == CONSTANT_Utf8_info){
				int len = ByteUtils.bytes2Int(classByte,offset+u1,u2);
				offset +=(u1+u2);
				String str = ByteUtils.bytes2String(classByte,offset,len);
				if(str.equalsIgnoreCase(oldStr)){
					byte[] strBytes=ByteUtils.string2Bytes(newStr);
					byte[] strLen = ByteUtils.int2Bytes(newStr.length(),u2);
					classByte = ByteUtils.bytesReplace(classByte,offset - u2,u2,strLen);
					classByte = ByteUtils.bytesReplace(classByte,offset ,len,strBytes);
					return classByte;
				} else {
					offset +=len;
				}
			}else{
				offset += CONSTANT_ITEM_LENGTH[tag];
			}
		}
		return classByte;
	}

	/**
	 * 获取常量池中常量的数量
	 * @return
	 */
	public int getConstantPoolCount(){
		return ByteUtils.bytes2Int(classByte,CONSTANT_POOL_COUNT_INDEX,u2);
	}
}
ByteUtils 数据处理工具
public class ByteUtils {
	public  static  int bytes2Int(byte[] b,int start,int len){
		int sum =0;
		int end=start + len;
		for(int i=start;i<end;i++){
			int n=((int)b[i]) & 0xff;
			n<<=(--len) * 8;
			sum = n+sum;
		}
		return sum;
	}

	public  static  byte[] int2Bytes(int value,int len){
		byte[] b = new byte[len];
		for(int i=0;i<len;i++){
			b[len-i-1] =(byte) ((value>>8*i) &0xff);
		}
		return b;
	}

	public static String bytes2String(byte[] b,int start,int len){
		return new String(b,start,len);
	}

	public static  byte[] string2Bytes(String str){
		return str.getBytes();
	}

	public static byte[] bytesReplace(byte[] originalBytes,int offset,int len,byte[] replaceBytes){
		byte[] newBytes = new byte[originalBytes.length + (replaceBytes.length - len)];
		System.arraycopy(originalBytes,0,newBytes,0,offset);
		System.arraycopy(replaceBytes,0,newBytes,offset,replaceBytes.length);
		System.arraycopy(originalBytes,offset+len,newBytes,offset+replaceBytes.length,originalBytes.length-offset-len);
		return newBytes;
	}
}

 

Java类写好后,写一个jsp文件

<%@ page import="java.lang.*" %>
<%@ page import="java.io.*" %>
<%@ page import="com.dong.remoteExecute.*" %>

<%
    InputStream is = new FileInputStream("D:\\down\\RemoteExecuterTest.class");
    byte[] b = new byte[is.available()];
    is.read(b);
    is.close();

    out.println("<textarea style='width:1000px;height : 800px' >" );
    out.println("here is result:");
    out.println(JavaClassExecuter.execute(b));
    out.println("</textarea>");
%>

jsp中读取 一个 特定的 class文件(示例中的 RemoteExecuterTest),即我们需要执行的文件,位置不限,因为我们只需要找到文件并读文件流;jsp文件放到我们项目的页面部署位置其他class文件放到项目中class的目录下。

读取class文件字节流后,将字节流传入 JavaClassExecuter 类,进行 输入输出流的替换,类加载,反射调用,返回结果。

这样,即不影响生产环境,也不用重启,连输出结果都不会影响日志文件,毫无痕迹。且类在用完后还可以被回收。

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值