Nacos的單機模式啟動失敗問題及解決
背景
項目上使用Nacos做服務(wù)注冊與發(fā)現(xiàn),在開發(fā)環(huán)境中我們使用的容器化的Naocs部署,并且使用的單機節(jié)點以及內(nèi)置數(shù)據(jù)庫derby,但是最近在部署Nacos的服務(wù)的使用發(fā)現(xiàn)啟動失敗,報錯日志如下(日志很長,已經(jīng)截取了一些):
2021-09-18 22:40:31,822 INFO Tomcat initialized with port(s): 8848 (http)
2021-09-18 22:40:32,148 INFO Root WebApplicationContext: initialization completed in 11864 ms
2021-09-18 22:40:44,722 ERROR Error starting Tomcat context. Exception: org.springframework.beans.factory.BeanCreationException. Message: Error creating bean with name 'authFilterRegistration' defined in class path resource [com/alibaba/nacos/core/auth/AuthConfigs.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.boot.web.servlet.FilterRegistrationBean]: Factory method 'authFilterRegistration' threw exception; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'authFilter': Unsatisfied dependency expressed through field 'authManager'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'nacosAuthManager': Unsatisfied dependency expressed through field 'authenticationManager'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'nacosAuthConfig': Unsatisfied dependency expressed through field 'userDetailsService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'nacosUserDetailsServiceImpl': Unsatisfied dependency expressed through field 'userPersistService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'embeddedUserPersistServiceImpl': Unsatisfied dependency expressed through field 'databaseOperate'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'standaloneDatabaseOperateImpl': Invocation of init method failed; nested exception is java.lang.RuntimeException: com.alibaba.nacos.api.exception.runtime.NacosRuntimeException: errCode: 500, errMsg: load schema.sql error.
2021-09-18 22:40:44,827 WARN Exception encountered during context initialization - cancelling refresh attempt: org.springframework.context.ApplicationContextException: Unable to start web server; nested exception is org.springframework.boot.web.server.WebServerException: Unable to start embedded Tomcat
2021-09-18 22:40:44,837 INFO Nacos Log files: /home/nacos/logs
2021-09-18 22:40:44,849 INFO Nacos Log files: /home/nacos/conf
2021-09-18 22:40:44,853 INFO Nacos Log files: /home/nacos/data
2021-09-18 22:40:44,856 ERROR Startup errors : {}
org.springframework.context.ApplicationContextException: Unable to start web server; nested exception is org.springframework.boot.web.server.WebServerException: Unable to start embedded Tomcat
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:157)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:540)
...............
Caused by: com.alibaba.nacos.api.exception.runtime.NacosRuntimeException: errCode: 500, errMsg: load schema.sql error.
at com.alibaba.nacos.config.server.service.datasource.LocalDataSourceServiceImpl.reload(LocalDataSourceServiceImpl.java:101)
at com.alibaba.nacos.config.server.service.datasource.LocalDataSourceServiceImpl.initialize(LocalDataSourceServiceImpl.java:170)
at com.alibaba.nacos.config.server.service.datasource.LocalDataSourceServiceImpl.init(LocalDataSourceServiceImpl.java:83)
at com.alibaba.nacos.config.server.service.datasource.DynamicDataSource.getDataSource(DynamicDataSource.java:47)
... 166 common frames omitted
Caused by: java.sql.SQLTimeoutException: Login timeout exceeded.
at org.apache.derby.impl.jdbc.SQLExceptionFactory.getSQLException(Unknown Source)
at org.apache.derby.impl.jdbc.SQLExceptionFactory.getSQLException(Unknown Source)
at org.apache.derby.impl.jdbc.Util.generateCsSQLException(Unknown Source)
at org.apache.derby.impl.jdbc.Util.generateCsSQLException(Unknown Source)
at org.apache.derby.jdbc.InternalDriver.timeLogin(Unknown Source)
at org.apache.derby.jdbc.InternalDriver.connect(Unknown Source)
at org.apache.derby.jdbc.InternalDriver.connect(Unknown Source)
at org.apache.derby.jdbc.EmbeddedDriver.connect(Unknown Source)
at com.zaxxer.hikari.util.DriverDataSource.getConnection(DriverDataSource.java:138)
at com.zaxxer.hikari.pool.PoolBase.newConnection(PoolBase.java:354)
at com.zaxxer.hikari.pool.PoolBase.newPoolEntry(PoolBase.java:202)
at com.zaxxer.hikari.pool.HikariPool.createPoolEntry(HikariPool.java:473)
at com.zaxxer.hikari.pool.HikariPool.checkFailFast(HikariPool.java:554)
at com.zaxxer.hikari.pool.HikariPool.<init>(HikariPool.java:115)
at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:112)
at com.alibaba.nacos.config.server.service.datasource.LocalDataSourceServiceImpl.reload(LocalDataSourceServiceImpl.java:96)
... 169 common frames omitted
Caused by: org.apache.derby.iapi.error.StandardException: Login timeout exceeded.
at org.apache.derby.iapi.error.StandardException.newException(Unknown Source)
at org.apache.derby.impl.jdbc.SQLExceptionFactory.wrapArgsForTransportAcrossDRDA(Unknown Source)
... 185 common frames omitted
根據(jù)錯誤日志分析
Nacos在加載schema.sql文件時,derby數(shù)據(jù)庫報了一個Login Timeout exceeded的錯誤,看著像是超時,此時根據(jù)異常堆棧查看源碼如下:
private EmbedConnection timeLogin(String var1, Properties var2, int var3) throws SQLException { try { InternalDriver.LoginCallable var4 = new InternalDriver.LoginCallable(this, var1, var2); Future var5 = _executorPool.submit(var4); long var6 = System.currentTimeMillis(); long var8 = var6 + (long)var3 * 1000L; while(var6 < var8) { try { EmbedConnection var10 = (EmbedConnection)var5.get(var8 - var6, TimeUnit.MILLISECONDS); return var10; } catch (InterruptedException var16) { InterruptStatus.setInterrupted(); var6 = System.currentTimeMillis(); } catch (ExecutionException var17) { throw this.processException(var17); } catch (TimeoutException var18) { throw Util.generateCsSQLException("XBDA0.C.1", new Object[0]); } } throw Util.generateCsSQLException("XBDA0.C.1", new Object[0]); } finally { InterruptStatus.restoreIntrFlagIfSeen(); } }
代碼邏輯很簡單,封裝了獲取EmbedConnection這個數(shù)據(jù)庫鏈接的過程為異步獲取,通過Future來控制超時時長,那么此時可以判斷出就是在獲取EmbedConnection這個數(shù)據(jù)庫鏈接的時候超時了。
但是獲取EmbedConnection為什么超時,里面到底做了什么,不得而知(由于源碼的class文件中缺乏了本地變量表,導(dǎo)致代碼閱讀困難),此時注意到:
2021-09-18 22:40:31,822 INFO Tomcat initialized with port(s): 8848 (http)
2021-09-18 22:40:44,722 ERROR。。。。
這兩行日志為SpringBoot的啟動日志,而這兩行日志中,日志打印的時間差了10s左右,那么猜測可能就是這10s中獲取數(shù)據(jù)庫連接超時,并且一定會有一個線程一直阻塞中。
此時我們想到可以使用 jstack命令去查看java進程的線程堆棧日志,如下:
"derby.rawStoreDaemon" #38 daemon prio=5 os_prio=0 tid=0x00007ff090057800 nid=0x6337 in Object.wait() [0x00007ff097ffe000]
java.lang.Thread.State: TIMED_WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000ee89fd70> (a org.apache.derby.impl.services.daemon.BasicDaemon)
at org.apache.derby.impl.services.daemon.BasicDaemon.rest(Unknown Source)
- locked <0x00000000ee89fd70> (a org.apache.derby.impl.services.daemon.BasicDaemon)
at org.apache.derby.impl.services.daemon.BasicDaemon.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)"Thread-15" #37 daemon prio=5 os_prio=0 tid=0x00007ff12928b800 nid=0x6336 runnable [0x00007ff0ec1be000]
java.lang.Thread.State: RUNNABLE
at java.io.FileDescriptor.sync(Native Method)
at org.apache.derby.impl.io.DirRandomAccessFile.sync(Unknown Source)
at org.apache.derby.impl.store.raw.data.RAFContainer.writeRAFHeader(Unknown Source)
at org.apache.derby.impl.store.raw.data.RAFContainer.clean(Unknown Source)
- locked <0x00000000ef759b28> (a org.apache.derby.impl.store.raw.data.RAFContainer4)
at org.apache.derby.impl.services.cache.ConcurrentCache.cleanAndUnkeepEntry(Unknown Source)
at org.apache.derby.impl.services.cache.ConcurrentCache.cleanCache(Unknown Source)
at org.apache.derby.impl.services.cache.ConcurrentCache.cleanAll(Unknown Source)
at org.apache.derby.impl.store.raw.data.BaseDataFileFactory.checkpoint(Unknown Source)
at org.apache.derby.impl.store.raw.data.BaseDataFileFactory.createFinished(Unknown Source)
at org.apache.derby.impl.store.raw.RawStore.createFinished(Unknown Source)
at org.apache.derby.impl.store.access.RAMAccessManager.createFinished(Unknown Source)
at org.apache.derby.impl.db.BasicDatabase.createFinished(Unknown Source)
at org.apache.derby.impl.db.BasicDatabase.boot(Unknown Source)
at org.apache.derby.impl.services.monitor.BaseMonitor.boot(Unknown Source)
at org.apache.derby.impl.services.monitor.TopService.bootModule(Unknown Source)
at org.apache.derby.impl.services.monitor.BaseMonitor.bootService(Unknown Source)
at org.apache.derby.impl.services.monitor.BaseMonitor.createPersistentService(Unknown Source)
at org.apache.derby.impl.services.monitor.FileMonitor.createPersistentService(Unknown Source)
at org.apache.derby.iapi.services.monitor.Monitor.createPersistentService(Unknown Source)
at org.apache.derby.impl.jdbc.EmbedConnection$5.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at org.apache.derby.impl.jdbc.EmbedConnection.createPersistentService(Unknown Source)
at org.apache.derby.impl.jdbc.EmbedConnection.createDatabase(Unknown Source)
at org.apache.derby.impl.jdbc.EmbedConnection.<init>(Unknown Source)
at org.apache.derby.jdbc.InternalDriver$1.run(Unknown Source)
at org.apache.derby.jdbc.InternalDriver$1.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at org.apache.derby.jdbc.InternalDriver.getNewEmbedConnection(Unknown Source)
at org.apache.derby.jdbc.InternalDriver$LoginCallable.call(Unknown Source)
at org.apache.derby.jdbc.InternalDriver$LoginCallable.call(Unknown Source)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
......."main" #1 prio=5 os_prio=0 tid=0x00007ff12804c000 nid=0x61d1 waiting on condition [0x00007ff130ee5000]
java.lang.Thread.State: TIMED_WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000c01f6de8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078)
at java.util.concurrent.ThreadPoolExecutor.awaitTermination(ThreadPoolExecutor.java:1475)
at com.alibaba.nacos.common.utils.ThreadUtils.shutdownThreadPool(ThreadUtils.java:121)
at com.alibaba.nacos.common.utils.ThreadUtils.shutdownThreadPool(ThreadUtils.java:106)
at com.alibaba.nacos.common.executor.ThreadPoolManager.destroy(ThreadPoolManager.java:156)
- locked <0x00000000c03b65d8> (a java.lang.Object)
at com.alibaba.nacos.common.executor.ThreadPoolManager.shutdown(ThreadPoolManager.java:197)
at com.alibaba.nacos.core.code.StartingSpringApplicationRunListener.failed(StartingSpringApplicationRunListener.java:147)
at org.springframework.boot.SpringApplicationRunListeners.callFailedListener(SpringApplicationRunListeners.java:91)
at org.springframework.boot.SpringApplicationRunListeners.failed(SpringApplicationRunListeners.java:84)
at org.springframework.boot.SpringApplication.handleRunFailure(SpringApplication.java:828)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:327)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1260)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1248)
at com.alibaba.nacos.Nacos.main(Nacos.java:35)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:49)
at org.springframework.boot.loader.Launcher.launch(Launcher.java:108)
at org.springframework.boot.loader.Launcher.launch(Launcher.java:58)
at org.springframework.boot.loader.PropertiesLauncher.main(PropertiesLauncher.java:467)"VM Thread" os_prio=0 tid=0x00007ff12819d000 nid=0x61df runnable
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007ff12805e800 nid=0x61d8 runnable
"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00007ff128060800 nid=0x61d9 runnable
"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x00007ff128062800 nid=0x61da runnable
"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x00007ff128064000 nid=0x61db runnable
"VM Periodic Task Thread" os_prio=0 tid=0x00007ff1281f0000 nid=0x61e7 waiting on condition
JNI global references: 991
經(jīng)過多次的jstack命令查看,發(fā)現(xiàn)Thread-15這個線程一直阻塞在
java.io.FileDescriptor.sync(Native Method)
查看下jdk中對此方法的說明:
- 在涉及IO的操作中,linux系統(tǒng)為了平衡物理介質(zhì)和內(nèi)存的速度差異,引入了PageCache的概念,在我們調(diào)用outputstram的write的方法時,并不是將內(nèi)容寫入到物理介質(zhì)中而是寫入到PageCache中并且標記此塊內(nèi)存為dirty。
- linux的后臺會啟動一個線程定時將標記為dirty的內(nèi)存塊刷新到物理介質(zhì)中,而如果此時虛機斷電的話,那么沒有刷新到物理介質(zhì)中的內(nèi)容就有可能會丟失,所以linxu會提供fsync的命令強制刷新,而不是等定時任務(wù)刷新。
- 而對應(yīng)jdk中封裝就是FileDescriptor.sync的方法
這個問題我的第一反應(yīng)就是會不會是PageCache過大,導(dǎo)致刷盤過慢通過free -g命令查看
free -g total used free shared buff/cache available Mem: 8 1 0 0 6 6 Swap: 9 0 9
發(fā)現(xiàn)總共內(nèi)存為8g,而PageCache占用了高達6g,立馬將PageCache強制刷盤,并重新啟動Nacos,可惜問題依舊存在。
此時我的想法是會不會是在執(zhí)行某一個fsync的命令的時候,執(zhí)行時間過久呢,我希望能夠知道每個fsync的執(zhí)行耗時,修改啟動命令為:
#nohup java ${JAVA_OPT} >${BASE_DIR}/logs/start.out 2>&1 </dev/null strace -T -ttt -ff -xx -yy -o strace.log java ${JAVA_OPT} >${BASE_DIR}/logs/start.out 2>&1 </dev/null
增加了strace命令去追蹤每個系統(tǒng)調(diào)用的耗時,并且以線程ID的形式分成不同的文件,strace.log后面跟著的數(shù)字就是線程ID。
[root@localhost nacos]# ls -l total 77832 drwxr-xr-x 2 root root 4096 Sep 18 22:37 bin drwxr-xr-x 2 root root 4096 Sep 18 01:52 conf drwxr-xr-x 3 root root 4096 Sep 18 22:40 data -rw-r--r-- 1 root root 696 Sep 18 22:40 derby.log drwxr-xr-x 2 root root 4096 Sep 18 22:40 logs -rw-r--r-- 1 root root 44298 Sep 18 22:40 strace.log.27777 -rw-r--r-- 1 root root 58640394 Sep 18 22:40 strace.log.27778 -rw-r--r-- 1 root root 16262 Sep 18 22:40 strace.log.27779 -rw-r--r-- 1 root root 16882 Sep 18 22:40 strace.log.25398 ...... drwxr-xr-x 2 root root 4096 Sep 18 22:38 target drwxr-xr-x 3 root root 4096 Sep 18 22:40 work
此時我們回到之前打印的線程堆棧日志,找到阻塞的線程
"Thread-15" #37 daemon prio=5 os_prio=0 tid=0x00007ff12928b800 nid=0x6336 runnable [0x00007ff0ec1be000]
線程Id: nid=0x6336,通過進制換算將16進制換算成10進制為25398,找到strace.log.25398,下載并通過notepad++ 打開,搜索fsync命令:
// 時間戳 1631906136.913442 fsync(44<\x2f\x68\x6f\x6d\x65\x2f\x6e\x61\x63\x6f\x73\x2f\x64\x61\x74\x61\x2f\x64\x65\x72\x62\x79\x2d\x64\x61\x74\x61\x2f\x64\x62\x2e\x6c\x63\x6b>) = 0 // fsync命令耗時 <0.184688>
找到所有的fsync命令,通過打印的時間戳計算總耗時大約為11s左右。
此時只需要確認前面獲取鏈接的超時時間,確認默認的超時時長為多少,查看Nacos的代碼,找到初始化數(shù)據(jù)庫連接的地方,通過代碼跟蹤找到:
private synchronized void initialize(String jdbcUrl) { HikariDataSource ds = new HikariDataSource(); ds.setDriverClassName(jdbcDriverName); ds.setJdbcUrl(jdbcUrl); ds.setUsername(userName); ds.setPassword(password); ds.setIdleTimeout(30_000L); ds.setMaximumPoolSize(80); ds.setConnectionTimeout(10000L); DataSourceTransactionManager tm = new DataSourceTransactionManager(); tm.setDataSource(ds); if (jdbcTemplateInit) { jt.setDataSource(ds); tjt.setTransactionManager(tm); } else { jt = new JdbcTemplate(); jt.setMaxRows(50000); jt.setQueryTimeout(5000); jt.setDataSource(ds); tjt = new TransactionTemplate(tm); tjt.setTimeout(5000); jdbcTemplateInit = true; } reload(); }
ds.setConnectionTimeout(10000L);這里就是設(shè)置數(shù)據(jù)庫連接超時的地方,代碼中寫死為10s,和strace.log中計算的時長大致能對的上,所以至此可以確認,是調(diào)用linux的fsync的方法耗時過久導(dǎo)致業(yè)務(wù)側(cè)超時,但是為什么會耗時這么久?畢竟阿里給的默認10s應(yīng)該是經(jīng)過驗證的。帶著這個疑問我尋思應(yīng)該去查看磁盤的性能了。
iostat -x 1 1000 avg-cpu: %user %nice %system %iowait %steal %idle 3.49 0.00 2.00 22.94 0.00 71.57 Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util sda 0.00 43.00 0.00 52.00 0.00 488.00 14.92 0.96 17.56 0.00 17.56 18.42 95.80 dm-0 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 dm-1 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 dm-2 0.00 0.00 0.00 87.00 0.00 488.00 8.92 0.99 10.92 0.00 10.92 11.01 95.80 avg-cpu: %user %nice %system %iowait %steal %idle 3.77 0.00 1.26 23.37 0.00 71.61 Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util sda 0.00 47.00 0.00 59.00 0.00 548.00 18.58 0.99 16.47 0.00 16.47 16.24 95.80 dm-0 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 dm-1 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 dm-2 0.00 0.00 0.00 98.00 0.00 552.00 11.27 1.03 10.40 0.00 10.40 9.79 95.90 avg-cpu: %user %nice %system %iowait %steal %idle 3.99 0.00 1.75 22.94 0.00 71.32 Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util sda 0.00 42.00 0.00 55.00 0.00 464.00 13.24 0.98 19.11 0.00 19.11 17.22 94.70 dm-0 0.00 0.00 0.00 2.00 0.00 36.00 36.00 0.03 15.50 0.00 15.50 8.00 1.60 dm-1 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 dm-2 0.00 0.00 0.00 86.00 0.00 424.00 7.53 0.98 12.30 0.00 12.30 11.01 94.70
觀測磁盤使用率,發(fā)現(xiàn)在Nacos的啟動過程中, %util指標(磁盤的使用率)一直在95%左右,磁盤在被高度占用中,磁盤的寫入速率wkB/s平均在500kb/s左右。
至此可以確認磁盤的性能不足導(dǎo)致derby數(shù)據(jù)庫初始化的時候,刷盤耗時過久,超過了默認配置的10s的超時時長,導(dǎo)致Nacos啟動失敗。
修改方法很簡單,修改默認超時時長或者將默認的超時時長變更為配置項放在配置文件中。
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java調(diào)用CXF WebService接口的兩種方式實例
今天小編就為大家分享一篇關(guān)于Java調(diào)用CXF WebService接口的兩種方式實例,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-03-03Java讀寫文件,在文件中搜索內(nèi)容,并輸出含有該內(nèi)容的所有行方式
這篇文章主要介紹了Java讀寫文件,在文件中搜索內(nèi)容,并輸出含有該內(nèi)容的所有行方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-08-08Spring boot集成swagger2生成接口文檔的全過程
這篇文章主要給大家介紹了關(guān)于Spring boot集成swagger2生成接口文檔的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家學習或者使用Spring boot具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧2019-09-09SpringWebMVC的常用注解及應(yīng)用分層架構(gòu)詳解
這篇文章主要介紹了SpringWebMVC的常用注解及應(yīng)用分層架構(gòu),SpringWebMVC是基于ServletAPI構(gòu)建的原始Web框架,從?開始就包含在Spring框架中,感興趣的朋友可以參考下2024-05-05kafka分布式消息系統(tǒng)基本架構(gòu)及功能詳解
這篇文章主要為大家介紹了kafka分布式消息系統(tǒng)基本架構(gòu)及功能詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-03-03Java數(shù)據(jù)結(jié)構(gòu)與算法學習之循環(huán)鏈表
循環(huán)鏈表是另一種形式的鏈式存儲結(jié)構(gòu)。它的特點是表中最后一個結(jié)點的指針域指向頭結(jié)點,整個鏈表形成一個環(huán)。本文將為大家詳細介紹一下循環(huán)鏈表的特點與使用,需要的可以了解一下2021-12-12