亚洲精品中文免费|亚洲日韩中文字幕制服|久久精品亚洲免费|一本之道久久免费

      
      

            <dl id="hur0q"><div id="hur0q"></div></dl>

                一些可以顯著提高大型 Java 項目啟動速度的嘗試

                一些可以顯著提高大型 Java 項目啟動速度的嘗試

                我們線上的業(yè)務 jar 包基本上普遍比較龐大,動不動一個 jar 包上百 M,啟動時間在分鐘級,拖慢了我們在故障時快速擴容的響應。于是做了一些分析,看看 Java 程序啟動慢到底慢在哪里,如何去優(yōu)化,目前的效果是大部分大型應用啟動時間可以縮短 30%~50%

                主要有下面這些內容

                • 修改 async-profiler 源碼,只抓取啟動階段 main 線程的 wall 時間火焰圖( )
                • 重新實現 JarIndex( )
                • 結合 JarIndex 重新自定義類加載器,啟動提速 30%+( )
                • SpringBean 加載耗時 timeline 可視化分析( )
                • SpringBean 的可視化依賴分析( )
                • 基于依賴拓撲的 SpringBean 的異步加載( )

                無觀測不優(yōu)化

                秉承著無觀測不優(yōu)化的想法,首先我們要知道啟動慢到底慢在了哪里。我之前分享過很多次關于火焰圖的使用,結果很多人遇到問題就開始考慮火焰圖,但是一個啟動慢其實是一個時序問題,不是一個 hot CPU 熱點問題。很多時候慢,不一定是 cpu 占用過高,很有可能是等鎖、等 IO 或者傻傻的 sleep。

                在 Linux 中有一個殺手級的工具 bootchart 來分析 linux 內核啟動的問題,它把啟動過程中所有的 IO、CPU 占用情況都做了詳細的劃分,我們可以很清楚地看到各個時間段,時間耗在了哪里,基于這個 chart,你就可以看看哪些過程可以延后處理、異步處理等。

                在 Java 中,暫時沒有類似的工具,但是又想知道時間到底耗在了哪里要怎么做呢,至少大概知道耗在了什么地方。在生成熱點調用火焰圖的時候,我們通過 arthas 的幾個簡單的命令就可以生成,它底層用的是 async-profiler 這個開源項目,它的作者 apangin 做過一系列關于 jvm profiling 相關的分享,感興趣的同學可以去看看。

                async-profiler 底層原理簡介

                async-profiler 是一個非常強大的工具,使用 jvmti 技術來實現。它的 NB 之處在于它利用了 libjvm.so 中 JVM 內部的 API AsyncGetCallTrace 來獲取 Java 函數堆棧,精簡后的偽代碼如下:

                static bool vm_init(JavaVM *vm) { std::cout << "vm_init" << std::endl; // 從 libjvm.so 中獲取 AsyncGetCallTrace 的函數指針句柄 void *libjvm = dlopen("libjvm.so", RTLD_LAZY); _asyncGetCallTrace = (AsyncGetCallTrace) dlsym(libjvm, "AsyncGetCallTrace");}// 事件回調void recordSample(void *ucontext, uint64_t counter, jint event_type, Event *event) { std::cout << "Profiler::recordSample: " << std::endl; ASGCT_CallFrame frames[maxFramesToCapture]; ASGCT_CallTrace trace; trace.frames = frames; trace.env = getJNIEnv(g_jvm); // 調用 AsyncGetCallTrace 獲取堆棧 _asyncGetCallTrace(&trace, maxFramesToCapture, ucontext);}

                你可能要說獲取個堆棧還需要搞這么復雜,jstack 等工具不是實現得很好了嗎?其實不然。

                jstack 等工具獲取函數堆棧需要 jvm 進入到 safepoint,對于采樣非常頻繁的場景,會嚴重的影響 jvm 的性能,具體的原理不是本次內容的重點這里先不展開。

                async-profiler 除了可以生成熱點調用的火焰圖,它還提供了 Wall-clock profiling 的功能,這個功能其實就是固定時間采樣所有的線程(不管線程當前是 Running、Sleeping 還是 Blocked),它在文檔中也提到了,這種方式的 profiling 適合用來分析應用的啟動過程,我們姑且用這個不太精確的方式來粗略測量啟動階段耗時在了哪些函數里。

                但是這個工具會抓取所有的線程的堆棧,按這樣的方式抓取的 wall-clock 火焰圖沒法看,不信你看。

                就算你找到了 main 線程,在函數耗時算占比的時候也不太方便,我們關心的其實只是 main 線程(也就是加載 jar 包,執(zhí)行 spring 初始化的線程),于是我做了一些簡單的修改,讓 async-profiler 只取抓取 main 線程的堆棧。

                重新編譯運行

                java -agentpath:/path/to/libasyncProfiler.so=start,event=wall,interval=1ms,threads,file=profile.html-jar xxx.jar

                這樣生成的火焰圖就清爽多了,這樣就知道時間耗在了什么函數上。

                接下來就是分析這個 wall-clock 的火焰圖,點開幾個調用棧仔細分析,發(fā)現很多時間花費在類和資源文件查找和加載(挺失望的,java 連這部分都做不好)

                繼續(xù)分析代碼看看類加載在做什么。

                Java 垃圾般實現的類查找加載

                Java 地類加載不出意外最終都走到了 java.net.URLClassLoader#findClass 這里。

                這里的 ucp 指的是 URLClassPath,也就是 classpath 路徑的集合。對于 SpringBoot 的應用來說,classpath 已經在 META-INF 里寫清楚了。

                Spring-Boot-Classes: BOOT-INF/classes/Spring-Boot-Lib: BOOT-INF/lib/

                此次測試的程序 BOOT-INF/lib/ 有 300 多個依賴的 jar 包,當加載某個類時,除了 BOOT-INF/classes/ 之外 Java 居然要遍歷那 300 個 jar 包去查看這些 jar 包中是否包含某個類。

                我在 loader.getResource 上注入了一下打印,看看這些函數調用了多少次。

                可以看到太喪心病狂了,加載一個類,居然要調用 loader.getResource 去 jar 包中嘗試幾百次。我就按二分之一 150 來算,如果加載一萬個類,要調用這個函數 150W 次。

                請忽略源碼中的 LookupCache 特性,這個特性看起來是為了加速 jar 包查找的,但是這個特性看源碼是一個 oracle 商業(yè)版的才有的特性,在目前的 jdk 中是無法啟用的。(推測,如果理解不對請告知我)

                于是有了一些粗淺的想法,為何不告訴 java 這個類在那個 jar 里?做索引這么天然的想法為什么不實現。

                以下面為例,項目依賴三個 jar 包,foo.jar、bar.jar、baz.jar,其中分別包含了特定包名的類,理想情況下我們可以生成一個索引文件,如下所示。

                foo.jarcom/foo1com/foo2bar.jarcom/barcom/bar/barbarbaz.jarcom/baz

                這就是我們接下來要介紹的 JarIndex 技術。

                JarIndex 技術

                其實 Jar 在文件格式上是支持索引技術的,稱為 JarIndex,通過 jar -i 就可以在 META-INF/ 目錄下生成 INDEX.LIST 文件。別高興的太早,這個 JarIndex 目前無法真正起到作用,有下面幾個原因:

                • INDEX.LIST 文件生成不正確,尤其是目前最流行的 fatjar 中包含 jar 列表的情況
                • classloader 不支持(那不是白忙活嗎)

                首先來看 INDEX.LIST 文件生成不正確的問題,隨便拿一個 jar 文件,使用 jar -i 生成一下試試。

                JarIndex-Version: 1.0encloud-api_origin.jarBOOT-INFBOOT-INF/classesBOOT-INF/classes/comBOOT-INF/classes/com/encloud….META-INFMETA-INF/mavenMETA-INF/maven/com.encloudMETA-INF/maven/com.encloud/encloud-apiBOOT-INF/liborgorg/springframeworkorg/springframework/bootorg/springframework/boot/loaderorg/springframework/boot/loader/jarorg/springframework/boot/loader/dataorg/springframework/boot/loader/archiveorg/springframework/boot/loader/util

                可以看到在 BOOT-INF/lib 目錄中的類索引并沒有在這里生成,這里面可是有 300 多個 jar 包。

                同時生成不對的地方還有,org 目錄下只有文件夾并沒有 class 文件,org 這一行不應該在 INDEX.LIST 文件中。

                第二個缺陷才是最致命的,目前的 classloader 不支持 JarIndex 這個特性。

                所以我們要做兩個事情,生成正確的 JarIndex,同時修改 SpringBoot 的 classloader 讓其支持 JarIndex。

                生成正確的 JarIndex

                這個簡單,就是遍歷 jar 包里的類,將其所在的包名抽取出來。SpringBoot 應用有三個地方存放了 class:

                • BOOT-INF/classes
                • BOOT-INF/lib
                • jar 包根目錄下 org/springframework/boot/loader

                生成的時候需要考慮到上面的情況,剩下的就簡單了。遍歷這些目錄,將所有的包含 class 文件的包名過濾過來就行。

                大概生成的結果是:

                JarIndex-Version: 1.0encloud-api.jar/BOOT-INF/classescom/encloudcom/encloud/app/controllercom/encloud/app/controller/v2/org/springframework/boot/loaderorg/springframework/boot/loader/archiveorg/springframework/boot/loader/dataorg/springframework/boot/loader/jarorg/springframework/boot/loader/util/BOOT-INF/lib/spring-core-4.3.9.RELEASE.jarorg/springframework/asmorg/springframework/cgliborg/springframework/cglib/beansorg/springframework/cglib/core/BOOT-INF/lib/guava-19.0.jarcom/google/common/annotationscom/google/common/basecom/google/common/base/internalcom/google/common/cache… other jar …

                除了加載類需要查找,其實還有不少資源文件需要查找,比如 spi 等掃描過程中需要,順帶把資源文件的索引也生成一下寫入到 RES_INDEX.LIST 中,原理類似,這里展開。

                自定義 classloder

                生成了 INDEX.LIST 文件,接下來就是要實現了一個 classloader 能支持一步到位通過索引文件去對應的 jar 包中去加載 class,核心的代碼如下:

                public class JarIndexLaunchedURLClassLoader extends URLClassLoader { public JarIndexLaunchedURLClassLoader(boolean exploded, Archive rootArchive, URL[] urls, ClassLoader parent) { super(urls, parent); initJarIndex(urls); // 根據 INDEX.LIST 創(chuàng)建包名到 jar 文件的映射關系 } @Override protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { Class loadedClass = findLoadedClass(name); if (loadedClass != null) return loadedClass; // 如果是 loader 相關的類,則直接加載,不用找了,就在 jar 包的根目錄下 if (name.startsWith(“org.springframework.boot.loader.”) || name.startsWith(“com.seewo.psd.bootx.loader.”)) { Class result = loadClassInLaunchedClassLoader(name); if (resolve) { resolveClass(result); } return result; } // skip java.*, org.w3c.dom.* com.sun.* ,這些包交給 java 默認的 classloader 去處理 if (!name.startsWith(“java”) && !name.contains(“org.w3c.dom.”) && !name.contains(“xml”) && !name.startsWith(“com.sun”)) { int lastDot = name.lastIndexOf(‘.’); if (lastDot >= 0) { String packageName = name.substring(0, lastDot); String packageEntryName = packageName.replace(‘.’, ‘/’); String path = name.replace(‘.’, ‘/’).concat(“.class”); // 通過 packageName 找到對應的 jar 包 List loaders = package2LoaderMap.get(packageEntryName); if (loaders != null) { for (JarFileResourceLoader loader : loaders) { ClassSpec classSpec = loader.getClassSpec(path); // 從 jar 包中讀取文件 if (classSpec == null) { continue; } // 文件存在,則加載這個 class Class definedClass = defineClass(name, classSpec.getBytes(), 0, classSpec.getBytes().length, classSpec.getCodeSource()); definePackageIfNecessary(name); return definedClass; } } } } // 執(zhí)行到這里,說明需要父類加載器來加載類(兜底) definePackageIfNecessary(name); return super.loadClass(name, resolve); }}

                到這里我們基本上就實現了一個支持 JarIndex 的類加載器,這里的改動經實測效果已經效果非常明顯。

                除此之外,我還發(fā)現查找一個已加載的類是一個非常高頻執(zhí)行的操作,于是可以在 JarIndexLaunchedURLClassLoader 之前再加一層緩存(思想來自 sofa-boot)

                public class CachedLaunchedURLClassLoader extends JarIndexLaunchedURLClassLoader { private final Map classCache = new ConcurrentHashMap(3000); @Override protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { return loadClassWithCache(name, resolve); } private Class loadClassWithCache(String name, boolean resolve) throws ClassNotFoundException { LoadClassResult result = classCache.get(name); if (result != null) { if (result.getEx() != null) { throw result.getEx(); } return result.getClazz(); } try { Class clazz = super.findLoadedClass(name); if (clazz == null) { clazz = super.loadClass(name, resolve); } if (clazz == null) { classCache.put(name, LoadClassResult.NOT_FOUND); } return clazz; } catch (ClassNotFoundException exception) { classCache.put(name, new LoadClassResult(exception)); throw exception; }}

                注意:這里為了簡單示例直接用 ConcurrentHashMap 來緩存 class,更好的做法是用 guava-cache 等可以帶過期淘汰的 map,避免類被永久緩存。

                如何不動 SpringBoot 的代碼實現 classloader 的替換

                接下的一個問題是如何不修改 SpringBoot 的情況下,把 SpringBoot 的 Classloader 替換為我們寫的呢?

                大家都知道,SpringBoot 的 jar 包啟動類其實并不是我們項目中寫的 main 函數,其實是

                org.springframework.boot.loader.JarLauncher,這個類才是真正的 jar 包的入口。

                package org.springframework.boot.loader;public class JarLauncher extends ExecutableArchiveLauncher {public static void main(String[] args) throws Exception {new JarLauncher().launch(args);}}

                那我們只要替換這個入口類就可以接管后面的流程了。如果只是替換那很簡單,修改生成好的 jar 包就可以了,但是這樣后面維護的成本比較高,如果在打包的時候就替換就好了。SpringBoot 的打包是用 spring-boot-maven-plugin 插件

                org.springframework.boot spring-boot-maven-plugin

                最終生成的 META-INF/MANIFEST.MF 文件如下

                $ cat META-INF/MANIFEST.MFManifest-Version: 1.0Implementation-Title: encloud-apiImplementation-Version: 2.0.0-SNAPSHOTArchiver-Version: Plexus ArchiverBuilt-By: arthurImplementation-Vendor-Id: com.encloudSpring-Boot-Version: 1.5.4.RELEASEImplementation-Vendor: Pivotal Software, Inc.Main-Class: org.springframework.boot.loader.JarLauncherStart-Class: com.encloud.APIBootSpring-Boot-Classes: BOOT-INF/classes/Spring-Boot-Lib: BOOT-INF/lib/Created-By: Apache Maven 3.8.5Build-Jdk: 1.8.0_332Implementation-URL: http://projects.spring.io/spring-boot/parent/enclo ud-api/

                為了實現我們的需求,就要看 spring-boot-maven-plugin 這個插件到底是如何寫入 Main-Class 這個類的,經過漫長的 maven 插件源碼的調試,發(fā)現這個插件居然提供了擴展點,可以支持修改 Main-Class,它提供了一個 layoutFactory 可以自定義

                org.springframework.boot spring-boot-maven-plugin repackage com.seewo.psd.bootx bootx-loader-tools 0.1.1

                實現這個

                package com.seewo.psd.bootx.loader.tools;import org.springframework.boot.loader.tools.*;import java.io.File;import java.io.IOException;import java.util.Locale;public class MyLayoutFactory implements LayoutFactory { private static final String NESTED_LOADER_JAR = “META-INF/loader/spring-boot-loader.jar”; private static final String NESTED_LOADER_JAR_BOOTX = “META-INF/loader/bootx-loader.jar”; public static class Jar implements RepackagingLayout, CustomLoaderLayout { @Override public void writeLoadedClasses(LoaderClassesWriter writer) throws IOException { // 拷貝 springboot loader 相關的文件到 jar 根目錄 writer.writeLoaderClasses(NESTED_LOADER_JAR); // 拷貝 bootx loader 相關的文件到 jar 根目錄 writer.writeLoaderClasses(NESTED_LOADER_JAR_BOOTX); } @Override public String getLauncherClassName() { // 替換為我們自己的 JarLauncher return “com.seewo.psd.bootx.loader.JarLauncher”; } }}

                接下來實現我們自己的 JarLauncher

                package com.seewo.psd.bootx.loader;import java.net.URL;public class JarLauncher extends org.springframework.boot.loader.JarLauncher { @Override protected ClassLoader createClassLoader(URL[] urls) throws Exception { return new CachedLaunchedURLClassLoader(urls, getClass().getClassLoader()); } public static void main(String[] args) throws Exception { new JarLauncher().launch(args); }}

                重新編譯就可以實現替換

                $ cat META-INF/MANIFEST.MFManifest-Version: 1.0…Main-Class: com.seewo.psd.bootx.loader.JarLauncher…

                到這里,我們就基本完成所有的工作,不用改一行業(yè)務代碼,只用改幾行 maven 打包腳本,就可以實現支持 JarIndex 的類加載實現。

                優(yōu)化效果

                我們來看下實際的效果,項目 1 稍微小型一點,啟動耗時從 70s 降低到 46s

                第二個 jar 包更大一點,效果更明顯,啟動耗時從 220s 減少到 123s

                未完待續(xù)

                其實優(yōu)化到這里,還遠遠沒有達到我想要的目標,為什么啟動需要這么長時間,解決了類查找的問題,那我們來深挖一下 Spring 的初始化。

                Spring bean 的初始化是串行進行的,于是我先來做一個可視化 timeline,看看到底是哪些 Bean 耗時很長。

                Spring Bean 初始化時序可視化

                因為不會寫前端,這里偷一下懶,利用 APM 的工具,把數據上報到 jaeger,這樣我們就可以得到一個包含調用關系的timeline 的界面了。jaeger 的網址在這里:www.jaegertracing.io/

                首先我們繼承 DefaultListableBeanFactory 來對 createBean 的過程做記錄。

                public class BeanLoadTimeCostBeanFactory extends DefaultListableBeanFactory { private static ThreadLocal parentStackThreadLocal = new ThreadLocal(); @Override protected Object createBean(String beanName, RootBeanDefinition rbd, Object[] args) throws BeanCreationException { // 記錄 bean 初始化開始 Object object = super.createBean(beanName, rbd, args); // 記錄 bean 初始化結束 return object; }

                接下來我們實現 ApplicationContextInitializer,在 initialize 方法中替換 beanFactory 為我們自己寫的。

                public class BeanLoadTimeCostApplicationContextInitializer implements ApplicationContextInitializer, Ordered { public BeanLoadCostApplicationContextInitializer() { System.out.println(“in BeanLoadCostApplicationContextInitializer()”); } @Override public void initialize(ConfigurableApplicationContext applicationContext) { if (applicationContext instanceof GenericApplicationContext) { System.out.println(“BeanLoadCostApplicationContextInitializer run”); BeanLoadTimeCostBeanFactory beanFactory = new BeanLoadTimeCostBeanFactory(); Field field = GenericApplicationContext.class.getDeclaredField(“beanFactory”); field.setAccessible(true); field.set(applicationContext, beanFactory); } }}

                接下來將記錄的狀態(tài)上報到 jaeger 中,實現可視化堆棧顯示。

                public void reportBeanCreateResult(BeanCreateResult beanCreateResult) { Span span = GlobalTracer.get().buildSpan(beanCreateResult.getBeanClassName()).withStartTimestamp(beanCreateResult.getBeanStartTime() * 1000).start(); try (Scope ignore = GlobalTracer.get().scopeManager().activate(span)) { for (BeanCreateResult item : beanCreateResult.getChildren()) { Span childSpan = GlobalTracer.get().buildSpan(item.getBeanClassName()).withStartTimestamp(item.getBeanStartTime() * 1000).start(); try (Scope ignore2 = GlobalTracer.get().scopeManager().activate(childSpan)) { printBeanStat(item); } finally { childSpan.finish(item.getBeanEndTime() * 1000); } } } finally { span.finish(beanCreateResult.getBeanEndTime() * 1000); }}

                通過這種方式,我們可以很輕松的看到 spring 啟動階段 bean 加載的 timeline,生成的圖如下所示。

                這對我們進一步優(yōu)化 bean 的加載提供了思路,可以看到 bean 的依賴關系和加載耗時具體耗在了哪個 bean。通過這種方式可以在 SpringBean 串行加載的前提下,把 bean 的加載盡可能的優(yōu)化。

                SpringBean 的依賴分析

                更好一點的方案是基于 SpringBean 的依賴關系做并行加載。這個特性 2011 年前就有人提給了 Spring,具體看這個 issue:github.com/spring-proj…

                就在去年,還有人去這個 issue 下去恭祝這個 issue 10 周年快樂。

                做并行加載確實有一些難度,真實項目的 Spring Bean 依賴關系非常復雜,我把 Spring Bean 的依賴關系導入到 neo4j 圖數據庫,然后進行查詢

                MATCH (n)RETURN n;

                得到的圖如下所示。一方面 Bean 的數量特別多,還有復雜的依賴關系,以及循環(huán)依賴。

                基于此依賴關系,我們是有機會去做 SpringBean 的并行加載的,這部分還沒實現,希望后面有機會可以完整的實現這塊的邏輯,個人感覺可以做到 10s 內啟動完一個超大的項目。

                Java 啟動優(yōu)化的其它技術

                Java 啟動的其它技術還有 Heap Archive、CDS,以及 GraalVM 的 AOT 編譯,不過這幾個技術目前都有各自的缺陷,還無法完全解決目前我們遇到的問題。

                后記

                這篇文章中用到的技術只是目前比較粗淺的嘗試,如果大家有更好的優(yōu)化,可以跟我交流,非常感謝。

                作者:挖坑的張師傅鏈接:https://juejin.cn/post/7117815437559070734

                鄭重聲明:本文內容及圖片均整理自互聯網,不代表本站立場,版權歸原作者所有,如有侵權請聯系管理員(admin#wlmqw.com)刪除。
                用戶投稿
                上一篇 2022年7月11日 21:09
                下一篇 2022年7月11日 21:09

                相關推薦

                • 我國首臺130噸級重復使用液氧煤油補燃循環(huán)發(fā)動機試車成功

                  新華社西安11月26日電記者26日從中國航天科技集團六院獲悉,由該院自主研制的首臺130噸級重復使用液氧煤油補燃循環(huán)發(fā)動機兩次起動試車取得圓滿成功。 該型發(fā)動機是瞄準我國新一代運載…

                  2022年11月27日
                • 世界領先!我國已應用于新一代戰(zhàn)機→

                  本文轉自【央視軍事】; “3D打印技術在飛機上的應用 我們已達到規(guī)?;⒐こ袒?處于世界領先位置” 如何運用3D打印設備 生產新一代戰(zhàn)機的零部件? 規(guī)模化+工程化 3D打印件批量裝…

                  2022年11月27日
                • 30個無加盟費的項目(茶顏悅色奶茶店加盟費多少)

                  茶顏悅色又爆了,8月18日,茶顏悅色南京門店正式開業(yè),開張不到半小時,門店就人滿為患,消費者的購買熱情十分高漲,而由于人流量過大造成擁堵,茶顏悅色也不得不暫停營業(yè)。 當然,這里面排…

                  2022年11月27日
                • 凈利潤率越高越好嗎(凈利潤率多少合適)

                  一、持續(xù)增收不增利,平均凈利潤率首次跌入個位數 2021年,增收不增利依舊是行業(yè)主流。具體來看,大部分企業(yè)營業(yè)收入呈增長態(tài)勢,E50企業(yè)平均同比增速達到17.3%,但是利潤增速則明…

                  2022年11月26日
                • 5+3疫情防控從哪天開始算(遼寧疫情防控最新政策)

                  最近有關國內各地的疫情大家也都有在持續(xù)關注,目前國內各地疫情隔離時間也根據二十條防控措施有了新的調整。那么,5+3疫情防控從哪天開始算?對于密接的5+3隔離時間計算大家還是比較關心…

                  2022年11月25日
                • 藍碼怎么變綠碼需要幾天(藍碼怎么變綠碼需要幾天)

                  大家都知道健康碼的顏色有紅碼、綠碼、黃碼,近日湖南健康碼上線“藍碼”,不少小伙伴發(fā)現自己健康碼變藍了,都想趕緊恢復綠碼,那么藍碼怎么變綠碼需要幾天?下面小編為大家?guī)硭{碼變綠碼需要…

                  2022年11月25日
                • 寶可夢朱紫野怪對應努力值表 野怪對應努力值查詢一覽圖

                  寶可夢朱紫野怪對應努力值是多少?不同的野怪對應的努力值不一樣,因此不少玩家對于野怪對應努力值的詳情不太了解,今天我們就來看一看野怪對應努力值表的具體內容,小編已經將詳情分享在下面,…

                  2022年11月25日
                • 規(guī)范透明促PPP高質量發(fā)展——16萬億元大市場迎來新規(guī)

                  近日,財政部印發(fā)《關于進一步推動政府和社會資本合作(PPP)規(guī)范發(fā)展、陽光運行的通知》,從做好項目前期論證、推動項目規(guī)范運作、嚴防隱性債務風險、保障項目陽光運行四個方面進一步規(guī)范P…

                  2022年11月25日
                • 拼多多百億補貼預售一般多久發(fā)貨(拼多多百億補貼預售)

                  拼多多里面有很多優(yōu)惠活動,其中百億補貼活動非?;鸨恍├锩娴臇|西價格比別的平臺便宜,質量也有保障,還有預售的活動,那么拼多多百億補貼預售一般多久發(fā)貨?下面小編為大家?guī)砥炊喽喟賰|…

                  2022年11月25日
                • 北京疫情多久能解除封控(北京疫情還要多久結束)

                  最近一段時間北京疫情形勢備受關注,馬上就要到年底了,不少人想要去北京辦事,。都非常關注當地疫情相關政策,那么 北京疫情多久能解除封控?北京疫情什么時候恢復正常生活?下面小編為大家?guī)А?/p>

                  2022年11月25日

                聯系我們

                聯系郵箱:admin#wlmqw.com
                工作時間:周一至周五,10:30-18:30,節(jié)假日休息