Skip to content

Instantly share code, notes, and snippets.

@sdhjl2000
Created November 1, 2017 01:25
Show Gist options
  • Save sdhjl2000/b7830daeb483e762fac42ac903f9b51e to your computer and use it in GitHub Desktop.
Save sdhjl2000/b7830daeb483e762fac42ac903f9b51e to your computer and use it in GitHub Desktop.
aop
/**
* 拦截redisclient,可以自动把压测流量导入影子库。
*/
@Configuration
@ConditionalOnResource(resources = "/autoconfig/perftest.properties")
@ConditionalOnClass({RedisClient.class, TransmittableThreadLocal.class})
public static class RedisClientConfigurer {
private volatile Class<?> lastJedisConnectionProxyClass;
private static final Logger logger = LoggerFactory.getLogger(RedisClientConfigurer.class);
@Bean
public SpecifiedBeanPostProcessor perfTestRedisClientPostProcessor() {
return new SpecifiedBeanPostProcessor<RedisClient>() {
@Override
public int getOrder() {
return -2;
}
@Override
public Class<RedisClient> getBeanType() {
return RedisClient.class;
}
@Override
public Object postProcessBeforeInitialization(RedisClient bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(RedisClient bean, String beanName) throws BeansException {
try {
RedisClient rawbean= (RedisClient) AopTargetUtils.getTarget(bean);
Field field=ReflectionUtils.findField(rawbean.getClass(), "jedisPool");
ReflectionUtils.makeAccessible(field);
Pool<Jedis> jedisPool= (Pool<Jedis>)ReflectionUtils.getField(field,rawbean);
ProxyFactory factory = new ProxyFactory();
factory.addAdvice(new JedisPoolInterceptor());
factory.setTarget(jedisPool);
Pool<Jedis> newJedisPool = (Pool<Jedis>) factory.getProxy();
if (lastJedisConnectionProxyClass != null && lastJedisConnectionProxyClass != newJedisPool.getClass()) {
logger.error("newJedisPool is not same,this is spring's bug,please upgrade spring-boot's version to 1.3.8.RELEASE or higher! {},{},{},{}", new Object[]{getClass().getClassLoader(), newJedisPool.getClass().getClassLoader(), lastJedisConnectionProxyClass, newJedisPool.getClass()});//如果抱这个错,则有perm区内存溢出的风险,需要关注
}
lastJedisConnectionProxyClass = newJedisPool.getClass();
ReflectionUtils.setField(field,rawbean,newJedisPool);
return bean;
}catch (Exception ex){
logger.error(ex.getMessage(),ex);
return bean;
}
}
};
}
}
@Configuration
@ConditionalOnResource(resources = "/autoconfig/perftest.properties")
@ConditionalOnClass({TransmittableThreadLocal.class, Jedis.class,Aspect.class})
public static class JedisPerfConfiguration{
@Bean
public JedisPerfAspect getJedisPerfAspect(){
return new JedisPerfAspect();
}
}
package cn.com.duibaboot.ext.autoconfigure.perftest;
/**
* Created by hujinliang on 2017/10/27.
*/
import java.util.List;
import com.google.common.collect.ImmutableList;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import redis.clients.jedis.Jedis;
/**
* {@link MethodInterceptor}
*/
public class JedisMethodInterceptor implements MethodInterceptor {
public static final Integer PERFDB_INDEX=2;
private static final List<String> notInterceptorJedisMethodList = ImmutableList.<String>builder().add("get").add("hget")
.add("hgetAll").add("hmget").add("mget")
.add("hlen").add("llen").add("strlen")
.add("bitcount").add("zcount").add("pfcount")
.add("scan").add("hscan").add("sscan").add("zscan")
.add("keys").add("hkeys").add("ttl")
.build().asList();
JedisMethodInterceptor() {
}
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
String methodName = invocation.getMethod().getName();
//忽略Object基类中的方法
if (methodName.equals("toString") || methodName.equals("hashCode") || methodName.equals("equals")) {
return invocation.proceed();
}
if (PerfTestContext.isCurrentInPerfTestMode() && !notInterceptorJedisMethodList.contains(methodName)) {
((Jedis)invocation.getThis()).select(PERFDB_INDEX);
}
return invocation.proceed();
}
}
package cn.com.duibaboot.ext.autoconfigure.perftest;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import cn.com.duiba.wolf.redis.RedisClient;
import com.alibaba.ttl.TransmittableThreadLocal;
import com.google.common.collect.ImmutableList;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.jedis.JedisConnection;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import redis.clients.jedis.Jedis;
/**
* 加入AOP,spring-data-reids执行影子库
*/
public class JedisPoolInterceptor implements MethodInterceptor{
private static final Logger logger = LoggerFactory.getLogger(JedisPoolInterceptor.class);
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
String methodName = invocation.getMethod().getName();
//忽略Object基类中的方法
if (methodName.equals("getResource")) {
Jedis jedis=(Jedis) invocation.proceed();
ProxyFactory factory = new ProxyFactory();
factory.addAdvice(new JedisMethodInterceptor());
factory.setTarget(jedis);
Object newJedis = factory.getProxy();
return newJedis;
}
return invocation.proceed();
}
}
package cn.com.duibaboot.ext.autoconfigure.perftest;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import com.alibaba.ttl.TransmittableThreadLocal;
import com.google.common.collect.ImmutableList;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.jedis.JedisConnection;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
/**
* 加入AOP,spring-data-reids执行影子库
*/
@Aspect
public class JedisPerfAspect {
private static final List<String> notInterceptorConectionMethodList = ImmutableList.<String>builder().add("get").add("hGet")
.add("hGetAll").add("hMGet").add("mGet")
.add("hLen").add("lLen").add("strLen")
.add("bitCount").add("zCount")
.add("scan").add("hScan").add("sScan").add("zScan")
.add("keys").add("hKeys")
.build().asList();
private static final Logger logger = LoggerFactory.getLogger(JedisPerfAspect.class);
public static ConcurrentHashMap<String, JedisConnectionFactory> shadeDataSource = new ConcurrentHashMap<>();
private final TransmittableThreadLocal<RedisConnection> shadeConnection = new TransmittableThreadLocal<>();
private volatile Class<?> lastJedisConnectionProxyClass;
@Around("execution(* org.springframework.data.redis.connection.jedis.JedisConnectionFactory.getConnection(..))")
public Object springDataConnectionJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
String methodName = signature.getMethod().getName();
if ("getConnection".equals(methodName)) {
JedisConnectionFactory jedisConnectionFactory = (JedisConnectionFactory) joinPoint.getTarget();
JedisConnection connection = (JedisConnection) joinPoint.proceed();
ProxyFactory factory = new ProxyFactory();
factory.addAdvice(new JedisConnectionMethodInterceptor());
factory.setTarget(connection);
JedisConnection newConnection = (JedisConnection) factory.getProxy();//getClass().getClassLoader()
if (lastJedisConnectionProxyClass != null && lastJedisConnectionProxyClass != newConnection.getClass()) {
logger.error("JedisConnectionProxyClass is not same,this is spring's bug,please upgrade spring-boot's version to 1.3.8.RELEASE or higher! {},{},{},{}", new Object[]{getClass().getClassLoader(), newConnection.getClass().getClassLoader(), lastJedisConnectionProxyClass, newConnection.getClass()});//如果抱这个错,则有perm区内存溢出的风险,需要关注
}
lastJedisConnectionProxyClass = newConnection.getClass();
if (PerfTestContext.isCurrentInPerfTestMode()) {
String dbKey= jedisConnectionFactory.getHostName() + jedisConnectionFactory.getPort();
if(!shadeDataSource.containsKey(dbKey)){
JedisConnectionFactory shadeConnectionFactory = new JedisConnectionFactory();
BeanUtils.copyProperties(jedisConnectionFactory, shadeConnectionFactory);
shadeConnectionFactory.afterPropertiesSet();
shadeConnectionFactory.setDatabase(JedisMethodInterceptor.PERFDB_INDEX);
shadeDataSource.put(dbKey,shadeConnectionFactory);
}
shadeConnection.set(shadeDataSource.get(dbKey).getConnection());
}
return newConnection;
}
return joinPoint.proceed();
}
/**
* {@link MethodInterceptor}
*/
private class JedisConnectionMethodInterceptor implements MethodInterceptor {
JedisConnectionMethodInterceptor() {
}
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
String methodName = invocation.getMethod().getName();
if (methodName.equals("isPipelined") || methodName.equals("openPipeline") || methodName.equals("isQueueing")
|| methodName.equals("isClosed")
|| methodName.equals("close")
|| methodName.equals("closePipeline")) {
return invocation.proceed();
} else if (methodName.equals("getNativeConnection")) {
Object nativeConnection = invocation.proceed();
ProxyFactory factory = new ProxyFactory();
factory.addAdvice(new JedisMethodInterceptor());
factory.setTarget(nativeConnection);
Object newNativeConnection = factory.getProxy();//getClass().getClassLoader()
return newNativeConnection;
}
return interceptorRedisConnectionMethods(invocation);
}
}
private Object interceptorRedisConnectionMethods(MethodInvocation invocation) throws Throwable {
String methodName = invocation.getMethod().getName();
//忽略Object基类中的方法
if (methodName.equals("toString") || methodName.equals("hashCode") || methodName.equals("equals")) {
return invocation.proceed();
}
Object value = null;
if (PerfTestContext.isCurrentInPerfTestMode() && !notInterceptorConectionMethodList.contains(methodName)) {
value = invocation.getMethod().invoke(shadeConnection.get(), invocation.getArguments());
} else {
value = invocation.proceed();
}
return value;
}
}
package cn.com.duibaboot.ext.autoconfigure.perftest;
import static cn.com.duibaboot.ext.autoconfigure.perftest.PerfTestAutoConfiguration.TransmittableThreadLocalHolder.threadLocal2PressureTest;
import java.util.Properties;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* mybatis拦截器,压测时读流量走主库,写流量走影子库
* Created by hujinliang on 2017/9/8.
*/
@Intercepts({ @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class }) })
public class MybatisResetPerfTestInterceptor implements Interceptor {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public Object intercept(Invocation invocation) throws Throwable {
Boolean val = threadLocal2PressureTest.get();
if (val != null) {
threadLocal2PressureTest.remove();//删除压测标记,让后续读请求走主库
}
Object o = invocation.proceed();
if (val != null) {
threadLocal2PressureTest.set(val);//恢复压测标记
}
return o;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
public class AopTargetUtils {
/**
* 获取 目标对象
* @param proxy 代理对象
* @throws Exception
*/
public static Object getTarget(Object proxy) throws Exception {
if(!AopUtils.isAopProxy(proxy)) {
return proxy;//不是代理对象
}
if(AopUtils.isJdkDynamicProxy(proxy)) {
return getJdkDynamicProxyTargetObject(proxy);
} else { //cglib
return getCglibProxyTargetObject(proxy);
}
}
private static Object getCglibProxyTargetObject(Object proxy) throws Exception {
Field h = proxy.getClass().getDeclaredField("CGLIB$CALLBACK_0");
h.setAccessible(true);
Object dynamicAdvisedInterceptor = h.get(proxy);
Field advised = dynamicAdvisedInterceptor.getClass().getDeclaredField("advised");
advised.setAccessible(true);
return ((AdvisedSupport)advised.get(dynamicAdvisedInterceptor)).getTargetSource().getTarget();
}
private static Object getJdkDynamicProxyTargetObject(Object proxy) throws Exception {
Field h = proxy.getClass().getSuperclass().getDeclaredField("h");
h.setAccessible(true);
AopProxy aopProxy = (AopProxy) h.get(proxy);
Field advised = aopProxy.getClass().getDeclaredField("advised");
advised.setAccessible(true);
return ((AdvisedSupport)advised.get(aopProxy)).getTargetSource().getTarget();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment