Using the plug-in mechanism of Hystrix, the world's hottest programmer learning route

Keywords: Back-end Programmer

Find expansion port

Take a closer look and see when the thread pool was created?

The entry is shown in the figure below. Each time a new HystrixCommand is created, the constructor of the parent class will be called:

As shown in the figure above, the initThreadPool will create a thread pool. It should be noted that the first argument here, threadPool, is the fifth formal parameter of the constructor. At present, all passed in are null. Why do you say this? Let's continue:

private static HystrixThreadPool initThreadPool(HystrixThreadPool fromConstructor, HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties.Setter threadPoolPropertiesDefaults) {
    if (fromConstructor == null) {
        //1 get the default implementation of HystrixThreadPool
        return HystrixThreadPool.Factory.getInstance(threadPoolKey, threadPoolPropertiesDefaults);
    } else {
       return fromConstructor;
    }
}

As we said above, the first argument is always null, so it will go one place here.

com.netflix.hystrix.HystrixThreadPool.Factory#getInstance
    
static HystrixThreadPool getInstance(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties.Setter propertiesBuilder) {
            String key = threadPoolKey.name();

            //1 this should find it for all but the first time
            HystrixThreadPool previouslyCached = threadPools.get(key);
            if (previouslyCached != null) {
                return previouslyCached;
            }

            //2 if we get here this is the first time so we need to initialize
            synchronized (HystrixThreadPool.class) {
                if (!threadPools.containsKey(key)) {
                    // 3
                    threadPools.put(key, new HystrixThreadPoolDefault(threadPoolKey, propertiesBuilder));
                }
            }
            return threadPools.get(key);
        }
  • At 1, the cache will be found. As mentioned earlier, go to the map and find the corresponding thread pool according to the key of the thread pool
  • 2 places, if not found, create
  • 3, new HystrixThreadPoolDefault, create thread pool

Let's move on to three places:

public HystrixThreadPoolDefault(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties.Setter propertiesDefaults) {
    // 1
    this.properties = HystrixPropertiesFactory.getThreadPoolProperties(threadPoolKey, propertiesDefaults);
    // 2
    HystrixConcurrencyStrategy concurrencyStrategy = HystrixPlugins.getInstance().getConcurrencyStrategy();
    // 3
    this.metrics = HystrixThreadPoolMetrics.getInstance(threadPoolKey,
            concurrencyStrategy.getThreadPool(threadPoolKey, properties),
            properties);
    // 4
    this.threadPool = this.metrics.getThreadPool();
   ...
}
  • 1. Obtain the default configuration of the thread pool, which is similar to that in the Setter mentioned earlier

  • At step 2, get an object of type hystrixcurrencystrategy from HystrixPlugins.getInstance() and save it to the local variable concurrency strategy

  • 3. Initialize metrics. The second parameter here is obtained by concurrencyStrategy.getThreadPool. This operation will actually create a thread pool.

com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy#getThreadPool
    
public ThreadPoolExecutor getThreadPool(final HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties threadPoolProperties) {
        final ThreadFactory threadFactory = getThreadFactory(threadPoolKey);
		...
        final int keepAliveTime = threadPoolProperties.keepAliveTimeMinutes().get();
        final int maxQueueSize = threadPoolProperties.maxQueueSize().get();

		...
        // 1 
        return new ThreadPoolExecutor(dynamicCoreSize, dynamicCoreSize, keepAliveTime, TimeUnit.MINUTES, workQueue, threadFactory);
        }
    }

In the first place above, the thread pool will be created. However, the default thread pool class of jdk is directly used to create it. What's the matter? The types are dead. Can't expand...

Discover the plug-in mechanism of hystrix

However, after looking back and taking a closer look, this getThreadPool is a method of the hystrixcurrencystrategy class, and this method is also an instance method.

The method cannot be changed. Can the instance be changed? Look at the previous code:

ok, then analyze:

    public HystrixConcurrencyStrategy getConcurrencyStrategy() {
        if (concurrencyStrategy.get() == null) {
            //1 check for an implementation from Archaius first
            Object impl = getPluginImplementation(HystrixConcurrencyStrategy.class);
            concurrencyStrategy.compareAndSet(null, (HystrixConcurrencyStrategy) impl);
        }
        return concurrencyStrategy.get();
    }

1. Get the implementation according to this class. It feels a little funny.

        // 1
        T p = getPluginImplementationViaProperties(pluginClass, dynamicProperties);
        if (p != null) return p;    
        // 2
        return findService(pluginClass, classLoader);
    }
  • 1. It was obtained from a dynamic attribute. Later, it was found that if Netflix Archaius was integrated, the attribute could be obtained dynamically, similar to a configuration center

  • 2. If not found earlier, it is the SPI mechanism of JDK.

    private static <T> T findService(
            Class<T> spi, 
            ClassLoader classLoader) throws ServiceConfigurationError {
        
        ServiceLoader<T> sl = ServiceLoader.load(spi,
                classLoader);
        for (T s : sl) {
            if (s != null)
                return s;
        }
        return null;
    }

That's easy to say. SPI, we can customize an implementation to replace the default one. hystrix does a good job and has good scalability.

Now that you know that you can customize the hystrix concurrency strategy, how do you customize it?

This class is an abstract class with the following methods:

getThreadPool
    
getBlockingQueue(int maxQueueSize) 
    
Callable<T> wrapCallable(Callable<T> callable)
    
getRequestVariable(final HystrixRequestVariableLifecycle<T> rv)     

It is said to be an abstract class, but there is no method we need to implement. All methods have default implementations. We only need to rewrite the methods that need to be overridden.

Here, I value the third method:

/**
 * Provides an opportunity to wrap/decorate a {@code Callable<T>} before execution.
 * <p>
 * This can be used to inject additional behavior such as copying of thread state (such as {@link ThreadLocal}).
 * <p>
 * <b>Default Implementation</b>
 * <p>
 * Pass-thru that does no wrapping.
 * 
 * @param callable
 *            {@code Callable<T>} to be executed via a {@link ThreadPoolExecutor}
 * @return {@code Callable<T>} either as a pass-thru or wrapping the one given
 */
public <T> Callable<T> wrapCallable(Callable<T> callable) {
    return callable;
}

The method annotation is as above. Let me briefly say that before execution, it provides an opportunity for you to wrap the label, that is, the label that will eventually be thrown into the thread pool for execution.

We can wrap the original callable. Before execution, save the threadlocal variable of the current thread, which is A, and then set it to callable; When the callable is executed, the threadlocal in our A can be used to replace the thread in the worker thread.

It's useless to say more. Let's look at the code directly here:

// 0
public class MyHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy {

    @Override
    public <T> Callable<T> wrapCallable(Callable<T> callable) {
        /**
         * 1 Gets the threadlocalmap of the current thread
         */
        Object currentThreadlocalMap = getCurrentThreadlocalMap();

        Callable<T> finalCallable = new Callable<T>() {
            // 2
            private Object callerThreadlocalMap = currentThreadlocalMap;
			// 3
            private Callable<T> targetCallable = callable;

            @Override
            public T call() throws Exception {
                /**
                 * 4 Save the original thread variables of the worker thread
                 */
                Object oldThreadlocalMapOfWorkThread = getCurrentThreadlocalMap();
                /**
                 *5 Set the thread variable of this thread to the thread variable of caller
                 */
                setCurrentThreadlocalMap(callerThreadlocalMap);

                try {
                    // 6
                    return targetCallable.call();
                }finally {
                    // 7
                    setCurrentThreadlocalMap(oldThreadlocalMapOfWorkThread);
                    log.info("restore work thread's threadlocal");
                }

            }
        };

        return finalCallable;
    }
  • At 0, a custom class inherits the hystrixcurrencystrategy and is ready to override its default wrap method
  • 1, get the threadlocal of the external thread
  • 2 and 3, which are already anonymous internal classes. Two field s are defined to store the threadlocal of the external thread in 1 and the callable to wrap
  • At this time, it is in the execution logic of the run method: save the thread local variables of the worker thread
  • 5, use the threadlocal of the external thread to cover its own
  • 6, call the real business logic
  • At 7, it is restored to the threadlocal of the thread itself

Code to get threadlocal of thread:

    private Object getCurrentThreadlocalMap() {
        Thread thread = Thread.currentThread();
        try {
            Field field = Thread.class.getDeclaredField("threadLocals");
            field.setAccessible(true);
            Object o = field.get(thread);
            return o;
        } catch (NoSuchFieldException | IllegalAccessException e) {
            log.error("{}",e);
        }
        return null;
    }

Code for setting threadlocal of thread:

private void setCurrentThreadlocalMap(Object newThreadLocalMap) {
    Thread thread = Thread.currentThread();
    try {
        Field field = Thread.class.getDeclaredField("threadLocals");
        field.setAccessible(true);
        field.set(thread,newThreadLocalMap);

    } catch (NoSuchFieldException | IllegalAccessException e) {
        log.error("{}",e);
    }
}

Information about plug-in mechanism

https://github.com/Netflix/Hystrix/wiki/Plugins

Operation effect

controller code

@RequestMapping("/")
public String hystrixOrder () {
    // 1
    SessionUtils.getSessionVOFromRedisAndPut2ThreadLocal();
    // 2
    SimpleHystrixCommand simpleHystrixCommand = new SimpleHystrixCommand(testService);
    String res = simpleHystrixCommand.execute();
    return res;
}
  • 1, set ThreadLocal variable
    public static UserVO getSessionVOFromRedisAndPut2ThreadLocal() {



    // 2
    SimpleHystrixCommand simpleHystrixCommand = new SimpleHystrixCommand(testService);
    String res = simpleHystrixCommand.execute();
    return res;
}
  • 1, set ThreadLocal variable
    public static UserVO getSessionVOFromRedisAndPut2ThreadLocal() {


Posted by tcarnes on Wed, 08 Sep 2021 13:41:13 -0700