0

I have following code:

@SpringBootApplication
@Component
public class ProductApiApplication2  implements CommandLineRunner{

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public void run(String... args) throws Exception {

        for (int i=0;i<5;i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("start time:" + System.currentTimeMillis() / 1000L);
                    String value = stringRedisTemplate.opsForList().rightPop("aaa", 5, TimeUnit.SECONDS);
                    System.out.println("get value, time :" + System.currentTimeMillis() / 1000L);

                }
            }).start();
        }

    }

    public static void main(String[] args) {
        SpringApplication.run(ProductApiApplication2.class, args);
    }
}

build with Spring Boot and Spring Data Redis. I hope the threads will finish at the same time, but it was not. I got this output:

start 
start 
start 
start 
start 

get value, time :1493881455
get value, time :1493881460
get value, time :1493881466
get value, time :1493881472
get value, time :1493881477

It looks like the processing does not happen in parallel. Why is this?

Here is my config:

spring :
  profiles : dev-company
  redis :
    cluster :
      nodes :
        - 192.168.3.171:7001
        - 192.168.3.171:7002
        - 192.168.3.168:7003
        - 192.168.3.168:7004
        - 192.168.3.169:7005
        - 192.168.3.169:7006

the pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.4.5.RELEASE</version>
    </parent>

    <groupId>com.test</groupId>
    <artifactId>test</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>test</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.7</source>
                    <target>1.7</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
mp911de
  • 17,546
  • 2
  • 55
  • 95
Alex
  • 1

1 Answers1

0

TL;DR;

Stop writing own micro-benchmarks. Getting these things right is unbelievably hard.

Explanation

You're creating and starting your threads sequentially. While the first thread is already running, you're starting the next one and so on without having a synchronization point. Additionally, your code does not pre-warm connections. This means, depending on the pool state, your Redis client will either create new connections to the cluster nodes or reuse existing connections. Connection creation impacts the resulting times.

What you want is prepare all threads, start them, wait until a condition applies to start work on all threads at the same time.

Something like:

    final CountDownLatch latch = new CountDownLatch(1);

    for (int i=0;i<5;i++){
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {   
                    latch.await();
                } catch(Exception e) {
                }
                System.out.println("start time:" + System.currentTimeMillis() / 1000L);
                String value = stringRedisTemplate.opsForList().rightPop("aaa", 5, TimeUnit.SECONDS);
                System.out.println("get value, time :" + System.currentTimeMillis() / 1000L);

            }
        }).start();
    }

    Thread.sleep(500); // give threads enough time to initialize

    latch.countDown();

Synchronization is still poor, but in this case, at least the threads should start work at the same time im most cases (GC, machine speed excluded).

mp911de
  • 17,546
  • 2
  • 55
  • 95
  • thanks. but now the question is stringRedisTemplate.opsForList().rightPop("aaa", 5, TimeUnit.SECONDS) block all the threads – Alex May 04 '17 at 09:03
  • I totally missed that this is a blocking operation. Use `rightPop` without a timeout, better use five keys that are located each on a different cluster node. Redis itself is single-threaded therefore you won't see much throughput improvement if all requests hit the same node. – mp911de May 04 '17 at 10:26
  • use five keys has the same result , I found that org.springframework.data.redis.connection.ClusterCommandExecutor use an AsyncTaskExecutor to execute the redis command, but the AsyncTaskExecutor just has one thread on it's threadPool, – Alex May 04 '17 at 11:36