ZWY面试总结
代辦
AQS
Concurrenthashmap
投遞公司
| 騰訊 | |||
| 美團(張云峰) | 筆試完成 | https://zhaopin.meituan.com/web/personalCenter/deliveryRecord | |
| 字節 | https://jobs.bytedance.com/experienced/position/application | ||
| 阿里 | |||
| 小紅書 | |||
| 騰訊云智 | 待面試 | 4.5 | |
| 騰訊音樂 | 筆試完成 | ||
| 京東 | 測評完成 | https://campus.jd.com/#/myDeliver?type=present | |
| 微眾銀行 | 一起觸發更多可能 (webank.com) | ||
| 閱文集團 | https://www.nowcoder.com/careers/yuewen/122324 | ||
| 中國銀行104573 | https://applyjob.chinahr.com/page/job/success?projectId=63f47e7255cbed088c78eed1 | ||
| 中國郵政 | https://xiaoyuan.zhaopin.com/scrd/postprocess?cid=58805&pid=101739970&productId=7&channelId=null&taskId=null | ||
| 螞蟻 | 掛到螞蟻集團 | https://talent.antgroup.com/personal/campus-application | |
| 特斯拉 | https://app.mokahr.com/campus-recruitment/tesla/91939#/candidateHome/applications | ||
| 58 | https://campus.58.com/Portal/Apply/Index | ||
| 立得空間 | https://www.showmebug.com/written_pads/HRUHZN | ||
| 恒生 | https://campus.hundsun.com/personal/deliveryRecord | ||
| wind | |||
| 銀泰百貨 | https://app.mokahr.com/m/candidate/applications/deliver-query/intime | ||
| 曠世 | https://app.mokahr.com/campus-recruitment/megviihr/38642#/candidateHome/applications | ||
| 騰訊音樂 | https://join.tencentmusic.com/deliver | ||
| 完美世界 | https://app.mokahr.com/campus-recruitment/pwrd/45131#/candidateHome/applications | ||
| 七牛云 | https://app.mokahr.com/campus-recruitment/qiniuyun/73989#/candidateHome/applications | ||
| cider | https://ciderglobal.jobs.feishu.cn/504718/position/application | ||
| 360(運維開發) | https://360campus.zhiye.com/personal/deliveryRecord | ||
| 知乎(改簡歷) | https://app.mokahr.com/campus_apply/zhihu/68321#/candidateHome/applications | ||
| 猿輔導 | https://hr.yuanfudao.com/campus-recruitment/fenbi/47742/#/candidateHome/applications | ||
| 愛奇藝 | https://careers.iqiyi.com/apply/iqiyi/39117#/jobs | ||
| 百詞斬 | https://join.baicizhan.com/campus | ||
| 昆侖萬維 | https://app.mokahr.com/campus-recruitment/klww/67963#/candidateHome/applications | ||
| bilibli | https://jobs.bilibili.com/campus/records | ||
| geek | https://app.mokahr.com/campus_apply/geekplus/98039#/candidateHome/applications | 
| 眾安 | https://app.mokahr.com/campus-recruitment/zhongan/71908?sourceToken=d895a22a006b8a6da61313d9b4091850#/candidateHome/applications | |
| 虎牙 | https://app.mokahr.com/campus_apply/huya/4112?edit=1#/candidateHome/applications | |
| oppo | https://careers.oppo.com/campus/record | |
| 第四范式 | https://app.mokahr.com/campus-recruitment/4paradigm/58145#/candidateHome/applications | |
代投:商湯 geek 雷火 oppo vivo 字節 阿里 騰訊 聯想 西山居
自我介紹
我叫張維陽,是中國地質大學武漢的一名研二學生,以下是我的自我介紹:
在項目方面,為了河南高校魔方愛好者之間更好地交流與分享經驗 ,為河南高校魔方聯盟搭建了一個基于SpringBoot的魔方經驗分享及管理平臺,主要功能包括用戶注冊和登錄、魔方教程和經驗分享、論壇和社區互動、管理和監控等。 在這其中加入了一些中間件進行優化,最后做了上線部署。得到了魔方愛好者的一致認可。同時為了更好的了解RPC,自己手寫了一個RPC框架,主要內容包括注冊中心、網絡傳輸、序列化與反序列化、動態代理、負載均衡等 ,從而加深了對RPC框架的理解。
比賽方面,參加過幾次數學建模競賽和數學競賽,獲得過第十二屆全國大學生數學競賽一等獎以及高教社全國大學生數學建模二等獎。學習之余,也喜歡通過博客整理分享自己所學知識,以上就是我的自我介紹。
優缺點
缺點: 缺少相關專業的實踐、工作經驗;遇事不夠沉著冷靜,容易緊張;
優點:有團隊精神意識,善于溝通。大三參加數學建模時,協調組內其他成員,對編程遇到困難的組員提供幫助 ,最終順利完成比賽并獲得獎項 。
你還有什么要問的?
- 部門的主營業務是什么?
- 部門使用的技術棧、編程語言是什么、使用哪些框架、中間件?表達下自己對技術的好奇
項目
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-PPOD9POX-1681383922602)(https://raw.githubusercontent.com/viacheung/img/main/image/image-20230413011206981.png)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-KKm7TkAK-1681383922605)(https://raw.githubusercontent.com/viacheung/img/main/image/image-20230404225216774.png)]
魔方天地
介紹一下項目?
該項目是一個致力于魔方愛好者之間交流、分享經驗的在線平臺,主要功能包括用戶注冊和登錄、魔方教程的
 展示分享、魔方論壇互動、比賽和活動組織、用戶管理等。
以下是該項目的主要特點和功能:
以上就是我項目的主要內容
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-RI90XXJc-1681383922608)(https://raw.githubusercontent.com/viacheung/img/main/image/image-20230322005426563.png)]
1、JWT+token+redis
用戶jwt鑒權流程
jwt 有三部分組成:A.B.C
A:Header,{“type”:“JWT”,“alg”:“HS256”} 固定 定義生成簽名算法以及Token的類型
B:playload,存放實際需要傳遞的信息,比如,用戶id,過期時間等等,可以被解密,不能存放敏感信息
C: 簽證,A和B加上秘鑰 加密而成,只要秘鑰不丟失,可以認為是安全的。
jwt 驗證,主要就是驗證C部分 是否合法。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-9p98Egvg-1681383922609)(https://raw.githubusercontent.com/viacheung/img/main/image/image-20230320181343651.png)]
用戶使用用戶名密碼來請求服務器
服務器進行驗證用戶的信息
3.服務器通過驗證,發送給用戶一個token(header.payload(id 電話號碼啥的).簽證)
4.客戶端存儲token,并在每次請求時附送上這個token值(Authorization里面)
服務端驗證token值,并返回數據,從中也可獲取用戶相關信息
jwt token生成和校驗
使用私鑰加密生成token 公鑰解密獲取token中的信息
jwt退出登錄/修改密碼時如何使原來的token失效
刪除redis里面的token即可
redis or jwt?
然后呢,JWT是個啥?其實就是把用戶的用戶信息,用密鑰加密防篡改,然后放在請求頭里。用戶請求的時候呢,再解密。服務器就不需要保存用戶的信息了。簡單來說,這個加密之后的信息寫的是啥,服務器就認為用戶是啥。
美其名曰,無狀態,不需要消耗服務器的存儲,減輕服務器壓力。但是反過來,卻帶來了無法注銷,請求頭體積大,加解密效率等其他問題。然后為了解決這些問題,把redis的那一套解決方案,再引過來,兩種方式結合在一起。。。講道理,我吐了。強行混著來,jwt的意義何在呢?
用redis和不用的區別
1、用redis可以直接使jwt失效或者延期,方便
2、多個子系統可以訪問redis數據庫
就單點登錄里的token而言,單點登錄是為了實現一次登錄其他相互信任的系統不用登陸就可訪問的效果,既然如此就不止一個系統,每個系統的數據存在不同的數據庫中,A系統不可以訪問B系統的數據庫,若將token存在于A系統數據庫中,B系統登錄時就訪問不到token,但是所有系統都可以訪問redis緩存數據庫,而且token具有時效性,而redis天然支持設置過期時長【set(key,value,毫秒值)】
3、而且redis響應速度很快
登錄時把jwt存進redis,設置成2倍的jwt有效期。登出刪除redis保存的jwt。
如何防止jwt token被竊取
1、采用更安全的傳輸協議https
2、加密傳輸
3、代碼層面也可以做安全檢測,比如ip地址發生變化,MAC地址發生變化等等,可以要求重新登錄
4、使用私鑰加密生成token 公鑰解密獲取token中的信息
1)系統不能把jwt作為唯一的身份識別條件,不然被別人拿到了jwt就相當于獲得了所有相關賬戶的權限,但對于這一點我還在學習中。2)存儲jwt、傳輸的方式應該再加強。
private static final String slat = "mszlu!@#";//加密鹽 因為數據庫不能給人看密碼 每次都用這一個字符串用來加密token過期了怎么辦?
我的項目是token不過期、redis記錄過期
https://juejin.cn/post/7126708538440679460
如何踢人下線
直接把token和存入redis里,驗證有無token即可。踢人下線直接清除redis中的token。和筆者的思路是類似的。
2、ThreadLocal保存用戶信息
目的
方便鑒權,查詢是否在線,減少數據庫讀操作
token:sysUser
不想用session(服務器存儲 分布式)
文章發布 需要用戶id,直接拿,評論也是一樣,方便
request獲取? 但是從設計上、代碼分層上來說,
并發問題
降低redis使用?
redis中可以獲取用戶信息,但是因為redis中的key是token,要先拿到token才能拿到用戶信息,但是token不是每個類中都存在,想在每個類都獲取到用戶信息
一般我們需要獲取當前用戶信息,放到緩存?每次解析token,然后傳遞?我們可以使用ThreadLocal來解決。將用戶信息保存在線程中,當請求結束后我們在把保存的信息清除掉。這樣我們才開發的時候就可以直接從全局的ThreadLocal中很方便的獲取用戶信息。當前線程在任何地方需要時,都可以使用
步驟
- 創建ThreadLocal類,在其中設置相關的添加、獲取以及刪除方法。
- 創建登錄攔截器,重新其中的preHandle()(ThreadLocal.put())和afterCompletion()(不用了手動remove)方法。
- webmvcConfig里面注冊攔截器(告訴springmvc我們要攔截誰) 排除登錄注冊這種就行 其他都需要登錄
原理
Thread類中,有個ThreadLocal.ThreadLocalMap 的成員變量。ThreadLocalMap內部維護了Entry數組,每個Entry代表一個完整的對象,key是ThreadLocal本身,value是ThreadLocal的泛型對象值
并發多線程場景下,每個線程Thread,在往ThreadLocal里設置值的時候,都是往自己的ThreadLocalMap里存,讀也是以某個ThreadLocal作為引用,在自己的map里找對應的key,從而可以實現了線程隔離。
為什么不直接用線程id作為ThreadLocalMap的key呢?
用了兩個ThreadLocal成員變量的話。如果用線程id作為ThreadLocalMap的key,怎么區分哪個ThreadLocal成員變量呢?因此還是需要使用ThreadLocal作為Key來使用。每個ThreadLocal對象,都可以由threadLocalHashCode屬性唯一區分的,每一個ThreadLocal對象都可以由這個對象的名字唯一區分
弱引用導致的內存泄漏呢?
ThreadLocalMap使用ThreadLocal的弱引用作為key,當ThreadLocal變量被手動設置為null,即一個ThreadLocal沒有外部強引用來引用它,當系統GC時,ThreadLocal一定會被回收。這樣的話,ThreadLocalMap中就會出現key為null的Entry,就沒有辦法訪問這些key為null的Entry的value,如果當前線程再遲遲不結束的話(比如線程池的核心線程),這些key為null的Entry的value就會一直存在一條強引用鏈:Thread變量 -> Thread對象 -> ThreaLocalMap -> Entry -> value -> Object 永遠無法回收,造成內存泄漏。
實際上,ThreadLocalMap的設計中已經考慮到這種情況。所以也加上了一些防護措施:即在ThreadLocal的get,set,remove方法,都會清除線程ThreadLocalMap里所有key為null的value。
key是弱引用,GC回收會影響ThreadLocal的正常工作嘛?
不會的,因為有ThreadLocal變量引用著它,是不會被GC回收的,除非手動把ThreadLocal變量設置為null
ThreadLocal內存泄漏的demo
用線程池,一直往里面放對象
因為我們使用了線程池,線程池有很長的生命周期,因此線程池會一直持有tianLuoClass(ThreadLocal泛型值)對象的value值,即使設置tianLuoClass = null;引用還是存在的
為什么弱引用
當ThreadLocal的對象被回收了,因為ThreadLocalMap持有ThreadLocal的弱引用,即使沒有手動remove刪除,ThreadLocal也會被回收。value則在下一次ThreadLocalMap調用set,get,remove的時候會被清除。
InheritableThreadLocal保證父子線程間的共享數據
在子線程中,是可以獲取到父線程的 InheritableThreadLocal 類型變量的值,但是不能獲取到 ThreadLocal 類型變量的值(因為ThreadLocal是線程隔離)。
在Thread類中,除了成員變量threadLocals之外,還有另一個成員變量:inheritableThreadLocals。
當parent的inheritableThreadLocals不為null時,就會將parent的inheritableThreadLocals,賦值給前線程的inheritableThreadLocals。說白了,就是如果當前線程的inheritableThreadLocals不為null,就從父線程哪里拷貝過來一個過來,類似于另外一個ThreadLocal,但是數據從父線程那里來的。有興趣的小伙伴們可以在去研究研究源碼~
ThreadLocal的應用場景和使用注意點
ThreadLocal的很重要一個注意點,就是使用完,要手動調用remove()。
而ThreadLocal的應用場景主要有以下這幾種:
- 使用日期工具類,當用到SimpleDateFormat,使用ThreadLocal保證線性安全
- 全局存儲用戶信息(用戶信息存入ThreadLocal,那么當前線程在任何地方需要時,都可以使用)
- 保證同一個線程,獲取的數據庫連接Connection是同一個,使用ThreadLocal來解決線程安全的問題
- 使用MDC保存日志信息。
ThreadLocal特點
線程并發:在多線程并發場景下使用
**傳遞數據:**可以通過ThreadLocal在同一線程,不同組件中傳遞公共變量(保存每個線程的數據,在需要的地方可以直接獲取, 避免參數直接傳遞帶來的代碼耦合問題)
線程隔離:每個線程的變量都是獨立的, 不會互相影響
ThreadLocal 和Synchronized
ThreadLocal模式與Synchronized關鍵字都用于處理多線程并發訪問變量的問題
ThreadLocal:以空間換取時間的思想, 為每一個線程都提供了一份變量的副本, 從而實現同訪問而 互相不干擾。 多線程中讓每個線程之間的數據相互隔離
Synchronized:以時間換取空間的思想,只提供了一份變量, 讓不同的線程排隊訪問。多個線程之間訪問資源的同步
ThreadLocal不能解決共享變量的線程安全問題
子線程訪問父線程的共享變量時候,是“引用傳遞”,多個子線程訪問的話所以線程不安全
- 每個線程獨享一份new出來的實例 -> 線程安全
- 多個線程共享一份“引用類型”實例 -> 線程不安全
ThreadLocal與Thread同步機制的比較
線性探測法
線性探測法顧名思義,就是解決沖突的函數是一個線性函數,最直接的就是在TreadLocal的代碼中也用的是這樣一個解決沖突的函數。
f(x)= x+1但是要注意的是TreadLocal中,是一個環狀的探測,如果到達邊界就會直接跨越邊界到另一頭去。
線性探測法的優點:
缺點:
之前我們說過,線性探測法有個問題是,一旦發生碰撞,很可能之后每次都會產生碰撞,導致連環撞車。而使用0x61c88647這個值做一個hash的增長值就可以從一定程度上解決這個問題讓生成出來的值較為均勻地分布在2的冪大小的數組中。也就是說當我們用0x61c88647作為步長累加為每個ThreadLocal分配各自的ID也就是threadLocalHashCode再與2的冪取模,得到的結果分布很均勻。
0x61c88647選取其實是與斐波那契散列有關,這個就是數學知識了,這里不展開。
3、日志記錄放入線程池
原因
不能讓記錄日志出現失誤影響用戶的登錄
為了出現錯誤時候的排查
記錄日志錄入數據庫時,脫離主線程,實現異步插入,這樣不會拖延主線程的執行時間
 ps:記錄日志,可以寫在業務邏輯中,也可以利用aop自動記錄。
我的線程池參數?
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-DmLQyJqn-1681383922611)(https://raw.githubusercontent.com/viacheung/img/main/image/QFR%7D%7D83L%60%255T_PO3GGRG@NR.png)]
這么設置也挺好,你的最佳核心線程數應該在cpuNum~cpuNum2 之間呢,cpuNum2 設置的太多了,cpu負載過大,速度反而會慢下來,cpuNum給的太少,也不能充分利用性能,利用隊列滿了之后溢出來的任務,被備用線程也就是核心線程之外的那些線程給處理了的這種機制,讓他自動找補一下。
關于異步
如果是注冊這類的功能不適合異步,肯定要同步的,注冊成功才返回成功。我這個上傳視頻到OSS要異步的原因是因為用戶要先把視頻發到我的云服務器中,云服務器再去接收到的視頻放到阿里云OSS中,是兩個步驟。如果要兩個操作成功才返回給用戶上傳成功的話太久了。所以在用戶上傳視頻到云服務器成功后就可以返回成功信息給用戶了,后面服務器再自己把視頻上傳到OSS(這個過程挺慢的)。具體邏輯還不夠完善(主要是錢沒到位)。不是什么情況下都可以用異步處理的。要一個操作設計多個步驟,并且后續的步驟都和用戶沒什么關系的情況下才會考慮要不要異步處理(可以提供用戶體驗,不用一直等)。比如銀行的某些代付交易,在上游的數據檢查完成正常后就會直接返回上游交易成功,用戶直接就看到了交易結果,后續的調用銀行核心記賬這些操作其實還在跑,可能幾秒甚至幾分鐘后才是真正的交易完成。
步驟
1.配置線程池(ThreadPoolTaskExecutor)
 2.自定義一個異步任務管理器
 3.自定義任務
 4.指定地點處,調用執行任務管理器,傳入指定的任務
execute() vs submit()
都是提交任務到線程池
- execute() 方法用于提交不需要返回值的任務,所以無法判斷任務是否被線程池執行成功與否;
- submit()方法用于提交需要返回值的任務。線程池會返回一個future類型的對象,通過這個future對象可以判斷任務是否執行成功,并且可以通過future的get()方法來獲取返回值,get()方法會阻塞當前線程直到任務完成,而使用 get(long timeout,TimeUnit unit)方法則會阻塞當前線程一段時間后立即返回,這時候有可能任務沒有執行完。
為什么要用線程池?
資源消耗–重復利用 響應速度—立即執行 可管理性—線程池統一分配
- 降低資源消耗。 通過重復利用已創建的線程降低線程創建和銷毀造成的消耗。
- 提高響應速度。 當任務到達時,任務可以不需要的等到線程創建就能立即執行。
- 提高線程的可管理性。 線程是稀缺資源,如果無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一的分配,調優和監控。
如何創建線程池
1、構造?法
2、通過 Executor 框架的?具類 Executors 來實現
三種ThreadPoolExecutor:
fixedThreadPool() 固定線程數
SingleThreadExecutor 只有一個線程
CachedThreadPool 根據情況調整 數量不固定
ThreadPoolExecutor 類分析
ThreadPoolExecutor 構造函數七大參數分析
-  corePoolSize : 核心線程大小。線程池一直運行,核心線程就不會停止。 
-  maximumPoolSize :線程池最大線程數量。非核心線程數量=maximumPoolSize-corePoolSize 
-  keepAliveTime :非核心線程的心跳時間。如果非核心線程在keepAliveTime內沒有運行任務,非核心線程會消亡。 
-  workQueue :阻塞隊列。ArrayBlockingQueue,LinkedBlockingQueue等,用來存放線程任務。 
-  defaultHandler :飽和策略。ThreadPoolExecutor類中一共有4種飽和策略。通過實現 RejectedExecutionHandler 接口。 飽和策略 - AbortPolicy : 線程任務丟棄報錯。默認飽和策略。
- DiscardPolicy : 線程任務直接丟棄不報錯。
- DiscardOldestPolicy : 將workQueue隊首任務丟棄,將最新線程任務重新加入隊列執行。
- CallerRunsPolicy :線程池之外的線程直接調用run方法執行。
 
-  ThreadFactory :線程工廠。新建線程工廠。 
線程池原理分析
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-JL9eoruj-1681383922612)(https://raw.githubusercontent.com/viacheung/img/main/image/1460000039258685)]
常見java線程池
1、newCachedThreadPool
創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程。
這種類型的線程池特點是:
工作線程的創建數量幾乎沒有限制(其實也有限制的,數目為Interger. MAX_VALUE), 這樣可靈活的往線程池中添加線程。
不足:這種方式雖然可以根據業務場景自動的擴展線程數來處理我們的業務,但是最多需要多少個線程同時處理缺是我們無法控制的;
優點:如果當第二個任務開始,第一個任務已經執行結束,那么第二個任務會復用第一個任務創建的線程,并不會重新創建新的線程,提高了線程的復用率;
2、newFixedThreadPool
創建一個指定工作線程數量的線程池。每當提交一個任務就創建一個工作線程,如果工作線程數量達到線程池初始的最大數,則將提交的任務存入到池隊列中。
**缺點:**它具有線程池提高程序效率和節省創建線程時所耗的開銷的優點。但是,在線程池空閑時,即線程池中沒有可運行任務時,它不會釋放工作線程,還會占用一定的系統資源。
優點:newFixedThreadPool的線程數是可以進行控制的,因此我們可以通過控制最大線程來使我們的服務器打到最大的使用率,同事又可以保證及時流量突然增大也不會占用服務器過多的資源。
3、newSingleThreadExecutor
創建一個單線程化的Executor,只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。如果這個線程異常結束,會有另一個取代它,保證順序執行。單工作線程最大的特點是可保證順序地執行各個任務,并且在任意給定的時間不會有多個線程是活動的。
4、newScheduleThreadPool
創建一個定長的線程池,而且支持定時的以及周期性的任務執行,支持定時及周期性任務執行。
線程池常用的阻塞隊列有哪些?
表格左側是線程池,右側為它們對應的阻塞隊列,可以看到 5 種線程池對應了 3 種阻塞隊列
LinkedBlockingQueue 對于 FixedThreadPool 和 SingleThreadExector 而言,它們使用的阻塞隊列是容量為 Integer.MAX_VALUE 的 LinkedBlockingQueue,可以認為是無界隊列。由于 FixedThreadPool 線程池的線程數是固定的,所以沒有辦法增加特別多的線程來處理任務,因此需要這樣一個沒有容量限制的阻塞隊列來存放任務。
由于線程池的任務隊列永遠不會放滿,所以線程池只會創建核心線程數量的線程,所以此時的最大線程數對線程池來說沒有意義,因為并不會觸發生成多于核心線程數的線程。
SynchronousQueue 第二種阻塞隊列是 SynchronousQueue,對應的線程池是 CachedThreadPool。線程池 CachedThreadPool 的最大線程數是 Integer 的最大值,可以理解為線程數是可以無限擴展的。CachedThreadPool 和上一種線程池 FixedThreadPool 的情況恰恰相反,FixedThreadPool 的情況是阻塞隊列的容量是無限的,而這里 CachedThreadPool 是線程數可以無限擴展,所以 CachedThreadPool 線程池并不需要一個任務隊列來存儲任務,因為一旦有任務被提交就直接轉發給線程或者創建新線程來執行,而不需要另外保存它們。 我們自己創建使用 SynchronousQueue 的線程池時,如果不希望任務被拒絕,那么就需要注意設置最大線程數要盡可能大一些,以免發生任務數大于最大線程數時,沒辦法把任務放到隊列中也沒有足夠線程來執行任務的情況。
DelayedWorkQueue 第三種阻塞隊列是DelayedWorkQueue,它對應的線程池分別是 ScheduledThreadPool 和 SingleThreadScheduledExecutor,這兩種線程池的最大特點就是可以延遲執行任務,比如說一定時間后執行任務或是每隔一定的時間執行一次任務。
DelayedWorkQueue 的特點是內部元素并不是按照放入的時間排序,而是會按照延遲的時間長短對任務進行排序,內部采用的是“堆”的數據結構。之所以線程池 ScheduledThreadPool 和 SingleThreadScheduledExecutor 選擇 DelayedWorkQueue,是因為它們本身正是基于時間執行任務的,而延遲隊列正好可以把任務按時間進行排序,方便任務的執行。
源碼中線程池是怎么復用線程的?
源碼中ThreadPoolExecutor中有個內置對象Worker,每個worker都是一個線程,worker線程數量和參數有關,每個worker會while死循環從阻塞隊列中取數據,通過置換worker中Runnable對象,運行其run方法起到線程置換的效果,這樣做的好處是避免多線程頻繁線程切換,提高程序運行性能。
如何合理配置線程池參數
選擇的關鍵點是:
- 盡量減少線程切換和管理的開支
- 最大化利用cpu
-  這種場景適合線程盡量少,因為如果線程太多,任務執行時間段很快就執行完了,有可能出現線程切換和管理多耗費的時間,大于任務執行的時間,這樣效率就低了。線程池線程數可以設置為CPU核數+1 2.并發比較低,耗時比較長的任務: 
需要我們自己配置最大線程數 maximumPoolSize ,為了高效的并發運行,這時需要看我們的業務是IO密集型還是CPU密集型。
CPU密集型 CPU密集的意思是該任務需要最大的運算,而沒有阻塞,CPU一直全速運行。CPU密集任務只有在真正的多核CPU上才能得到加速(通過多線程)。而在單核CPU上,無論你開幾個模擬的多線程該任務都不可能得到加速,因為CPU總的運算能力就那么多。一般公式:CPU核數 + 1個線程數
IO密集型 IO密集型,即該任務需要大量的IO,即大量的阻塞。在單線程上運行IO密集型的任務會導致大量的CPU運算能力浪費在等待。所以在IO密集型任務中使用多線程可以大大的加速程序運行,即使在單核CPU上這種加速主要就是利用了被浪費掉的阻塞時間。
IO 密集型時,大部分線程都阻塞,故需要多配制線程數。公式為:
CPU核數*2 CPU核數/(1-阻塞系數) 阻塞系數在0.8~0.9之間 查看CPU核數: System.out.println(Runtime.getRuntime().availableProcessors()); 例如:8核CPU:8/ (1 - 0.9) = 80個線程數注:IO密集型(某大廠實踐經驗)核心線程數 = CPU核數 / (1-阻塞系數) 或著 CPU密集型:核心線程數 = CPU核數 + 1 IO密集型:核心線程數 = CPU核數 * 2也有說配置為cpu核數
Executor和Executors的區別?
Executors :按照需求創建了不同的線程池,來滿足業務的需求。
Executor :執行線程任務 獲得任務執行的狀態并且可以獲取任務的返回值。
使用ThreadPoolExecutor 可以創建自定義線程池。Future 表示異步計算的結果,他提供了檢查計算是否完成的方法,以等待計算的完成,并可以使用get()方法獲取計算的結果
線程池應用場景
1、異步發送郵件通知
 發送一個任務,然后注入到線程池中異步發送。
2、心跳請求任務
 創建一個任務,然后定時發送請求到線程池中。
3、如果用戶量比較大,導致占用過多的資源,可能會導致我們的服務由于資源不足而宕機;
3.1 線程池中線程的使用率提升,減少對象的創建、銷毀;
3.2 線程池可以控制線程數,有效的提升服務器的使用資源,避免由于資源不足而發生宕機等問題;
4、Elasticsearch搜索 +同步數據到索引
步驟
1、導包 做配置
2、配置Document
先要配置實體和ES的映射,通過在實體類中加入注解的方式來自動映射跟索引,我這里是配置了product索引和實體的映射
3、使用ElasticsearchRestTemplate
在Spring啟動的時候自動注入了該Bean,它封裝了操作Elasticsearch的增刪改查API
完全匹配查詢條件 --> 按照閱讀量排序—>高亮顯示—>分頁
es刷新時間
1s 為什么要延長 es主要業務寫日志
5、閱讀評論數放入Redis中
查看完文章了,新增閱讀數,做了一個更新操作,更新時加寫鎖,阻塞其他的讀操作,性能就會比較低(沒辦法解決,增加閱讀數必然要加鎖)
更新增加了此次接口的耗時(考慮減少耗時)如果一旦更新出問題,不能影響查看操作
 線程池 可以把更新操作扔到 線程池中去執行和主線程就不相關了
 threadService.updateArticleViewCount(articleMapper, article);
在這里我們采用redis incr自增實現
redis定時任務自增實現閱讀數和評論數更新
閱讀數和評論數 ,考慮把閱讀數和評論數 增加的時候 放入redis incr自增,使用定時任務 定時把數據固話到數據庫當中
定時任務 :遍歷redis中前綴是VIEW_COUNT的所有key,通過subString方法獲取文章id,獲取key存儲的閱讀數,把文章id和閱讀數放入ViewCountQuery對象中,對象放入list集合中,批量更新
@Scheduled(cron = "0 30 4 ? * *")//每天凌晨四點半觸發https://blog.csdn.net/m0_52914401/article/details/124343310?utm_medium=distribute.pc_relevant.none-task-blog-2defaultbaidujs_baidulandingword~default-0-124343310-blog-125651961.pc_relevant_recovery_v2&spm=1001.2101.3001.4242.1&utm_relevant_index=3
出現緩存不一致問題?
1、先刪除緩存,后更新數據庫
答案一:延時雙刪
(1)先淘汰緩存
(2)再寫數據庫
(3)休眠1秒,再次淘汰緩存,這么做,可以將1秒內所造成的緩存臟數據,再次刪除。確保讀請求結束,寫請求可以刪除讀請求造成的緩存臟數據。自行評估自己的項目的讀數據業務邏輯的耗時,寫數據的休眠時間則在讀數據業務邏輯的耗時基礎上,加幾百ms即可。
(我的理解:請求A先刪緩存再往DB寫數據,就算這時B來查數據庫,緩存沒數據,然后查DB,此時查到的是舊數據,寫到緩存,A等待B寫完之和再刪緩存,這樣就緩存一致)
如果使用的是 Mysql 的讀寫分離的架構的話,那么其實主從同步之間也會有時間差。
此時來了兩個請求,請求 A(更新操作) 和請求 B(查詢操作)
此時的解決辦法就是如果是對 Redis 進行填充數據的查詢數據庫操作,那么就強制將其指向主庫進行查詢。
答案二: 更新與讀取操作進行異步串行化
采用更新與讀取操作進行異步串行化
異步串行化
我在系統內部維護n個內存隊列,更新數據的時候,根據數據的唯一標識,將該操作路由之后,發送到其中一個jvm內部的內存隊列中(對同一數據的請求發送到同一個隊列)。讀取數據的時候,如果發現數據不在緩存中,并且此時隊列里有更新庫存的操作,那么將重新讀取數據+更新緩存的操作,根據唯一標識路由之后,也將發送到同一個jvm內部的內存隊列中。然后每個隊列對應一個工作線程,每個工作線程串行地拿到對應的操作,然后一條一條的執行。
這樣的話,一個數據變更的操作,先執行刪除緩存,然后再去更新數據庫,但是還沒完成更新的時候,如果此時一個讀請求過來,讀到了空的緩存,那么可以先將緩存更新的請求發送到隊列中,此時會在隊列中積壓,排在剛才更新庫的操作之后,然后同步等待緩存更新完成,再讀庫。
讀操作去重
多個讀庫更新緩存的請求串在同一個隊列中是沒意義的,因此可以做過濾,如果發現隊列中已經有了該數據的更新緩存的請求了,那么就不用再放進去了,直接等待前面的更新操作請求完成即可,待那個隊列對應的工作線程完成了上一個操作(數據庫的修改)之后,才會去執行下一個操作(讀庫更新緩存),此時會從數據庫中讀取最新的值,然后寫入緩存中。
如果請求還在等待時間范圍內,不斷輪詢發現可以取到值了,那么就直接返回;如果請求等待的時間超過一定時長,那么這一次直接從數據庫中讀取當前的舊值。(返回舊值不是又導致緩存和數據庫不一致了么?那至少可以減少這個情況發生,因為等待超時也不是每次都是,幾率很小吧。這里我想的是,如果超時了就直接讀舊值,這時候僅僅是讀庫后返回而不放緩存)
2、先更新數據庫,后刪除緩存
這一種情況也會出現問題,比如更新數據庫成功了,但是在刪除緩存的階段出錯了沒有刪除成功,那么此時再讀取緩存的時候每次都是錯誤的數據了。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ygWZDQKt-1681383922614)(https://raw.githubusercontent.com/viacheung/img/main/image/1735bb5881fb4a1b~tplv-t2oaga2asx-watermark.awebp)]
此時解決方案就是利用消息隊列進行刪除的補償。具體的業務邏輯用語言描述如下:
但是這個方案會有一個缺點就是會對業務代碼造成大量的侵入,深深的耦合在一起,所以這時會有一個優化的方案,我們知道對 Mysql 數據庫更新操作后在binlog 日志中我們都能夠找到相應的操作,那么我們可以訂閱 Mysql 數據庫的 binlog 日志對緩存進行操作。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-3LQvY5Dt-1681383922616)(https://raw.githubusercontent.com/viacheung/img/main/image/1735bb588215b298~tplv-t2oaga2asx-watermark.awebp)]
6、RabbitMQ消息隊列實現ES和數據庫的數據同步
主要是增刪改帖子
這里用的Direct隊列
- Fanout交換機將消息路由給每一個與之綁定的隊列
- Direct交換機根據RoutingKey判斷路由給哪個隊列
- 如果多個隊列具有相同的RoutingKey,則與Fanout功能類似
步驟
1、導包:spring-boot-starter-amqp
SpringAMQP是基于RabbitMQ封裝的一套模板,并且還利用SpringBoot對其實現了自動裝配,使用起來非常方便。
2、添加配置
spring: rabbitmq: host: localhost port: 5672 username: admin password: 123456 virtual-host: myHost3、RabbitMQ的配置 聲明Exchange、Queue、RoutingKey
4、生產者對應的增刪改里面發送消息
所謂的生產者就是我們數據庫的服務方,當我們對數據庫的數據進行增刪改的時候,我們應該像消息隊列發送消息來通知ES我們進行了增刪改操作,以便ES進行數據的同步。
5、消費者MQListener監聽消息
所謂的消費者就是ES服務的操作方,通過實時的對消息隊列的監聽,通過消息隊列對應的key值來進行選擇服務的調用,不同的key調用不同的服務,獲取服務方傳輸的數據,然后進行數據的同步。
7、死信交換機
目的
實現消息的延遲投遞,避免消息丟失或無限制的重試
概念
當你在消費消息時,如果隊列里的消息出現以下情況
 1,消息被否定確認,使用 channel.basicNack 或channel.basicReject ,并且此時requeue 屬性被設置為false。
 2,消息在隊列的存活時間超過設置的TTL時間。
 3,消息隊列的消息數量已經超過最大隊列長度。
那么該消息將成為“死信”。“死信”消息會被RabbitMQ進行特殊處理,如果配置了死信隊列信息,那么該消息將會被丟進死信隊列中,如果沒有配置,則該消息將會被丟棄。
步驟
大概可以分為以下步驟:
1,配置業務隊列,綁定到業務交換機上
 2,為業務隊列配置死信交換機和路由key
 3,為死信交換機配置死信隊列
為每個需要使用死信的業務隊列配置一個死信交換機,這里同一個項目的死信交換機可以共用一個,然后為每個業務隊列分配一個單獨的路由key。
有了死信交換機和路由key后,接下來,就像配置業務隊列一樣,配置死信隊列,然后綁定在死信交換機上。也就是說,死信隊列并不是什么特殊的隊列,只不過是綁定在死信交換機上的隊列。死信交換機也不是什么特殊的交換機,只不過是用來接受死信的交換機,所以可以為任何類型【Direct、Fanout、Topic】。一般來說,會為每個業務隊列分配一個獨有的路由key,并對應的配置一個死信隊列進行監聽,也就是說,一般會為每個重要的業務隊列配置一個死信隊列。
死信消息變化
如果隊列配置了參數 x-dead-letter-routing-key 的話,“死信”的路由key將會被替換成該參數對應的值。如果沒有設置,則保留該消息原有的路由key。
應用場景
確保未被正確消費的消息不被丟棄(更新刪除修改帖子)
發生消費異常可能原因:
當發生異常時,當然不能每次通過日志來獲取原消息,然后讓運維幫忙重新投遞消息(沒錯,以前就是這么干的= =)。通過配置死信隊列,可以讓未正確處理的消息暫存到另一個隊列中,待后續排查清楚問題后,編寫相應的處理代碼來處理死信消息,這樣比手工恢復數據要好太多了。
總結
死信隊列其實并沒有什么神秘的地方,不過是綁定在死信交換機上的普通隊列,而死信交換機也只是一個普通的交換機,不過是用來專門處理死信的交換機。
總結一下死信消息的生命周期:
1,業務消息被投入業務隊列
 2,消費者消費業務隊列的消息,由于處理過程中發生異常,于是進行了nck或者reject操作
 3,被nck或reject的消息由RabbitMQ投遞到死信交換機中
 4,死信交換機將消息投入相應的死信隊列
 5,死信隊列的消費者消費死信消息
 ———————————————
8、RabbitMQ如何處理消息丟失
1)生產者弄丟了數據
生產者將數據發送到rabbitmq的時候,可能因為網絡問題導致數據就在半路給搞丟了。
1.1 使用事務(不推薦)
生產者發送數據前開啟事務,然后發送消息,如果消息沒有成功被rabbitmq接收到,那么生產者會收到異常報錯,此時就可以回滾事務(channel.txRollback),然后重試發送消息;如果收到了消息,那么可以提交事務(channel.txCommit)。但是問題是,開始rabbitmq事務機制,基本上吞吐量會下來,因為太耗性能。
1.2 發送回執確認(推薦)
在生產者那里設置開啟confirm模式之后,你每次寫的消息都會分配一個唯一的id,然后如果寫入了rabbitmq中,rabbitmq會給你回傳一個ack消息,告訴你說這個消息ok了。如果rabbitmq沒能處理這個消息,會回調你一個nack接口,告訴你這個消息接收失敗,你可以重試。
但如果RabbitMQ服務端正常接收到了,把ack信息發送給生產者,結果這時網斷了:
可以結合這個機制自己在內存里維護每個消息id的狀態,如果超過一定時間還沒接收到這個消息的回調,那么你可以重發。(消費者就要處理冪等問題,多次接收到同一條消息)
區別
事務機制是同步的,你提交一個事務之后會阻塞在那兒,但是confirm機制是異步的,你發送個消息之后就可以發送下一個消息,然后那個消息rabbitmq接收了之后會異步回調你一個接口通知你這個消息接收到了。
所以一般在生產者這塊避免數據丟失,都是用confirm機制的。
2)RabbitMQ弄丟了數據-開啟RabbitMQ的數據持久化
數據持久化:rabbitmq自己掛了,恢復之后會自動讀取之前存儲的數據,一般數據不會丟。除非極其罕見的是,rabbitmq還沒持久化,自己就掛了,可能導致少量數據會丟失的,但是這個概率較小。
使用:跟生產者那邊的confirm機制配合起來,只有消息被持久化到磁盤之后,才會通知生產者ack了,所以哪怕是在持久化到磁盤之前,rabbitmq掛了,數據丟了,生產者收不到ack,你也是可以自己重發的。
步驟: 第一個是創建queue的時候將其設置為持久化,這樣就可以保證rabbitmq持久化queue的元數據。
第二個是發送消息的時候將消息的deliveryMode設置為2,就是將消息設置為持久化的
這樣abbitmq哪怕是掛了,再次重啟,也會從磁盤上重啟恢復queue,恢復這個queue里的數據。
3)消費端弄丟了數據
主要是因為你消費的時候,剛消費到,還沒處理,結果進程掛了比如重啟了,那么就尷尬了,RabbitMQ認為你都消費了,這數據就丟了。或者消費者拿到數據之后掛了,這時候需要MQ重新指派另一個消費者去執行任務
這個時候得用RabbitMQ提供的ack機制,也是一種處理完成發送回執確認的機制。如果MQ等待一段時間后你沒有發送過來處理完成 那么RabbitMQ就認為你還沒處理完,這個時候RabbitMQ會把這個消費分配給別的consumer去處理,消息是不會丟的。
https://segmentfault.com/a/1190000019125512
RabbitMQ相關知識
多線程vsMQ
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-HZDHh4XB-1681383922618)(https://raw.githubusercontent.com/viacheung/img/main/image/image-20230325004307581.png)]
為什么使用MQ?
使用MQ的場景很多,主要有三個:解耦、異步、削峰。
- 解耦:假設現在,日志不光要插入到數據庫里,還要在硬盤中增加文件類型的日志,同時,一些關鍵日志還要通過郵件的方式發送給指定的人。那么,如果按照原來的邏輯,A可能就需要在原來的代碼上做擴展,除了B服務,還要加上日志文件的存儲和日志郵件的發送。但是,如果你使用了MQ,那么,A服務是不需要做更改的,它還是將消息放到MQ中即可,其它的服務,無論是原來的B服務還是新增的日志文件存儲服務或日志郵件發送服務,都直接從MQ中獲取消息并處理即可。這就是解耦,它的好處是提高系統靈活性,擴展性。
- 異步:可以將一些非核心流程,如日志,短信,郵件等,通過MQ的方式異步去處理。這樣做的好處是縮短主流程的響應時間,提升用戶體驗。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-4vk2gmra-1681383922619)(https://raw.githubusercontent.com/viacheung/img/main/image/727602-20200108091722601-747710174.png)]
- 削峰:MQ的本質就是業務的排隊。所以,面對突然到來的高并發,MQ也可以不用慌忙,先排好隊,不要著急,一個一個來。削峰的好處就是避免高并發壓垮系統的關鍵組件,如某個核心服務或數據庫等。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Bq0Xl4Fe-1681383922620)(https://raw.githubusercontent.com/viacheung/img/main/image/727602-20200108091915241-1598228624.png)]
后面請求積壓在MQ里面 不過是短暫的
消息隊列的缺點
1、 系統可用性降低
系統引入的外部依賴越多,越容易掛掉。
2、 系統復雜度提高
加入了消息隊列,要多考慮很多方面的問題,比如:一致性問題、如何保證消息不被重復消費、如何保證消息可靠性傳輸等。因此,需要考慮的東西更多,復雜性增大。
3、 一致性問題
A 系統處理完了直接返回成功了,人都以為你這個請求就成功了;但是問題是,要是 BCD 三個系統那里,BD 兩個系統寫庫成功了,結果 C 系統寫庫失敗了,這就數據不一致了。
生產者消息運轉的流程
消費者接收消息過程?
生產者如何將消息可靠投遞到RabbitMQ?
RabbitMQ如何將消息可靠投遞到消費者?
消息冪等
根據業務特性,選取業務中唯一的某個屬性,比如訂單號作為區分消息是否重復的屬性。在進行插入訂單之前,先從數據庫查詢一下該訂單號的數據是否存在,如果存在說明是重復消費,如果不存在則插入。偽代碼如下:
我遇到的問題
1、前后端交互問題
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ODbMiyUn-1681383922622)(https://raw.githubusercontent.com/viacheung/img/main/image/image-20230322145324052.png)]
其他問題
1、TEXT 存儲文章內容
TINYTEXT(255長度)
TEXT(65535)
MEDIUMTEXT(int最大值16M)
LONGTEXT(long最大值4G)
2、Docker
Docker如何解決大型項目依賴關系復雜,不同組件依賴的兼容性問題?
- Docker允許開發中將應用、依賴、函數庫、配置一起打包,形成可移植鏡像
- Docker應用運行在容器中,使用沙箱機制,相互隔離
Docker如何解決開發、測試、生產環境有差異的問題?
- Docker鏡像中包含完整運行環境,包括系統函數庫,僅依賴系統的Linux內核,因此可以在任意Linux操作系統上運行
Docker是一個快速交付應用、運行應用的技術,具備下列優勢:
- 可以將程序及其依賴、運行環境一起打包為一個鏡像,可以遷移到任意Linux操作系統
- 運行時利用沙箱機制形成隔離容器,各個應用互不干擾
- 啟動、移除都可以通過一行命令完成,方便快捷
Docker和虛擬機的差異:
- docker是一個系統進程;虛擬機是在操作系統中的操作系統
- docker體積小、啟動速度快、性能好;虛擬機體積大、啟動速度慢、性能一般
基本概念
鏡像:
- 將應用程序及其依賴、環境、配置打包在一起
容器:
- 鏡像運行起來就是容器,一個鏡像可以運行多個容器 容器有自己獨立的cpu 內存 文件系統 避免污染鏡像
Docker結構:
-  服務端:接收命令或遠程請求,操作鏡像或容器 
-  客戶端:發送命令或者請求到Docker服務端 
DockerHub:
- 一個鏡像托管的服務器,類似的還有阿里云鏡像服務,統稱為DockerRegistry
命令
docker run命令的常見參數有哪些?
- –name:指定容器名稱
- -p:指定端口映射
- -d:讓容器后臺運行
查看容器日志的命令:
- docker logs
- 添加 -f 參數可以持續查看日志
查看容器狀態:
- docker ps
- docker ps -a 查看所有容器,包括已經停止的
數據卷
數據卷的作用:
- 將容器與數據分離,解耦合,方便操作容器內數據,保證數據安全
數據卷操作:
- docker volume create:創建數據卷
- docker volume ls:查看所有數據卷
- docker volume inspect:查看數據卷詳細信息,包括關聯的宿主機目錄位置
- docker volume rm:刪除指定數據卷
- docker volume prune:刪除所有未使用的數據卷
docker run的命令中通過 -v 參數掛載文件或目錄到容器中:
- -v volume名稱:容器內目錄
- -v 宿主機文件:容器內文件
- -v 宿主機目錄:容器內目錄
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-aErY5ZA0-1681383922623)(https://raw.githubusercontent.com/viacheung/img/main/image/image-20230403002812590.png)]
數據卷掛載與目錄直接掛載的
- 數據卷掛載耦合度低,由docker來管理目錄,但是目錄較深,不好找
- 目錄掛載耦合度高,需要我們自己管理目錄,不過目錄容易尋找查看、
dockerfile
dockercompose
Docker Compose可以基于Compose文件幫我們快速的部署分布式應用,而無需手動一個個創建和運行容器!
3、linux命令
RPC
反射:newInstance() (實例化) getDeclaredMethod(public方法) invoke(方法輸出)
蘑菇識別
上傳圖片到根目錄:根目錄 input文件夾存一下這個圖片,然后python identify.py 路徑+圖片名稱 識別出結果 各個種類的預測百分率(字符串) 對字符串進行一個處理
取前三,返回一個封裝結果,同時還支持詳細查詢蘑菇的描述
論壇知識點(業務)
文章列表
通過傳入的PageParams對象進行查詢 調用service -> mapper ->xml->sql的select查詢語句 ,傳參包括分類,標簽,年月(查詢范圍)返回IPage對象,copyList轉化格式
最熱標簽
SELECT tag_id FROM ms_article_tag group by tag_id order by count(*) desc limit #{limit}找ms_article_tag表查出對應article最多的tagid 然后再根據tagid查tag,封裝到tagvo里面 返回名稱+頭像
最熱文章
sevice層里面直接調用LambdaQueryWrapper
查詢條件構造器 lamda表達式 鏈式查詢總結
 
                            
                        - 上一篇: 论机电一体化的发展By integrat
- 下一篇: 【IXDC 2014】小米、BroadL
