In the previous section, we installed the GM tool locally: A step-by-step guide to installing GraphicsMagick locally. This chapter looks at how to integrate the tool into your project development.

In what way?

JNI/Command Line (IM4Java)

In the im4Java website:

Translation:Running native code from within Java using JNI always poses additional risks, especially for long-running processes (typically Web application servers). Memory corruption or segmentation errors, which can be triggered by deliberately manipulated images, can bring down the entire server.

So we choose to call from the command line.

Project integration

1. Introduce the GM command line tool into the project

When SpringBoot integrates Linux executables, we put the executables in the project’s resource directory:

One step is to copy the file to the host:

    private void initGM(a) throws Exception {
        String osName = System.getProperty("os.name").toLowerCase();
        log.info("os name: {}", osName);
        String gmPath;
        if (osName.contains("mac")) {
            gmPath = "gm/mac/gm";
        } else if (osName.contains("linux")) {
            // Initialize the container's environment
            initPodEnv();
            gmPath = "gm/linux/gm";
        } else {
            throw new RuntimeException("Illegal operating system:"+osName);
        }
        InputStream fisInJar = new ClassPathResource(gmPath).getInputStream();
        File file = File.createTempFile("GraphicsMagick"."_gm");
        file.setExecutable(true);
        GM_PATH = file.getAbsolutePath();
        // Copy the gm from the jar to the operating system directory
        OutputStream fosInOs = new FileOutputStream(file);
        byte[] buffer = new byte[1024];
        int readLength = fisInJar.read(buffer);
        while(readLength ! = -1) {
            fosInOs.write(buffer, 0, readLength);
            readLength = fisInJar.read(buffer);
        }
        IOUtils.closeQuietly(fosInOs);
        IOUtils.closeQuietly(fisInJar);
        log.info("Gm initialization completed");
    }
Copy the code

2. Automatically initialize the environment at project startup

The following is only for Linux automatic environment installation, MAC environment is mainly local development, you can install the environment:

    /** * Initializes the container's environment ** installs the libraries that the GM depends on */
    private void initPodEnv(a) throws Exception {
        log.info("============ start init pod env ============");
        Process exec1 = Runtime.getRuntime().exec("yum install -y gcc make");
        this.printLog(exec1);
        log.info("cmd 1 exec success");
       
        Process exec2 = Runtime.getRuntime().exec("yum install -y libpng-devel libjpeg-devel libtiff-devel jasper-devel freetype-devel libtool-ltdl-devel*");
        this.printLog(exec2);
        log.info("cmd 2 exec success");
        // Lack of dependencies for watermarking
        Process exec3 = Runtime.getRuntime().exec("yum -y install ghostscript");
        this.printLog(exec3);
        log.info("cmd 3 exec success");
        log.info("============ init pod env success ============");
    }
Copy the code

3. Gm process pooling

Imagine having to fork the GM process every time an image is processed, which is not only expensive, but also in a high concurrency situation, can easily result in too many child processes, resulting in high system load and frequent context switches.

So it is necessary to pool the GM process.

Prerequisite: The GM provides batch batch mode. In this mode, the GM process reads the standard input and receives commands line by line for real-time processing.

Each time you want to run a command, select a sub-process from the sub-process pool, perform image processing, and return the connection after processing.

Specific structure:

/** * GM process pool parameters */
@ConfigurationProperties(prefix = "gm.pool")
@Data
public class GMPoolProperties {

    /** * Maximum number of active connections */
    private int maxActive = 4;

    /** * Maximum number of free connections in the connection pool */
    private int maxIdle = 4;

    /** * Minimum number of free connections in the connection pool */
    private int minIdle = 2;

    /** * Minimum idle time (in milliseconds) for resources in the resource pool. When this value is reached, idle resources will be moved */
    private long minEvictableIdleTimeMillis = 300000L;

    /** * The action performed when the pool connections are exhausted */
    private WhenExhaustedAction whenExhaustedAction = WhenExhaustedAction.BLOCK;

    /** * Maximum wait time when no object is returned from the connection pool (ms) */
    private long maxWait = 5000;

    /** * Periodically validates idle links in the thread pool */
    private boolean testWhileIdle = false;

    /** * Detection interval of idle resources (milliseconds) */
    private long timeBetweenEvictionRunsMillis = 10000L;

}
Copy the code

At the beginning of the performance test

1, single thread test: single thread loop 100 times

technology Time consuming The average time
GraphicsMagick + im4java 2110 ms 21 ms
GraphicsMagick + IM4Java + pooling technology 1478 ms 15 ms
Bottom line: Performance improved by about 29%

2, multi-threaded concurrent test: concurrent 100 thread requests

technology Time consuming The average time
GraphicsMagick + im4java 37901 ms 379 ms
GraphicsMagick + IM4Java + pooling technology 22456 ms 224 ms
Bottom line: Approximately 41% performance improvement

Write in the last

The current mainstream is to use OpenReSTY (Nginx + Lua) to build image processing services, which may be poor in Java. Because I am familiar with Java technology stack, I will use Java implementation first.

The demo version of this article has been uploaded to Github. If you are interested, go to github.com/Shanbw/Grap…