字节码插桩听起来高大上,实际是通过无侵入方式修改字节码,通常干的还是动态代理的事,有两种方式。
java进程启动的时候指定agent (-javaagent:demo-agent-1.0-SNAPSHOT.jar),方法入口名必须是premain,在类加载阶段修改class,实现代理。
以下是alibaba TTL库agent的入口示例。
public static void premain(final String agentArgs, @NonNull final Instrumentation inst) { try { final List transformletList = new ArrayList<>(); transformletList.add(new TtlExecutorTransformlet(disableInheritableForThreadPool)); transformletList.add(new TtlPriorityBlockingQueueTransformlet()); transformletList.add(new TtlForkJoinTransformlet(disableInheritableForThreadPool)); if (isEnableTimerTask()) transformletList.add(new TtlTimerTaskTransformlet());//实现字节码替换 final ClassFileTransformer transformer = new TtlTransformer(transformletList); inst.addTransformer(transformer, true); } catch (Exception e) { } } attach模式是在java进程已经启动,然后再加载agent。
public static void main(String[] args) { VirtualMachine vm = null; try { //进程ID vm = VirtualMachine.attach("76293"); //java agent jar包路径 vm.loadAgent("gent-demo-jar-with-dependencies.jar"); } catch (Exception e) { e.printStackTrace(); }}agent入口方法必须是agentmain
public static void agentmain(String agentArgs, Instrumentation inst)由于java进程启动后,类已经加载完成,要想实现动态修改,必须重新加载类。如下
public static void agentmain(String agentArgs, Instrumentation inst) { System.out.println("loading dynamic agent ..."); MyClassFileTransformer tf = new MyClassFileTransformer(); inst.addTransformer(tf,true); // 要传递true,否则不生效 if(inst.isRetransformClassesSupported()){ Class<?>[] classes = inst.getAllLoadedClasses(); for (Class<?> c : classes) { if (c.getName().contains("TestDemo")) { try { inst.retransformClasses(c); } catch (UnmodifiableClassException e) { e.printStackTrace(); } break; } } }}灰度的核心是流量染色,把带有指定版本的请求,路由到对应版本的服务上。所以我们要做两件事:
第一把版本透传(类似链路跟踪)
第二服务路由的时候,路由到指定的版本服务。
agent实现思路如下(通过bytebuddy实现):
1.要拦截HTTP请求,然后把请求头的版本信息放到ThreadLocal。
public class ServletAdvice { @Advice.OnMethodEnter() public static void enter(@Advice.Argument(value = 0, readOnly = false, typing = Assigner.Typing.DYNAMIC) Object req, @Advice.Argument(value = 1, readOnly = false, typing = Assigner.Typing.DYNAMIC) Object resp) { HttpServletRequest request = (HttpServletRequest) req; String tag = request.getHeader(TagContext.TAG); if (StringUtils.isNotEmpty(tag)) { System.out.println("有对应的tag标记" + tag); TagContext.setTag(tag); } } @Advice.OnMethodExit(onThrowable = Throwable.class) public static void exit() { TagContext.remove(); }}2.feign服务调用的时候,服务列表只返回对应版本的服务。
public class NacosBalancerAdvice { @Advice.OnMethodExit private static void filterServer(@Advice.Return(readOnly = false) List instances) { if (instances == null) { return; } String tag = TagContext.getTag(); if (StringUtils.isEmpty(tag)) { return; } List newList = new ArrayList<>(); for (Server server : instances) { Map metadata = ((NacosServer) server).getMetadata(); if (metadata == null) { continue; } if (tag.equals(metadata.get(TagContext.TAG))) { newList.add(server); } } //发现有对应tag的服务 if(!newList.isEmpty()){ instances = newList; } }} 3.feign的Http请求发出去之前,把获取到的版本信息放到Header里面,进行透传。
public class URLConnectionAdvice { @Advice.OnMethodExit(onThrowable = Throwable.class) public static void exit(@Advice.Return(readOnly = false) HttpURLConnection urlConnection) { System.out.println("url connection"); String tag = TagContext.getTag(); if (StringUtils.isNotEmpty(tag)) { // log.info("透传tag{}", tag); System.out.println("透传tag:" + tag); urlConnection.addRequestProperty(TagContext.TAG, tag); } }} | 留言与评论(共有 0 条评论) “” |