-
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 log
java.lang.NullPointException
The 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
- Check the database, there is a corresponding data, is not the problem of data
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 call
reload()
- When you perform
configs.clear();
After that, the Map is an empty object with no data - If there are more than one thread at this time
getString(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 to
getString(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