不管是單體應用仍是分佈式應用,老是會有些許迭代或者緊急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 文件了,哈哈哈哈哈……