• background

    Are having afternoon tea in the afternoon on Thursday, while mobile phone (fair) brush, suddenly there is a customer service center of little sister seek for XXX operation failed again, but more a few times and no problem (also appeared before, but no exception handling code and log output is difficult to screen, can’t old code, written by former I didn’t also way, can only add Wait for the time of reappearance to see), look at the little sister anxious expression, afternoon tea instant not sweet, look for a bug!

  • The reasons causing

  • positioning

    Enter the account on the Rancher to find the corresponding service, according to the keyword to find the relevant logjava.lang.NullPointExceptionThe relevant code block is found following the number of error lines:

if (StringUtils.isNotEmpty(feeSetting.getFileId())){
    return schoolService.deal(sysConfigService.getString("url"));
}
Copy the code

The error is

schoolService.deal(sysConfigService.getString("url"));
Copy the code

To locate the problem, call String getString(String key); A null pointer.

  • Analysis of the

Related code:

public String getString(String key) {
    if (configs == null) {
        initConfig();
    }
    return configs.get(key);
}
Copy the code

InitConfig () :

private void initConfig() { synchronized (lock) { if ((configs == null) || configs.isEmpty()) { configs = new HashMap<String, String>(); // Load from db to configs loadSysConfig(); }}}Copy the code

Configs is a member variable

private static Map<String, String> configs = null;
Copy the code
  1. Check the database, there is a corresponding data, is not the problem of data
  2. getString(String key) No error is reported inside the interface, indicating that this program is not reported error

A null pointer is only possible if there is no corresponding data in the Map. The following code is found:

public void reload() { if ((configs ! = null) && ! configs.isEmpty()) { configs.clear(); this.initConfig(); }}Copy the code

There is only one place to call this method

@Component
public class SysConfgMQListener implements MessageListenerConcurrently {
   protected final Logger log = LoggerFactory.getLogger(SysConfgMQListener.class);

   @Autowired
   private ISysConfigService sysConfigService;


   @Override
   public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
      log.info("SysConfgMQListener retrieving...");
      for (MessageExt msg : msgs) {
         log.info("messageExt, body:{}", new String(msg.getBody()));
         this.sysConfigService.reload();
      }
      return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
   }

}
Copy the code

This is the consumer of RocketMq that’s called here, and it’s in broadcast mode, all nodes can consume, and this is the producer of Mq that is generated when the refresh is triggered in the background.

  • There’s only one truth

  • The consumption of Mq is triggered first, causing the Map to refresh and the reload callreload()
  • When you performconfigs.clear();After that, the Map is an empty object with no data
  • If there are more than one thread at this timegetString(String key)The obtained value is null
  • transform

  • The first thought was to replace the interface with Redis, but it was quickly self-deprecated. This interface worked fine for several years without triggering a refresh mechanism, and the base configuration with Redis was not easy to determine when the expiration time was set, and it required multiple I/OS to be passed, so the performance was not as good as the local Map.
  • The second solution that came to mind was togetString(String key)Method lock, this can only be a last resort
  • Is at a loss, suddenly a flash of light, this is not like the registry? Each client to pull data, and NACOS in order to high-performance is to use the idea of Copy on write to achieve, more want to more line, dry!

The code is modified as follows:

public void reload() { if ((configs ! = null) && ! Configs.isempty ()) {// the interface fetch requested between the two operations isEmpty // configs.isempty (); //this.initConfig(); this.reloadForConfigs(); }}Copy the code

The enclosing reloadForConfigs ();

private void reloadForConfigs() { Map<String, String> newConfigs = new HashMap<>(); try { List<Config> datas = configDao.listConfigs(); if (datas ! = null) { for (Config cf : datas) { newConfigs.put(cf.getKey(), cf.getValue()); } } } catch (Exception e) { LogUtil.exception(log, e); } the if (CollectionUtil isNotEmpty (newConfigs)) {/ / replace old enclosing configs = newConfigs; }}Copy the code

This transformation after the online, tracking for a period of time in the log did not find a null pointer (little sister also did not come to me – -, do not open sen), there is a little bit of a sense of achievement.

  • conclusion

  • Consider multi-threading and concurrent scenarios when developing
  • Don’t panic when you encounter problems, analyze them carefully
  • Good solutions don’t happen overnight
  • Read more good code such as the framework source code, continuous accumulation, now do not use, some time on the use