What pits are encountered in JAVA distributed development

Keywords: Redis Spring JDBC Lombok

1. Trampling Records in Spring Use

image
  • Spring uses multiple data sources through annotations

Pit: @Autowire is automatically injected by byType, while @Resource is automatically injected by byName by default, @Primary is the preferred choice.

For example, in a project there are two Redis sources, dataRedisTemplate and redisTemplate, respectively.

Redis Bean1: dataRedisTemplate, clusterNodes is ${data-redis.cluster.nodes}

@Bean(name = "dataRedisTemplate")
public RedisTemplate dataRedisTemplate() {
    RedisTemplate template = new RedisTemplate();
    template.setConnectionFactory(sessionLettuceConnectionFactory);
    Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
    ObjectMapper om = new ObjectMapper();
    om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    jackson2JsonRedisSerializer.setObjectMapper(om);
    template.setKeySerializer(new StringRedisSerializer());
    template.setValueSerializer(jackson2JsonRedisSerializer);
    template.setHashKeySerializer(jackson2JsonRedisSerializer);
    template.setHashValueSerializer(new StringRedisSerializer());
    template.afterPropertiesSet();
    return template;
}

// factory
@Resource
@Qualifier(value = "dataLettuceConnectionFactory")
private RedisConnectionFactory dataLettuceConnectionFactory;

// clusterNodes
@Value("${spring.data-redis.cluster.nodes}")
private String clusterNodes;

Redis Bean2: redisTemplate, cluster Nodes: ${redis.cluster.nodes}

@Primary
@Bean(name = "redisTemplate")
public RedisTemplate<String, Object> redisTemplate() {
    RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
    redisTemplate.setConnectionFactory(lettuceConnectionFactory);
    redisTemplate.setKeySerializer(new StringRedisSerializer());
    Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
    redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
    redisTemplate.setHashKeySerializer(new StringRedisSerializer());
    redisTemplate.setHashValueSerializer(new StringRedisSerializer());
    redisTemplate.afterPropertiesSet();
    return redisTemplate;
}

// factory
@Resource
@Qualifier(value = "lettuceConnectionFactory")
private RedisConnectionFactory lettuceConnectionFactory;

// clusterNodes
@Value("${spring.redis.cluster.nodes}")
private String clusterNodes;

In an application, I put data into Redis corresponding to "Redis Bean1: data Redis Template", so I use it in the following way:

@Autowired
private RedisTemplate dataRedisTemplate;

// Obtain data according to key
Object obj = dataRedisTemplate.opsForValue().get(key);

In fact, Redis Bean2: redisTemplate is used.
Solution 1: Replace @Autowire with @Resource annotation. As follows:

@Autowired
private RedisTemplate dataRedisTemplate;

// Replace @Autowire with @Resource

@Resource
private RedisTemplate dataRedisTemplate;

@ The biggest difference between Autowire and @Resource is that @Autowire is automatically injected by byType, while @Resource is automatically injected by byName by default.

A note @Primary should also be noted here. The official explanation is as follows:

Indicates that a bean should be given preference when multiple candidates are qualified to autowire a single-valued dependency. If exactly one 'primary' bean exists among the candidates, it will be the autowired value.

@Primary Priority scheme, annotated implementation, priority injection

Usually @Autowired is injected by byType, but when there are multiple implementation classes, byType is no longer the only way to inject, but it needs to be injected by byName, which by default is based on the variable name.

That is to say, if you don't add @Primary to redisTemplate(), there's no problem because @Autowire will be injected through byName when there are multiple implementations, but as mentioned above, Bean redisTemplate will be preferred because of the @Primary,@Autowire annotation.

Another solution is to add @Qualifier (value = data RedisTemplate), as follows:

@Autowired
private RedisTemplate dataRedisTemplate;

// Replace: Add @Qualifier (value = data RedisTemplate)

@Autowired
@Qualifier(value = "dataRedisTemplate")
private RedisTemplate dataRedisTemplate;
  • Spring transaction @Transactional failure problem

Pit: If other methods in the same class without @Transactional annotations call methods with @Transactional annotations internally, transactions of methods with @Transactional annotations are ignored and no rollback occurs.

FooService.class

public interface FooService {
    void insertRecord();
    void insertThenRollback() throws Exception;
    void invokeInsertThenRollback() throws Exception;
    void invokeInsertThenRollbackTwo() throws Exception;
}

FooServiceImpl.class

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Component
public class FooServiceImpl implements FooService {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Autowired
    private FooService fooService;

    @Override
    @Transactional
    public void insertRecord() {
        jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('AAA')");
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void insertThenRollback() throws Exception {
        jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('BBB')");
        throw new Exception();
    }

    @Override
    public void invokeInsertThenRollback() throws Exception {
        insertThenRollback();
    }

    @Override
    public void invokeInsertThenRollbackTwo() throws Exception {
        fooService.insertThenRollback();
    }
}

implement

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.AdviceMode;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@SpringBootApplication
@EnableTransactionManagement(mode = AdviceMode.PROXY)
@Slf4j
public class DeclarativeTransactionDemoApplication implements CommandLineRunner {
    @Autowired
    private FooService fooService;
    @Autowired
    private JdbcTemplate jdbcTemplate;

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

    @Override
    public void run(String... args) throws Exception {
        // Markup 1: Output AAA 1
        fooService.insertRecord();
        log.info("AAA {}",jdbcTemplate.queryForObject("SELECT COUNT(*) FROM FOO WHERE BAR='AAA'", Long.class));
        
        // Markup 2: Output BBB 0, transaction takes effect        
        try {
            fooService.insertThenRollback();
        } catch (Exception e) {
            log.info("BBB {}",jdbcTemplate.queryForObject("SELECT COUNT(*) FROM FOO WHERE BAR='BBB'", Long.class));
        }

        // Markup 3: Output BBB 1, transaction not valid
        // This place is the easiest place to step on the pit!!! * * *        
        try {
            fooService.invokeInsertThenRollback();
        } catch (Exception e) {
            log.info("BBB {}",jdbcTemplate.queryForObject("SELECT COUNT(*) FROM FOO WHERE BAR='BBB'", Long.class));
        }

        // Markup 4: Output BBB 1, transaction takes effect (if the code of Markup 3 is commented out, output BBB 0)
        // *** This is a way to avoid stepping on pits***        
        try {
            fooService.invokeInsertThenRollbackTwo();
        } catch (Exception e) {
            log.info("BBB {}",jdbcTemplate.queryForObject("SELECT COUNT(*) FROM FOO WHERE BAR='BBB'", Long.class));
        }
    }
}

2. Records of trampling pits during the use of RocketMQ

image
  • Rocket opens the VIP channel by default, causing the 10909 failed problem
    Pit: Rocket opens the VIP channel by default, and the VIP channel port is 10911-2 = 10909. If the Rocket server does not start port 10909, then report connect to <:10909> failed.

Solution: No VIP channel.

producer.setVipChannelEnabled(false);
consumer.setVipChannelEnabled(false);
  • The Rocket instanceName parameter is not configured, resulting in repeated consumption problems

Pit: One is that if Rocket does not configure instance Name, it will use pid to do instance Name. If instance Name is the same, it will repeat consumption, because cluster consumption mode is based on instance Name as the only consumption instance.

Looking at the source code, if no instance Name is specified, pid will be treated as instance Name by default, as follows:

if (this.instanceName.equals("DEFAULT")) {
    this.instanceName = String.valueOf(UtilAll.getPid());
}

public static int getPid() {
    RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean();
    String name = runtime.getName(); // format: "pid@hostname"
    try {
        return Integer.parseInt(name.substring(0, name.indexOf('@')));
    }
    catch (Exception e) {
        return -1;
    }
}

Solution: Operations configuration has $MQ_INSTANCE_NAME environment variable, different machines are different, so you can use: mq. consumer. instance Name: ${MQ_INSTANCE_NAME: default value} to configure.

@Value("${rocketmq.consumer.instanceName:${MQ_INSTANCE_NAME:fota}}")
private String clientInstanceName;

consumer.setInstanceName(clientInstanceName);

Posted by gabo0303 on Sat, 27 Apr 2019 05:30:36 -0700