不管是单体应用仍是分布式应用,老是会有些许迭代或者紧急Fix bug上线的神操做。可是若是不是那么幸运,当时还存在大量核心内存中数据在进行计算等逻辑,此时终止项目,就会出现核心数据或者状态丢失的不利状况,后续即便上线完成也要尽快追加数据。并发
那是否存在某种技巧???:在须要终止应用的时候,可以监听到终止操做,并保存核心数据现场,而后再终止应用,然后在应用恢复后,再进行核心数据恢复。 答案是确定的。
Runtime.getRuntime().addShutdownHook(Thread thread);
咱们能够借助于JDK为咱们所提供的上述钩子方法。这个方法的意思就是在JVM中增长一个关闭的钩子,当JVM关闭的时候,会执行系统中已经设置的全部经过方法addShutdownHook添加的钩子,当系统执行完这些钩子后,JVM才会关闭。因此这些钩子能够在JVM关闭的时候进行内存清理、对象销毁以及核心数据现场保存等操做。分布式
咱们应用程序运行中,在内存中存储着Map<String, User>(用户惟一标识符和用户信息的映射关系),此时,忽然须要紧急处理某个bug并打包上线。ide
用户映射关系已经创建好了,咱们总不能由于紧急上线就让用户从新登陆一次,只是为了构建这个映射关系???这样显然不是很合理,其次还有用户流失的风险,咱们怎么能够去冒着被大boss怒怼这般的大风险呢,搞很差年终奖尚未,哈哈哈哈哈……测试
那咱们换个思路,咱们要解决的问题是什么呢?由于Map<String, User>是在内存中保存的,一但应用终止,内存资源释放,内存中数据固然无存……因此,咱们的目标就是保存这个处于内存中的Map对象,对不对?那就简单了,咱们能够把这个对象序列化存储到本地文件里面不就行了吗?是否是很简单?而后呢,只须要在应用程序被终止前序列化且保存到本地文件,就能够了。操作系统
理好了思路,那就开始Coding吧!线程
private static final HashMap<String, User> cacheData = new HashMap<>(); private static final String filePath = System.getProperty("user.dir") + File.separator + "save_point.binary"; Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { saveData(); } }); private static void saveData() { ObjectOutputStream oos = null; try { File cacheFile = new File(filePath); if (!cacheFile.exists()) { cacheFile.createNewFile(); } oos = new ObjectOutputStream(new FileOutputStream(filePath)); oos.writeObject(cacheData); oos.flush(); } catch (IOException ex) { LOGGER.error("save memory data error", ex); } finally { try { if (oos != null) { oos.close(); } } catch (IOException ex) { LOGGER.error("close ObjectOutputStream error", ex); } } }
这样咱们就能够保证Map<String, User>这个映射关系保存好了。code
既然咱们保存了内存数据现场,那在应用启动后,咱们相应的也须要进行数据现场恢复,这样才能保证应用平滑过渡到终止前状态,同时用户还能无感知。对象
继续Coding...接口
@PostConstruct public void resoverData() { ObjectInputStream ois = null; try { File cacheFile = new File(filePath); if (cacheFile.exists()) { ois = new ObjectInputStream(new FileInputStream(filePath)); Map<String, User> cacheMap = (Map<String, User>) ois.readObject(); for (Map.Entry<String, User> entry : cacheMap.entrySet()) { cacheData.put(entry.getKey(), entry.getValue()); } LOGGER.info("Recover memory data successfully, cacheData={}" , cacheData.toString()); } } catch (Exception ex) { LOGGER.error("recover memory data error", ex); } finally { try { if (ois != null) { ois.close(); } } catch (IOException ex) { LOGGER.error("close ObjectInputStream error", ex); } } }
是否是整个过程似曾相识?没错,就是Java IO流 ObjectInputStream和ObjectOutputStream的应用。可是有一点须要注意,使用对象流的时候,须要保证被序列化的对象必须实现了Serializable接口,这样才能正常使用。内存
应用总体调用逻辑以下(测试的时候,第一次须要正常调用generateAndPutData()方法,终止项目保存现场后,须要把generateAndPutData()注释掉,看看时候正确恢复现场了。):
@SpringBootApplication public class SavePointApplication { private static final Logger LOGGER = LoggerFactory.getLogger(SavePointApplication.class); private static final HashMap<String, User> cacheData = new HashMap<>(); private static final String filePath = System.getProperty("user.dir") + File.separator + "save_point.binary"; public static void main(String[] args) { SpringApplication.run(SavePointApplication.class, args); LOGGER.info("save_point filePath={}", filePath); generateAndPutData(); Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { saveData(); } }); } private static void generateAndPutData() { cacheData.put("test1", new User(1L, "testName1")); cacheData.put("test2", new User(2L, "testName2")); cacheData.put("test3", new User(3L, "testName3")); }
为何应用程序终止时没有保存现场状态呢?那就要细说一下关闭钩子(shutdown hooks)了。
因此,若是咱们直接使用的kill -9 processId命令直接强制关闭的应用程序,JVM都被强制关闭了,还怎么运行咱们的Java代码呢?嘿嘿,因此咱们能够尝试着用以下命令替代kill -9 processId:
kill processId kill -2 processId kill -15 processId
经过上述命令进行终止应用的时候,是否是咱们看到咱们项目下成功生成了 save_point.binary 文件了,哈哈哈哈哈……