1 介面服務資料被劫包如何防止資料惡意提交
1.1:防篡改
- 客戶端提交請求之前,先對自己請求的引數全部進行拼接加密得到一個加密字串sign
- 請求引數加上sign,然后再發送給服務器
- 服務器將引數獲取后也進行相同的拼接加密得到自己的sign
- 比較與客戶端發來的sign是否相同
- 不相同則是被第三方修改過的,拒絕執行
關鍵:
- 第三方不知道加密方式和請求引數拼接規則,而客戶端與服務器是知道的,因此第三方不知道修改引數后如何生成與服務器生成相同的sign
- 只要請求修改了一點點加密得到的就是不同的簽名
1.2:防重放
(1)客戶端的請求引數上加一個請求時間timestamp
原理:服務器獲取請求的timestamp,然后比較自身系統時間,如果相差超過設定時間就是超時,該請求無效
作用:就算第三方截取了該請求,它也只能在設定時間內進行重放攻擊
(2)客戶端的請求引數上加一個隨機字串string
原理:服務端獲取請求的隨機字串string,然后查看是否在設定時間內別的請求使用過該string,被使用過就判定無效
作用:加上timestamp,就造成短時間內一個請求只能使用一次,因為就算請求被攔截,它請求成功一次后,第二次復制重放時就因為隨機字串被使用而被拒絕
2 什么情況下適合使用微服務
如果你的系統不到足夠復雜的程度不要考慮使用微服務,當業務不復雜,團隊規模不大的時候,單塊架構比微服務架構具有更高的生產率因為建立微服務架構需要額外的開銷來支持和管理微服務,從而降低生產率;但是隨著業務復雜性的增加和團隊規模的擴大,單塊架構比微服務架構的生產率下降更趨明顯,當復雜性達到一個臨界點,微服務架構的生產率會優于單塊架構,因為微服務的松散耦合自治特性級訓了生產率的下降趨勢,
所以我們在做專案時,一定要根據自己的團隊和業務復雜性來判斷,何時應用微服務,
2.1 單體服務的缺點:
- 部署成本高(無論是修改1行代碼,還是10行代碼,都要全量替換)
- 改動影響大,風險高(不論代碼改動多小,成本都相同)
- 因為成本高,風險高,所以導致部署頻率低(無法快速交付客戶需求)
2.2 微服務架構的特點:
優點:
針對特定服務發布,影響小,風險小,成本低
頻繁發布版本,快速交付需求
低成本擴容,彈性伸縮,適應云環境
缺點:
分布式系統的復雜性
部署,測驗和監控的成本問題
分布式事務和CAP的相關問題
3 集群環境中,session如何實作共享
3.1 會話保持
使用一個固定的服務器專門保持session,其他服務器共享,Session保持(會話保持)是我們見到最多的名詞之一,通過會話保持,負載均衡進行請求分發的時候保證每個客戶端固定的訪問到后端的同一臺應用服務器,會話保持方案在所有的負載均衡都有對應的實作,而且這是在負載均衡這一層就可以解決Session問題,
3.2 會話復制
會話復制在Tomcat上得到了支持,它是基于IP組播(multicast)來完成Session的復制,Tomcat的會話復制分為兩種:
全域會話復制:利用Delta Manager復制會話中的變更資訊到集群中的所有其他節點,
非全域復制:使用Backup Manager進行復制,它會把Session復制給一個指定的備份節點,
主要是因為會話復制不適合大的集群,根據筆者在生產的實踐案例,當時是在集群超過6個節點之后就會出現各種問題,不推薦生產使用,
3.3 會話共享
Session存放到哪里?
對于Session來說,肯定是頻繁使用的,雖然你可以把它存放在資料庫中,但是真正生產環境中我更推薦存放在性能更快的分布式KV資料中,例如:Memcached和Redis,
3.4會話保持/會話復制/會話共享區別:
會話保持的缺點: ①負載不均衡了 ②沒有徹底解決問題
會話復制的缺點: 集群超過6個節點就會出現一系列的問題
會話共享:會話資料共享在Nosql(Redis)資料庫中分享,
3.5 session實作原理
當服務器創建完session物件后,會把session物件的id以cookie形式回傳給客戶端,這樣,當用戶保持當前瀏覽器的情況下再去訪問服務器時,會把session的id傳給服務器,服務器根據session的id來為用戶提供相應的服務
session是服務端存盤,cookie是瀏覽器端存盤
Cookie是把用戶的資料寫給用戶的瀏覽器,
Session技術把用戶的資料寫到用戶獨占的session中,
Session物件由服務器創建,開發人員可以呼叫request物件的getSession方法得到session物件,
4 資料結構
4.1 線性表
線性表是由N個元素組成的有序系列,也是最常見的一種資料結構,重點有陣列和鏈表
4.1.1 陣列
陣列是一種存盤單元連續,用來存盤固定大小元素的線性表,陣列靜態分配記憶體,在記憶體中連續,利用下標定位,時間復雜度為O(1),插入或洗掉元素的時間復雜度O(n),Java中對應的集合實作,比如ArrayList
4.1.2 鏈表
鏈表又分為單鏈表和雙鏈表,是在物理存盤單元上非連續,非順序的存盤結構,資料元素的邏輯順序是通過鏈表中指標的鏈接次序實作的,鏈表動態分配記憶體,不連續,定位元素時間復雜度O(n),插入或洗掉元素時間復雜度O(1),Java中對應的集合實作,比如linkdeList.
4.1.3 陣列和鏈表的優缺點
陣列隨機訪問性強(通過下標進行快速定位),查找速度快,但插入和洗掉效率低(插入和洗掉需要移動資料);可能浪費記憶體(因為是連續的,所以每次申請陣列之前必須規定陣列的大小,如果大小不合理,則可能會浪費記憶體),記憶體空間要求高,必須有足夠的連續記憶體空間;陣列大小固定,不能動態拓展,
鏈表插入洗掉速度快(因為有next指標指向其下一個節點,通過改變指標的指向可以方便的增加洗掉元素);記憶體利用率高,不會浪費記憶體(可以使用記憶體中細小的不連續空間(大于node節點的大小),并且在需要空間的時候才創建空間),大小沒有固定,拓展很靈活,不能隨機查找,必須從第一個開始遍歷,查找效率低,
4.1.4 單鏈表和雙鏈表的特點和優缺點


特點:單鏈表只有一個指向下一結點的指標,也就是只能next;雙鏈表除了有一個指向下一結點的指標外,還有一個指向前一結點的指標,可以通過prev()快速找到前一結點,顧名思義,單鏈表只能單向讀取,
優缺點:1、洗掉單鏈表中的某個結點時,一定要得到待洗掉結點的前驅,得到該前驅有兩種方法,第一種方法是在定位待洗掉結點的同時一路保存當前結點的前驅,第二種方法是在定位到待洗掉結點之后,重新從單鏈表表頭開始來定位前驅,盡管通常會采用方法一,但其實這兩種方法的效率是一樣的,指標的總的移動操作都會有2*i次,而如果用雙向鏈表,則不需要定位前驅結點,因此指標總的移動操作為i次,
2、查找時也一樣,我們可以借用二分法的思路,從head(首節點)向后查找操作和last(尾節點)向前查找操作同步進行,這樣雙鏈表的效率可以提高一倍,
可是為什么市場上單鏈表的使用多余雙鏈表呢?
從存盤結構來看,每個雙鏈表的節點要比單鏈表的節點多一個指標,而長度為n就需要 n*length(這個指標的length在32位系統中是4位元組,在64位系統中是8個位元組) 的空間,這在一些追求時間效率不高應用下并不適應,因為它占用空間大于單鏈表所占用的空間;這時設計者就會采用以時間換空間的做法,這時一種工程總體上的衡量,
4.2 堆疊與佇列
4.2.1 堆疊
堆疊是一種運算受限的線性表,重點掌握其先進后出的特點,表的末端叫堆疊頂,基本操作有push(進堆疊)和pop(出堆疊),Java中的stack就是簡單的堆疊實作
4.2.2 佇列
佇列也是一種操作受限的線性表,重點掌握其先進先出的特點,表的前端只允許進行洗掉的操作,表的后端進行插入的操作,進行插入操作的端稱為隊尾,進行洗掉操作的稱為隊頭,Java中很多queue的實作,訊息中間件的佇列本質也是基于此,
4.3 樹
非線性結構里面,樹是非常非常重要的一種資料結構,基于本身的結構優勢,尤其在查找領域,應用廣泛,其中又以二叉樹最為重要,
4.3.1二叉搜索樹
二叉搜索樹又叫二叉查找樹,又叫二叉排序樹,性質如下:1 若左子樹不空,則左子樹上所有結點的值均小于它根節點的值;2 若右子樹不空,則右子樹上所有結點的值均大于它的根節點的值;3 左右子樹也分別為二叉排序樹;4 沒有鍵值相等的結點
4.3.2 平衡二叉樹
平衡二叉樹又叫AVL樹,性質如下:它的左子樹和右子樹都是平衡二叉樹,且左子樹和右子樹的深度之差的絕對值不超過1
4.3.3 紅黑樹
紅黑樹是一種特殊的平衡二叉樹,它保證在最壞情況下基本動態集合操作的事件復雜度為O(log n),紅黑樹放棄了追求完全平衡,追求大致平衡,在與平衡二叉樹的時間復雜度相差不大的情況下,保證每次插入最多只需要三次旋轉就能達到平衡,實作起來也更為簡單,平衡二叉樹追去絕對平衡,條件比較苛刻,實作起來比較麻煩,每次插入新節點之后需要旋轉的次數不能預知,
5 經典演算法題
5.1 兔子數量
有一對兔子,從出生后第三個月其每個月都生一對兔子,小兔子長到第三個月后每個月又生一對兔子,假如兔子都不死,沒問每個月兔子的總數為對少,
5.2 素數
判斷101-200之間有多少個素數,并輸出所有素數,
5.3 水仙花數
列印出所有的”水仙花數”,是指一個三位數,其各位數字的立方之和等于該數本身,例如153是一個水仙花數,因為153=1的三次+5的三次方+3的三次方
5.4 質因數
將一個正整數分解質因數,例如90=2*3*3*5
5.5 條件運算子
利用條件運算子的嵌套完成此題,學習成績>=90的同學用A表示,60-89分之間用B表示,60分以下用C表示
5.6 最大公約數和最小公倍數
輸入兩個正整數m和n,求其最大公約數和最小公倍數
5.7 字串分割
輸入一行字符,分別統計出其中英文、空格、數字和其它字符的個數
5.8 鍵盤輸入值讀取
求S=a+aa+aaa+aaaa+aa..aa的值,其中a是一個數字,幾個數相加由鍵盤控制
5.9 完數
一個數如果恰好等于它的因子之和,這個數就稱為’完數’,例如6=1+2+3.找出1000以內的所有完數,
5.10 反彈球
一球從100目高度自由落下,每次落地之后反跳回原來高度的一半再落下,其它在第10次落地時,共經過多宣告,第10次反彈多高,
6 Java記憶體模型
執行緒之間的共享變數存盤在主記憶體(main memory)中,每個執行緒都有一個私有的本地記憶體(local memory),本地記憶體中存盤了該執行緒以讀/寫共享變數的副本.

從上圖來看,執行緒A與執行緒B之間如要通信的話,必須要經歷下面2個步驟:
1. 首先,執行緒A把本地記憶體A中更新過的共享變數重繪到主記憶體中去,
2. 然后,執行緒B到主記憶體中去讀取執行緒A之前已更新過的共享變數,
6.1 執行緒之間的通信
執行緒的通信是指執行緒之間以何種機制來交換資訊,
在共享記憶體的并發模型里,執行緒之間共享程式的公共狀態,執行緒之間通過寫-讀記憶體中的公共狀態來隱式進行通信,典型的共享記憶體通信方式就是通過共享物件進行通信,
在訊息傳遞的并發模型里,執行緒之間沒有公共狀態,執行緒之間必須通過明確的發送訊息來顯式進行通信,在java中典型的訊息傳遞方式就是wait()和notify(),
6.2執行緒之間的同步
同步是指程式用于控制不同執行緒之間操作發生相對順序的機制,在共享記憶體并發模型里,同步是顯式進行的,程式員必須顯式指定某個方法或某段代碼需要在執行緒之間互斥執行,在訊息傳遞的并發模型里,由于訊息的發送必須在訊息的接收之前,因此同步是隱式進行的,
6.3 JVM對Java記憶體模型的實作

在JVM內部,Java記憶體模型把記憶體分成了兩部分:執行緒堆疊區和堆區.,
執行緒堆疊包含了當前執行緒執行的方法呼叫相關資訊;還包含了當前方法的所有本地變數資訊,一個執行緒只能讀取自己的執行緒堆疊,也就是說,執行緒中的本地變數對其它執行緒是不可見的,即使兩個執行緒執行的是同一段代碼,它們也會各自在自己的執行緒堆疊中創建本地變數,因此,每個執行緒中的本地變數都會有自己的版本,
所有原始型別(boolean,byte,short,char,int,long,float,double)的本地變數都直接保存在執行緒堆疊當中,對于它們的值各個執行緒之間都是獨立的,對于原始型別的本地變數,一個執行緒可以傳遞一個副本給另一個執行緒,當它們之間是無法共享的,
堆區包含了Java應用創建的所有物件資訊,不管物件是哪個執行緒創建的,其中的對象包括原始型別的封裝類(如Byte、Integer、Long等等),不管物件是屬于一個成員變數還是方法中的本地變數,它都會被存盤在堆區,對于一個物件的成員方法,這些方法中包含本地變數,仍需要存盤在堆疊區,即使它們所屬的物件在堆區, 對于一個物件的成員變數,不管它是原始型別還是包裝型別,都會被存盤到堆區,Static型別的變數以及類本身相關資訊都會隨著類本身存盤在堆區,
6.4 硬體記憶體架構

現代計算機一般都有2個以上CPU,而且每個CPU還有可能包含多個核心,因此,如果我們的應用是多執行緒的話,這些執行緒可能會在各個CPU核心中并行運行,
在CPU內部有一組CPU暫存器,也就是CPU的儲存器,CPU操作暫存器的速度要比操作計算機主存快的多,在主存和CPU暫存器之間還存在一個CPU快取,CPU操作CPU快取的速度快于主存但慢于CPU暫存器,某些CPU可能有多個快取層(一級快取和二級快取),計算機的主存也稱作RAM,所有的CPU都能夠訪問主存,而且主存比上面提到的快取和暫存器大很多,
當一個CPU需要訪問主存時,會先讀取一部分主存資料到CPU快取,進而在讀取CPU快取到暫存器,當CPU需要寫資料到主存時,同樣會先flush暫存器到CPU快取,然后再在某些節點把快取資料flush到主存,
6.5共享物件的可見性
當多個執行緒同時操作同一個共享物件時,如果沒有合理的使用volatile和synchronization關鍵字,一個執行緒對共享物件的更新有可能導致其它執行緒不可見,
想象一下我們的共享物件存盤在主存,一個CPU中的執行緒讀取主存資料到CPU快取,然后對共享物件做了更改,但CPU快取中的更改后的物件還沒有flush到主存,此時執行緒對共享物件的更改對其它CPU中的執行緒是不可見的,最終就是每個執行緒最終都會拷貝共享物件,而且拷貝的物件位于不同的CPU快取中,下圖展示了上面描述的程序,左邊CPU中運行的執行緒從主存中拷貝共享物件obj到它的CPU快取,把物件obj的count變數改為2,但這個變更對運行在右邊CPU中的執行緒不可見,因為這個更改還沒有flush到主存中: 要解決共享物件可見性這個問題,我們可以使用java volatile關鍵字, Java’s volatile keyword. volatile 關鍵字可以保證變數會直接從主存讀取,而對變數的更新也會直接寫到主存,volatile原理是基于CPU記憶體屏障指令實作的

6.6競爭現象
如果多個執行緒共享一個物件,如果它們同時修改這個共享物件,這就產生了競爭現象,如下圖所示,執行緒A和執行緒B共享一個物件obj,假設執行緒A從主存讀取Obj.count變數到自己的CPU快取,同時,執行緒B也讀取了Obj.count變數到它的CPU快取,并且這兩個執行緒都對Obj.count做了加1操作,此時,Obj.count加1操作被執行了兩次,不過都在不同的CPU快取中,如果這兩個加1操作是串行執行的,那么Obj.count變數便會在原始值上加2,最終主存中的Obj.count的值會是3,然而下圖中兩個加1操作是并行的,不管是執行緒A還是執行緒B先flush計算結果到主存,最終主存中的Obj.count只會增加1次變成2,盡管一共有兩次加1操作, 要解決上面的問題我們可以使用java synchronized代碼塊,synchronized代碼塊可以保證同一個時刻只能有一個執行緒進入代碼競爭區,synchronized代碼塊也能保證代碼塊中所有變數都將會從主存中讀,當執行緒退出代碼塊時,對所有變數的更新將會flush到主存,不管這些變數是不是volatile型別的,

7 網路IO模型
7.1同步與異步
同步與異步是用戶執行緒與內核的互動方式,同步指用戶執行緒發起IO請求后需要等待或者輪詢內核IO操作完成后才能繼續執行;異步是用戶執行緒發起IO請求后仍然繼續執行,當內核IO操作完成后會通知用戶執行緒,或者呼叫用戶執行緒注冊的回呼函式,
7.2阻塞與非阻塞
阻塞與非阻塞是用戶執行緒呼叫內核IO的操作方式,阻塞是IO操作需要徹底完成后才回傳到用戶空間;非阻塞是IO操作被呼叫后立即回傳給用戶一個狀態值,無需等待IO操作徹底完成,
7.2.1阻塞IO
阻塞IO是指呼叫了read后,必須等待資料到達,并且復制到了用戶空間,才能回傳,否則整個執行緒一直在等待,所以阻塞IO的問題就是,執行緒在讀寫IO的時候不能干其它的事情,

7.2.2非阻塞IO
非阻塞IO在呼叫read后,可以立刻回傳,然后問作業系統,資料有沒有在內核空間準備好,如果準備好了,就可以read出來了,因為不知道什么時候準備好,要保證實時性,就得不斷的輪詢,

7.2.3 IO多路復用(非阻塞IO)
在使用非阻塞IO的時候,如果每個執行緒訪問網路后都不停的輪詢,那么這個執行緒就被占用了,那跟阻塞IO也沒什么區別了,每個執行緒都輪詢自己的socket,這些執行緒也不能干其它的事情,如果能有一個專門的執行緒去輪詢所有的socket,如果資料準備好,就找一個執行緒處理,這就是IO多路復用,當然輪詢的執行緒也可以不用找其他執行緒處理,自己處理就行,例如redis就是這樣的,
IO多路復用,能夠讓一個或幾個執行緒去管理很多個(可以成千上萬)socket連接,這樣連接數就不再受限于系統能啟動的執行緒數,我們把select輪詢抽出來放在一個執行緒里, 用戶執行緒向其注冊相關socket或IO請求,等到資料到達時通知用戶執行緒,則可以提高用戶執行緒的CPU利用率.這樣, 便實作了異步方式,

8 訊息中間件如何保證訊息不丟失

8.1生產者沒有成功把訊息發送到MQ
a、丟失的原因:因為網路傳輸的不穩定性,當生產者在向MQ發送訊息的程序中,MQ沒有成功接收到訊息,但是生產者卻以為MQ成功接收到了訊息,不會再次重復發送該訊息,從而導致訊息的丟失,
b、解決辦法: 有兩個解決辦法:事務機制和confirm機制,最常用的是confirm機制,
事務機制:
RabbitMQ 提供了事務功能,生產者發送資料之前開啟 RabbitMQ 事務channel.txSelect,然后發送訊息,如果訊息沒有成功被 RabbitMQ 接收到,那么生產者會收到例外報錯,此時就可以回滾事務channel.txRollback,然后重試發送訊息;如果收到了訊息,那么可以提交事務channel.txCommit,偽代碼如下:
// 開啟事務
channel.txSelect
try {
// 這里發送訊息
} catch (Exception e) {
channel.txRollback
// 這里再次重發這條訊息
}
// 提交事務
channel.txCommit;
confirm機制:
RabbitMQ可以開啟 confirm 模式,在生產者那里設定開啟 confirm 模式之后,生產者每次寫的訊息都會分配一個唯一的 id,如果訊息成功寫入 RabbitMQ 中,RabbitMQ 會給生產者回傳一個 ack 訊息,告訴你說這個訊息 ok 了,如果 RabbitMQ 沒能處理這個訊息,會回呼你的一個 nack 介面,告訴你這個訊息接收失敗,生產者可以發送,而且你可以結合這個機制自己在記憶體里維護每個訊息 id 的狀態,如果超過一定時間還沒接收到這個訊息的回呼,那么可以重發,
注意:RabbitMQ的事務機制是同步的,很耗型能,會降低RabbitMQ的吞吐量,confirm機制是異步的,生成者發送完一個訊息之后,不需要等待RabbitMQ的回呼,就可以發送下一個訊息,當RabbitMQ成功接收到訊息之后會自動異步的回呼生產者的一個介面回傳成功與否的訊息,
8.2 RabbitMQ接收到訊息之后丟失了訊息
a、丟失的原因:RabbitMQ接收到生產者發送過來的訊息,是存在記憶體中的,如果沒有被消費完,此時RabbitMQ宕機了,那么再次啟動的時候,原來記憶體中的那些訊息都丟失了,
b、解決辦法:開啟RabbitMQ的持久化,當生產者把訊息成功寫入RabbitMQ之后,RabbitMQ就把訊息持久化到磁盤,結合上面的說到的confirm機制,只有當訊息成功持久化磁盤之后,才會回呼生產者的介面回傳ack訊息,否則都算失敗,生產者會重新發送,存入磁盤的訊息不會丟失,就算RabbitMQ掛掉了,重啟之后,他會讀取磁盤中的訊息,不會導致訊息的丟失,
c、持久化的配置:
第一點是創建 queue 的時候將其設定為持久化,這樣就可以保證 RabbitMQ 持久化 queue 的元資料,但是它是不會持久化 queue 里的資料的,
第二個是發送訊息的時候將訊息的 deliveryMode 設定為 2,就是將訊息設定為持久化的,此時 RabbitMQ 就會將訊息持久化到磁盤上去,
注意:持久化要起作用必須同時設定這兩個持久化才行,RabbitMQ 哪怕是掛了,再次重啟,也會從磁盤上重啟恢復 queue,恢復這個 queue 里的資料,
8.3 消費者弄丟了訊息
a、丟失的原因:如果RabbitMQ成功的把訊息發送給了消費者,那么RabbitMQ的ack機制會自動的回傳成功,表明發送訊息成功,下次就不會發送這個訊息,但如果就在此時,消費者還沒處理完該訊息,然后宕機了,那么這個訊息就丟失了,
b、解決的辦法:簡單來說,就是必須關閉 RabbitMQ 的自動 ack,可以通過一個 api 來呼叫就行,然后每次在自己代碼里確保處理完的時候,再在程式里 ack 一把,這樣的話,如果你還沒處理完,不就沒有 ack了?那 RabbitMQ 就認為你還沒處理完,這個時候 RabbitMQ 會把這個消費分配給別的 consumer 去處理,訊息是不會丟的,
9 資料庫SQL調優方式
9.1 創建索引
要盡量避免全表掃描,首先應考慮在 where 及 order by 涉及的列上建立索,在經常需要進行檢索的欄位上創建索引;一個表的索引數最好不要超過6個,索引固然可以提高相應的 select 的效率,但同時也降低了 insert 及 update 的效率,因為 insert 或 update 時有可能會重建索引,所以怎樣建索引需要慎重考慮,視具體情況而定,避免在索引上使用計算.
9.2 使用預編譯查詢
程式中通常是根據用戶的輸入來動態執行SQL,這時應該盡量使用引數化SQL,這樣不僅可以避免SQL注入漏洞攻擊,最重要資料庫會對這些引數化SQL進行預編譯,這樣第一次執行的時候DBMS會為這個SQL陳述句進行查詢優化并且執行預編譯,這樣以后再執行這個SQL的時候就直接使用預編譯的結果,這樣可以大大提高執行的速度,
9.3盡量將多條SQL陳述句壓縮到一句SQL中
每次執行SQL的時候都要建立網路連接、進行權限校驗、進行SQL陳述句的查詢優化、發送執行結果,這個程序 是非常耗時的,因此應該盡量避免過多的執行SQL陳述句,能夠壓縮到一句SQL執行的陳述句就不要用多條來執行,
9.4 .考慮使用’臨時表’暫存中間結果
簡化SQL陳述句的重要方法就是采用臨時表暫存中間結果,但是,臨時表的好處遠遠不止這些,將臨時結果暫存在臨時表,后面的查詢就在tempdb中了,這可以避免程式中多次掃描主表,也大大減少了程式執行中“共享鎖”阻塞“更新鎖”,減少了阻塞,提高了并發性能,
但是也得避免頻繁創建和洗掉臨時表,以減少系統表資源的消耗,
9.5盡量避免使用游標
盡量避免向客戶端回傳大資料量,若資料量過大,應該考慮相應需求是否合理,因為游標的效率較差,如果游標操作的資料超過1萬行,那么就應該考慮改寫,
9.6 sql關鍵字的慎用
1 事務開始和提交
只在必要的情況下才使用事務begin translation 和commit,SQL陳述句默認就是一個事務,在該陳述句執行完成后也是默認commit的,在保證資料一致性的前提下,egin translation套住的SQL陳述句越少越好!有些情況下可以采用觸發器同步資料,
2 用varchar/nvarchar 代替 char/nchar
盡可能的使用 varchar/nvarchar 代替 char/nchar ,因為首先變長欄位存盤空間小,可以節省存盤空間,其次對于查詢來說,在一個相對較小的欄位內搜索效率顯然要高些
3 用where字句替換HAVING字句
避免使用HAVING字句,因為HAVING只會在檢索出所有記錄之后才對結果集進行過濾,而where則是在聚合前 刷選記錄,如果能通過where字句限制記錄的數目,那就能減少這方面的開銷,HAVING中的條件一般用于聚合函式的過濾
4 用union all替換union
當SQL陳述句需要union兩個查詢結果集合時,即使檢索結果中不會有重復的記錄,如果使用union這兩個結果集 同樣會嘗試進行合并,然后在輸出最終結果前進行排序,因此如果可以判斷檢索結果中不會有重復的記錄時候,應該用union all,這樣效率就會因此得到提高,
5 其他
查詢條件不用is null的判斷;不用like查詢條件
10 spring 中使用到的設計模式
10.1代理模式(Proxy)
為其他物件提供一種代理以控制對這個物件的訪問, 從結構上來看和Decorator模式類似,但Proxy是控制,更像是一種對功能的限制,而Decorator是增加職責, spring的Proxy模式在aop中有體現,比如JdkDynamicAopProxy和Cglib2AopProxy,AOP能夠將那些與業務無關,卻為業務模塊所共同呼叫的邏輯或責任(例如事務處理、日志管理、權限控制等)封裝起來,便于減少系統的重復代碼,降低模塊間的耦合度,并有利于未來的可拓展性和可維護性,Spring AOP就是基于動態代理的,如果要代理的物件實作了某個介面,那么Spring AOP會使用JDK Proxy,去創建代理物件,而對于沒有實作介面的物件,Spring AOP會使用Cglib,這時候Spring AOP會使用Cglib生成一個被代理物件的子類來作為代理,如下圖所示:

10.2觀察者模式(Observer)
定義物件間的一種一對多的依賴關系,當一個物件的狀態發生改變時,所有依賴于它的物件都得到通知并被自動更新,spring中Observer模式常用的地方是listener的實作,如ApplicationListener,
定義物件間的一種一對多的依賴關系,當一個物件的狀態發生改變時,所有依賴于它的物件都得到通知并被自動更新,spring中Observer模式常用的地方是listener的實作,如ApplicationListener, 觀察者模式是一種物件行為型模式,它表示的是一種物件與物件之間具有依賴關系,當一個物件發生改變的時候,這個物件所依賴的物件也會做出反應,Spring 事件驅動模型就是觀察者模式很經典的一個應用,Spring 事件驅動模型非常有用,在很多場景都可以解耦我們的代碼,比如我們每次添加商品的時候都需要重新更新商品索引,這個時候就可以利用觀察者模式來解決這個問題,
Spring 的事件流程總結:
定義一個事件: 實作一個繼承自 ApplicationEvent,并且寫相應的建構式;
定義一個事件監聽者:實作 ApplicationListener 介面,重寫 onApplicationEvent() 方法;
使用事件發布者發布訊息: 可以通過 ApplicationEventPublisher 的 publishEvent() 方法發布訊息,
代碼例子:
// 定義一個事件,繼承自ApplicationEvent并且寫相應的建構式
public class DemoEvent extends ApplicationEvent{
private static final long serialVersionUID = 1L;
private String message;
public DemoEvent(Object source,String message){
super(source);
this.message = message;
}
public String getMessage() {
return message;
}
// 定義一個事件監聽者,實作ApplicationListener介面,重寫 onApplicationEvent() 方法;
@Component
public class DemoListener implements ApplicationListener<DemoEvent>{
//使用onApplicationEvent接收訊息
@Override
public void onApplicationEvent(DemoEvent event) {
String msg = event.getMessage();
System.out.println("接收到的資訊是:"+msg);
}
}
// 發布事件,可以通過ApplicationEventPublisher 的 publishEvent() 方法發布訊息,
@Component
public class DemoPublisher {
@Autowired
ApplicationContext applicationContext;
public void publish(String message){
//發布事件
applicationContext.publishEvent(new DemoEvent(this, message));
}
}
當呼叫 DemoPublisher 的 publish() 方法的時候,比如 demoPublisher.publish("你好") ,控制臺就會列印出:接收到的資訊是:你好 ,
10.3策略模式(Strategy)
定義一系列的演算法,把它們一個個封裝起來,并且使它們可相互替換,本模式使得演算法可獨立于使用它的客戶而變化, spring中在實體化物件的時候用到Strategy模式
形象的稱為就是一個介面在不同場景下有多個實作類,客戶端根據不同場景呼叫對應的實作類,
和工廠模式的比較:
1.工廠模式封裝的是物件,策略模式封裝的是演算法
2.工廠模式可能需要將工廠和產品都暴露給呼叫方,因為呼叫方可能會用到產品的不同方面,但是策略模式,只需要將context暴露給呼叫方,其內部演算法對呼叫方不可見,不需要將演算法子類暴露出去,因為這些演算法,本質上都是完成同一件事的不同方法,
10.4 包裝器模式(Decorator)
裝飾者模式可以動態地給物件添加一些額外的屬性或行為,相比于使用繼承,裝飾者模式更加靈活,簡單點兒說就是當我們需要修改原有的功能,但我們又不愿直接去修改原有的代碼時,設計一個Decorator套在原有代碼外面
在我們的專案中遇到這樣一個問題:我們的專案需要連接多個資料庫,而且不同的客戶在每次訪問中根據需要會去訪問不同的資料庫,我們以往在spring和hibernate框架中總是配置一個資料源,因而sessionFactory的dataSource屬性總是指向這個資料源并且恒定不變,所有DAO在使用sessionFactory的時候都是通過這個資料源訪問資料庫,但是現在,由于專案的需要,我們的DAO在訪問sessionFactory的時候都不得不在多個資料源中不斷切換,問題就出現了:如何讓sessionFactory在執行資料持久化的時候,根據客戶的需求能夠動態切換不同的資料源?我們能不能在spring的框架下通過少量修改得到解決?是否有什么設計模式可以利用呢? 首先想到在spring的applicationContext中配置所有的dataSource,這些dataSource可能是各種不同型別的,比如不同的資料庫:Oracle、SQL Server、MySQL等,也可能是不同的資料源:比如apache 提供的org.apache.commons.dbcp.BasicDataSource、spring提供的org.springframework.jndi.JndiObjectFactoryBean等,然后sessionFactory根據客戶的每次請求,將dataSource屬性設定成不同的資料源,以到達切換資料源的目的,spring中用到的包裝器模式在類名上有兩種表現:一種是類名中含有Wrapper,另一種是類名中含有Decorator,基本上都是動態地給一個物件添加一些額外的職責,
10.5配接器模式(Adapter)
配接器模式(Adapter Pattern) 將一個介面轉換成客戶希望的另一個介面,配接器模式使介面不兼容的那些類可以一起作業,其別名為包裝器(Wrapper),
spring AOP中的配接器模式
我們知道 Spring AOP 的實作是基于代理模式,但是 Spring AOP 的增強或通知(Advice)使用到了配接器模式,與之相關的介面是AdvisorAdapter ,Advice 常用的型別有:BeforeAdvice(目標方法呼叫前,前置通知)、AfterAdvice(目標方法呼叫后,后置通知)、AfterReturningAdvice(目標方法執行結束后,return之前)等等,每個型別Advice(通知)都有對應的攔截器:MethodBeforeAdviceInterceptor、AfterReturningAdviceAdapter、AfterReturningAdviceInterceptor,Spring預定義的通知要通過對應的配接器,適配成 MethodInterceptor介面(方法攔截器)型別的物件(如:MethodBeforeAdviceInterceptor 負責適配 MethodBeforeAdvice),
spring MVC中的配接器模式
在Spring MVC中,DispatcherServlet 根據請求資訊呼叫 HandlerMapping,決議請求對應的 Handler,決議到對應的 Handler(也就是我們平常說的 Controller 控制器)后,開始由HandlerAdapter 配接器處理,HandlerAdapter 作為期望介面,具體的配接器實作類用于對目標類進行適配,Controller 作為需要適配的類,
為什么要在 Spring MVC 中使用配接器模式? Spring MVC 中的 Controller 種類眾多,不同型別的 Controller 通過不同的方法來對請求進行處理,如果不利用配接器模式的話,DispatcherServlet 直接獲取對應型別的 Controller,需要的自行來判斷,像下面這段代碼一樣:
if(mappedHandler.getHandler() instanceof MultiActionController){
((MultiActionController)mappedHandler.getHandler()).xxx
}else if(mappedHandler.getHandler() instanceof XXX){
}else if(...){
}
假如我們再增加一個 Controller型別就要在上面代碼中再加入一行 判斷陳述句,這種形式就使得程式難以維護,也違反了設計模式中的開閉原則 – 對擴展開放,對修改關閉,
10.6單例模式(Singleton)
保證一個類僅有一個實體,并提供一個訪問它的全域訪問點, spring中的單例模式完成了后半句話,即提供了全域的訪問點BeanFactory,但沒有從構造器級別去控制單例,這是因為spring管理的是是任意的java物件,
形象的稱為就是一個介面在不同場景下有多個實作類,客戶端根據不同場景呼叫對應的實作類,
10.7工廠方法模式(Factory Method)
通常由應用程式直接使用new創建新的物件,為了將物件的創建和使用相分離,采用工廠模式,即應用程式將物件的創建及初始化職責交給工廠物件,
一般情況下,應用程式有自己的工廠物件來創建bean.如果將應用程式自己的工廠物件交給Spring管理,那么Spring管理的就不是普通的bean,而是工廠Bean
10.8簡單工廠模式
又叫做靜態工廠方法(StaticFactory Method)模式,但不屬于23種GOF設計模式之一,
簡單工廠模式的實質是由一個工廠類根據傳入的引數,動態決定應該創建哪一個產品類,
spring中的BeanFactory就是簡單工廠模式的體現,根據傳入一個唯一的標識來獲得bean物件,但是否是在傳入引數后創建還是傳入引數前創建這個要根據具體情況來定
10.9模板方法(Template Method)
模板方法模式是一種行為設計模式,它定義一個操作中的演算法的骨架,而將一些步驟延遲到子類中, 模板方法使得子類可以不改變一個演算法的結構即可重定義該演算法的某些特定步驟的實作方式,Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 結尾的對資料庫操作的類,它們就使用到了模板模式,一般情況下,我們都是使用繼承的方式來實作模板模式,但是 Spring 并沒有使用這種方式,而是使用Callback 模式與模板方法模式配合,既達到了代碼復用的效果,同時增加了靈活性,
11 JVM
11.1 jvm結構

JVM包含兩個子系統和兩個組件,兩個子系統為Class loader(類裝載)、Execution Engine(執行引擎);兩個組件為Runtime Data Area(運行時資料區)、Native Interface(本地介面),
Class loader(類裝載):根據給定的全限定名類名(如:java.lang.Object)來裝載Class檔案到Runtime Data Area中的Method Area,
Execution Engine(執行引擎):執行Classes中的指令,
Native Interface(本地介面):與Native Libraries互動,是其它編程語言互動的介面,
Runtime Data Area(運行時資料區域):這就是我們常說的JVM的記憶體,
加載程序:首先通過編譯器把 Java 代碼轉換成位元組碼,類加載器(ClassLoader)再把位元組碼加載到記憶體中,將其放在運行時資料區(Runtime data area)的方法區內,而位元組碼檔案只是 JVM 的一套指令集規范,并不能直接交給底層作業系統去執行,因此需要特定的命令決議器執行引擎(Execution Engine),將位元組碼翻譯成底層系統指令,再交由 CPU 去執行,
而這個程序中需要呼叫其他語言的本地庫介面(Native Interface)來實作整個程式的功能,
11.2 jvm資料存盤區域
程式計數器(Program Counter Register):當前執行緒所執行的位元組碼的行號指示器,位元組碼決議器的作業是通過改變這個計數器的值,來選取下一條需要執行的位元組碼指令,分支、回圈、跳轉、例外處理、執行緒恢復等基礎功能,都需要依賴這個計數器來完成;
Java 虛擬機堆疊(Java Virtual Machine Stacks):用于存盤區域變數表、運算元堆疊、動態鏈接、方法出口等資訊;
本地方法堆疊(Native Method Stack):與虛擬機堆疊的作用是一樣的,只不過虛擬機堆疊是服務 Java 方法的,而本地方法堆疊是為虛擬機呼叫 Native 方法服務的;
Java 堆(Java Heap):Java 虛擬機中記憶體最大的一塊,是被所有執行緒共享的,幾乎所有的物件實體都在這里分配記憶體;
方法區(Methed Area):用于存盤已被虛擬機加載的類資訊、常量、靜態變數、即時編譯后的代碼等資料,
11.3 JAVA記憶體泄漏
記憶體泄漏是指不再被使用的物件或者變數一直被占據在記憶體中,長生命周期的物件持有短生命周期物件的參考就很可能發生記憶體泄露,盡管短生命周期物件已經不再需要,但是因為長生命周期物件持有它的參考而導致不能被回收,這就是Java中記憶體泄露的發生場景,
11.4 JAVA垃圾收集器
在Java中,程式員是不需要顯示的去釋放一個物件的記憶體的,而是由虛擬機自行執行,在JVM中,有一個垃圾回收執行緒,它是低優先級的,在正常情況下是不會執行的,只有在虛擬機空閑或者當前堆記憶體不足時,才會觸發執行,掃面那些沒有被任何參考的物件,并將它們添加到要回收的集合中,進行回收,垃圾回識訓制有效的防止了記憶體泄露,可以有效的使用可使用的記憶體,垃圾收集器在做垃圾回收的時候,首先需要判定的就是哪些記憶體是需要被回收的,哪些物件是「存活」的,是不可以被回收的;哪些物件已經「死掉」了,需要被回收,
一般有兩種方法來判斷:參考計數器法:為每個物件創建一個參考計數,有物件參考時計數器 +1,參考被釋放時計數 -1,當計數器為 0 時就可以被回收,它有一個缺點不能解決回圈參考的問題;可達性分析演算法:從 GC Roots 開始向下搜索,搜索所走過的路徑稱為參考鏈,當一個物件到 GC Roots 沒有任何參考鏈相連時,則證明此物件是可以被回收的,
11.5 JVM 有哪些垃圾回收演算法
標記-清除演算法:標記無用物件,然后進行清除回收,缺點:效率不高,無法清除垃圾碎片,
復制演算法:按照容量劃分二個大小相等的記憶體區域,當一塊用完的時候將活著的物件復制到另一塊上,然后再把已使用的記憶體空間一次清理掉,缺點:記憶體使用率不高,只有原來的一半,
標記-整理演算法:標記無用物件,讓所有存活的物件都向一端移動,然后直接清除掉端邊界以外的記憶體,
分代演算法:根據物件存活周期的不同將記憶體劃分為幾塊,一般是新生代和老年代,新生代基本采用復制演算法,老年代采用標記整理演算法,
11.6 JVM調優
JVM 調優的工具:
JDK 自帶了很多監控工具,都位于 JDK 的BIN目錄下,其中最常用的是 Jconsole 和 Jvisualvm 這兩款視圖監控工具,
Jconsole:用于對 JVM 中的記憶體、執行緒和類等進行監控;
Jvisualvm:JDK 自帶的全能分析工具,可以分析:記憶體快照、執行緒快照、程式死鎖、監控記憶體的變化、gc 變化等,
常用的 JVM 調優的引數:
-Xms2g:初始化大小為 2G;
-Xmx2g:最大記憶體為 2G;
-XX:NewRatio=4:設定年輕的和老年代的記憶體比例為 1:4;
-XX:SurvivorRatio=8:設定新生代 Eden 和 Survivor 比例為 8:2;
–XX:+UseParNewGC:指定使用 ParNew + Serial Old 垃圾回收器組合;
-XX:+UseParallelOldGC:指定使用 ParNew + ParNew Old 垃圾回收器組合;
-XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器組合;
-XX:+PrintGC:開啟列印GC資訊;
-XX:+PrintGCDetails:列印GC詳細資訊,
12 spring事務
12.1 spring事務的傳播行為
PROPAGATION_REQUIRED–支持當前事務,如果當前沒有事務,就新建一個事務,這是最常見的選擇(大部分專案默認配置),
PROPAGATION_SUPPORTS–支持當前事務,如果當前沒有事務,就以非事務方式執行,
PROPAGATION_MANDATORY–支持當前事務,如果當前沒有事務,就拋出例外,
PROPAGATION_REQUIRES_NEW–新建事務,如果當前存在事務,把當前事務掛起,
PROPAGATION_NOT_SUPPORTED–以非事務方式執行操作,如果當前存在事務,就把當前事務掛起,
PROPAGATION_NEVER–以非事務方式執行,如果當前存在事務,則拋出例外,
PROPAGATION_NESTED–如果當前存在事務,則在嵌套事務內執行,如果當前沒有事務,則進行與PROPAGATION_REQUIRED類似的操作,
12.2 spring事務的隔離級別
default:使用底層資料庫的默認隔離級別對于大多數的資料庫來說,默認的隔離級別都是read_commted
read_commted:只允許事務讀取已經被其他事務提交的變更,可以避免臟讀,但不可以避免不可重復讀和幻讀
read_uncommted:允許事務讀取未被其他事務提交的變更,臟讀,不可重復讀,幻讀都會出現,
repeatable_read: 可避免臟讀和不可重復讀,但幻讀問題仍然存在
serlalizable:所有問題都可以避免,但性能十分低下,
13 多執行緒
13.1 執行緒安全的概念
如果你的代碼所在的行程中有多個執行緒在同時運行,而這些執行緒可能會同時運行這段代碼,如果每次運行結果和單執行緒運行的結果是一樣的,而且其他的變數的值也和預期的是一樣的,就是執行緒安全的, 或者說:一個類或者程式所提供的介面對于執行緒來說是原子操作或者多個執行緒之間的切換不會導致該介面的執行結果存在二義性,也就是說我們不用考慮同步的問題,執行緒安全問題都是由全域變數及靜態變數引起的,若每個執行緒中對全域變數、靜態變數只有讀操作,而無寫操作,一般來說,這個全域變數是執行緒安全的;若有多個執行緒同時執行寫操作,一般都需要考慮執行緒同步,否則就可能影響執行緒安全,存在競爭的執行緒不安全,不存在競爭的執行緒就是安全的
13.2 多執行緒同步的解決方案
1、synchronized同步方法;2、synchronized同步代碼塊;
3、wait與notify,這兩個方法并不是在Thread類中的,而是輸出Object類,這也意味著任何物件都可以呼叫這2個方法,
4、使用特殊域變數(volatile)實作執行緒同步,保證此變數對所有執行緒的可見性,指一條執行緒修改了這個變數的值,新值對于其他執行緒來說是可見的,通過新值立即同步到主記憶體和每次使用前從主記憶體重繪機制保證了可見性,禁止指令重排序保證了有序性,但并不是多執行緒安全的,它不保證原子性,Volatile變數最好是那種只有一個執行緒修改變數,多個執行緒讀取變數的地方,也就是對記憶體可見性要求高,而對原子性要求低的地方;
5、使用重入鎖實作執行緒同步;
6、使用區域變數實作執行緒同步(ThreadLocal),ThreadLocal 用作保存每個執行緒獨享的物件,為每個執行緒都創建一個副本,這樣每個執行緒都可以修改自己所擁有的副本, 而不會影響其他執行緒的副本,確保了執行緒安全,每個執行緒獲取到的資訊可能都是不一樣的,前面執行的方法保存了資訊后,后續方法可以通過ThreadLocal 直接獲取到,避免了傳參,類似于全域變數的概念,simpleDateFormat為例,ThreadLocal給每個執行緒維護一個自己的simpleDateFormat物件,這個物件在執行緒之間是獨立的,互相沒有關系的,這也就避免了執行緒安全問題ThreadLocal給每個執行緒維護一個自己的simpleDateFormat物件,這個物件在執行緒之間是獨立的,互相沒有關系的,這也就避免了執行緒安全問題;
7、使用阻塞佇列實作執行緒同步
8、使用原子變數實作執行緒同步
13.3可重入鎖ReentrantLock和synchronized的區別
同一個執行緒再次進入同步代碼的時候.可以使用自己已經獲取到的鎖,這就是可重入鎖,java里面內置鎖(synchronize)和Lock(ReentrantLock)都是可重入的,這兩種方式最大區別就是對于Synchronized來說,它是java語言的關鍵字,是原生語法層面的互斥,需要jvm實作,而ReentrantLock它是JDK 1.5之后提供的API層面的互斥鎖,需要lock()和unlock()方法配合try/finally陳述句塊來完成,
1.Synchronized
Synchronized經過編譯,會在同步塊的前后分別形成monitorenter和monitorexit這個兩個位元組碼指令,在執行monitorenter指令時,首先要嘗試獲取物件鎖,如果這個物件沒被鎖定,或者當前執行緒已經擁有了那個物件鎖,把鎖的計算器加1,相應的,在執行monitorexit指令時會將鎖計算器就減1,當計算器為0時,鎖就被釋放了,如果獲取物件鎖失敗,那當前執行緒就要阻塞,直到物件鎖被另一個執行緒釋放為止,
2.ReentrantLock
由于ReentrantLock是java.util.concurrent包下提供的一套互斥鎖,相比Synchronized,ReentrantLock類提供了一些高級功能,主要有以下3項:
1.等待可中斷,持有鎖的執行緒長期不釋放的時候,正在等待的執行緒可以選擇放棄等待,這相當于Synchronized來說可以避免出現死鎖的情況,
2.公平鎖,多個執行緒等待同一個鎖時,必須按照申請鎖的時間順序獲得鎖,Synchronized鎖非公平鎖,ReentrantLock默認的建構式是創建的非公平鎖,可以通過引數true設為公平鎖,但公平鎖表現的性能不是很好,
3.鎖系結多個條件,一個ReentrantLock物件可以同時系結多個物件,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/160035.html
標籤:其他
