騰訊內部技術——SpringBoot微信點餐系統

架構

先後端分離:java

部署架構:node

補充:spring

setting.xml 文件的做用:settings.xml是maven的全局配置文件。而pom.xml文件是所在項目的局部配置。Settings.xml中包含相似本地倉儲位置、修改遠程倉儲服務器、認證信息等配置。sql

maven的做用:藉助Maven,可將jar包僅僅保存在「倉庫」中,有須要該文件時,就引用該文件接口,不須要複製文件過來佔用空間。數據庫

注:這個「倉庫」應該就是本地安裝maven的目錄下的Repository的文件夾後端

分佈式鎖

線程鎖:當某個方法或代碼使用鎖,在同一時刻僅有一個線程執行該方法或該代碼段。線程鎖只在同一JVM中有效,由於線程鎖的實如今根本上是依靠線程之間共享內存實現的。如synchronized數組

進程鎖:爲了控制同一操做系統中多個進程訪問某個共享資源。瀏覽器

分佈式鎖:當多個進程不在同一個系統中,用分佈式鎖控制多個進程對資源的訪問。緩存

分佈式鎖通常有三種實現方式:1. 數據庫樂觀鎖;2. 基於Redis的分佈式鎖;3. 基於ZooKeeper的分佈式鎖。安全

樂觀鎖的實現:使用版本標識來肯定讀到的數據與提交時的數據是否一致。提交後修改版本標識,不一致時能夠採起丟棄和再次嘗試的策略。在此我向你們推薦一個架構學習交流羣。交流學習羣號:948368769裏面會分享一些資深架構師錄製的視頻錄像:有Spring,MyBatis,Netty源碼分析,高併發、高性能、分佈式、微服務架構的原理,JVM性能優化、分佈式架構等這些成爲架構師必備的知識體系。還能領取免費的學習資源,目前受益良多

分佈式鎖基於Redis的實現:(本系統鎖才用的)

基本命令:

SETNX(SET if Not exist):當且僅當 key 不存在,將 key 的值設爲 value ,並返回1;若給定的 key 已經存在,則 SETNX 不作任何動做,並返回0。

GETSET:將給定 key 的值設爲 value ,並返回 key 的舊值。先根據key獲取到舊的value,再set新的value。

EXPIRE 爲給定 key 設置生存時間,當 key 過時時,它會被自動刪除。

加鎖方式:

這裏的jedis是Java對Redis的集成

jedis.set(String key, String value, String nxxx, String expx, int time)

錯誤的加鎖方式1:若是程序在執行完setnx()以後忽然崩潰,致使鎖沒有設置過時時間。那麼將會發生死鎖。

Long result = jedis.setnx(Key, value);
if (result == 1) {
// 若在這裏程序忽然崩潰,則沒法設置過時時間,將發生死鎖
jedis.expire(Key, expireTime);
}

錯誤的加鎖方式2:分佈式鎖才用(Key,過時時間)的方式,若是鎖存在,那麼獲取它的過時時間,若是鎖的確已通過期了,那麼得到鎖,而且設置新的過時時間

錯誤分析:不一樣的客戶端之間須要同步好時間。

long expires = System.currentTimeMillis() + expireTime;
String expiresStr = String.valueOf(expires);
// 若是當前鎖不存在,返回加鎖成功
if (jedis.setnx(lockKey, expiresStr) == 1) {
return true;
}
// 若是鎖存在,獲取鎖的過時時間
String currentValueStr = jedis.get(lockKey);
if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {
// 鎖已過時,獲取上一個鎖的過時時間,並設置如今鎖的過時時間
String oldValueStr = jedis.getSet(lockKey, expiresStr);
if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
// 考慮多線程併發的狀況,只有一個線程的設置值和當前值相同,它纔有權利加鎖
return true;
}
}
// 其餘狀況,一概返回加鎖失敗
return false;

解鎖:判斷鎖的擁有者後可使用 jedis.del(lockKey) 來釋放鎖。

分佈式鎖基於Zookeeper的實現

Zookeeper簡介:Zookeeper提供一個多層級的節點命名空間(節點稱爲znode),每一個節點都用一個以斜槓(/)分隔的路徑表示,並且每一個節點都有父節點(根節點除外)。例如,/foo/doo這個表示一個znode,它的父節點爲/foo,父父節點爲/,而/爲根節點沒有父節點。

client不論鏈接到哪一個Server,展現給它都是同一個視圖,這是zookeeper最重要的性能。

Zookeeper 的核心是原子廣播,這個機制保證了各個Server之間的同步。實現這個機制的協議叫作Zab協議。Zab協議有兩種模式,它們分別是恢復模式(選主)和廣播模式(同步)。當服務啓動或者在領導者崩潰後,Zab就進入了恢復模式,當領導者被選舉出來,且大多數Server完成了和 leader的狀態同步之後,恢復模式就結束了。狀態同步保證了leader和Server具備相同的系統狀態。

爲了保證事務的順序一致性,zookeeper採用了遞增的事務id號(zxid)來標識事務,實現中zxid是一個64位的數字。

Zookeeper的分佈式鎖原理

獲取分佈式鎖的流程:

在獲取分佈式鎖的時候在locker節點(locker節點是Zookeeper的指定節點)下建立臨時順序節點,釋放鎖的時候刪除該臨時節點。

客戶端調用createNode方法在locker下建立臨時順序節點,而後調用getChildren(「locker」)來獲取locker下面的全部子節點,注意此時不用設置任何Watcher。

客戶端獲取到全部的子節點path以後,若是發現本身建立的子節點序號最小,那麼就認爲該客戶端獲取到了鎖。

若是發現本身建立的節點並不是locker全部子節點中最小的,說明本身尚未獲取到鎖,此時客戶端須要找到比本身小的那個節點,而後對其調用exist()方法,同時對其註冊事件監聽器。

以後,讓這個被關注的節點刪除,則客戶端的Watcher會收到相應通知,此時再次判斷本身建立的節點是不是locker子節點中序號最小的,若是是則獲取到了鎖,若是不是則重複以上步驟繼續獲取到比本身小的一個節點並註冊監聽。

個人解釋:A在Locker下建立了Node_n —>循環 ( 每次獲取Locker下的全部子節點 —> 對這些節點按節點自增號排序順序 —> 判斷本身建立的Node_n是不是第一個節點 —> 若是是則得到了分佈式鎖 —> 若是不是監聽上一個節點Node_n-1 等它釋放掉分佈式鎖。)

@ControllerAdvice處理全局異常

Mybatis註解方式的使用:

@insert 用註解方式寫SQL語句

分佈式系統的下的Session

一、分佈式系統:多節點,節點發送數據交互,不共享主內存,但經過網絡發送消息合做。

分佈式:不一樣功能模塊的節點

集羣:相同功能的節點

二、Session 與token

服務端在HTTP頭裏設置SessionID而客戶端將其保存在cookie

而使用Token時須要手動在HTTP頭裏設置,服務器收到請求後取出cookie進行驗證。

都是一個用戶一個標誌

三、分佈式系統中的Session問題:

高併發:經過設計保證系統可以同時並行處理不少請求。

當高併發量的請求到達服務端的時候經過負載均衡的方式分發到集羣中的某個服務器,這樣就有可能致使同一個用戶的屢次請求被分發到集羣的不一樣服務器上,就會出現取不到session數據的狀況

根據訪問不一樣的URL,負載到不一樣的服務器上去

三臺機器,A1部署類目,A2部署商品,A3部署單服務

通用方案:用Redis保存Session信息,服務器須要時都去找Redis要。登陸時保存好key-value,登出時讓他失效

垂直擴展:IP哈希 IP的哈希值相同的訪問同一臺服務器

session的一致性:只要用戶不重啓瀏覽器,每次http短鏈接請求,理論上服務端都能定位到session,保持會話。

Redis做爲分佈式鎖

高併發:經過設計保證系統可以同時並行處理不少請求。

同步:Java中的同步指的是經過人爲的控制和調度,保證共享資源的多線程訪問成爲線程安全。

線程的Block狀態:

a.調用join()和sleep()方法,sleep()時間結束或被打斷

b.wait(),使該線程處於等待池,直到notify()/notifyAll():不釋放資源

此外,在runnable狀態的線程是處於被調度的線程,Thread類中的yield方法可讓一個running狀態的線程轉入runnable。

Q:爲何wait,notify和notifyAll必須與synchronized一塊兒使用?

Obj.wait()、Obj.notify必須在synchronized(Obj){…}語句塊內。

A:wait就是說線程在獲取對象鎖後,主動釋放對象鎖,同時本線程休眠。

Q:Synchronized:

A:Synchronized就是非公平鎖,它沒法保證等待的線程獲取鎖的順序。

公平和非公平鎖的隊列都基於鎖內部維護的一個雙向鏈表,表結點Node的值就是每個請求當前鎖的線程。公平鎖則在於每次都是依次從隊首取值。

 

Spring + Redis緩存的兩個重要註解:

@cacheable 只會執行一次,當標記在一個方法上時表示該方法是支持緩存的,Spring會在其被調用後將其返回值緩存起來,以保證下次利用一樣的參數來執行該方法時能夠直接從緩存中獲取結果。在此我向你們推薦一個架構學習交流羣。交流學習羣號:948368769裏面會分享一些資深架構師錄製的視頻錄像:有Spring,MyBatis,Netty源碼分析,高併發、高性能、分佈式、微服務架構的原理,JVM性能優化、分佈式架構等這些成爲架構師必備的知識體系。還能領取免費的學習資源,目前受益良多

@cacheput:與@Cacheable不一樣的是使用@CachePut標註的方法在執行前不會去檢查緩存中是否存在以前執行過的結果,而是每次都會執行該方法,並將執行結果以鍵值對的形式存入指定的緩存中。

對數據庫加鎖

樂觀鎖 與 悲觀鎖

悲觀鎖依賴數據庫實現:

select * from account where name=」Erica」 for update

這條sql 語句鎖定了account 表中全部符合檢索條件(name=」Erica」)的記錄,使該記錄在修改期間其它線程不得佔有

代碼層加鎖:

String hql ="from TUser as user where user.name='Erica'";
Query query = session.createQuery(hql);
query.setLockMode("user",LockMode.UPGRADE); //加鎖
List userList = query.list();//執行查詢,獲取數據

其它

@Data 相似於自動生成了Getter()、Setter()、ToString()等方法

JAVA1.8的新特性StreamAPI:Collectors中提供了將流中的元素累積到匯聚結果的各類方式

List<Menu> menus=Menu.getMenus.stream().collect
(Collectors.toList())

For - each 寫法:

for each語句是java5新增,在遍歷數組、集合的時候,for each擁有不錯的性能。
public static void main(String[] args) {
String[] names = {"beibei", "jingjing"};
for (String name : names) {
System.out.println(name);
}
}

for each雖然能遍歷數組或者集合,可是隻能用來遍歷,沒法在遍歷的過程當中對數組或者集合進行修改。

BindingResult:一個@Valid的參數後必須緊挨着一個BindingResult 參數,不然spring會在校驗不經過時直接拋出異常

@Data
public class OrderForm {
@NotEmpty(message = "姓名必填")
private String name;
}

後臺:

@RequestMapping("save")
public String save( @Valid OrderForm order,BindingResult result) {
//
if(result.hasErrors()){
List<ObjectError> ls=result.getAllErrors();
for (int i = 0; i < ls.size(); i++) {
log.error("參數不正確,OrderForm={}", order);
throw new SellException(
………… ,
result.getFeildError.getDefaultMessage()
)
System.out.println("error:"+ls.get(i));
}
}
return "adduser";
}

result.getFeildError.getDefaultMessage()可拋出「姓名必填」 的異常。

四、List轉爲Map

public class Apple {
private Integer id;
private String name;
private BigDecimal money;
private Integer num;
/*構造函數*/
}
List<Apple> appleList = new ArrayList<>();//存放apple對象集合
Apple apple1 = new Apple(1,"蘋果1",new BigDecimal("3.25"),10);
Apple apple12 = new Apple(1,"蘋果2",new BigDecimal("1.35"),20);
Apple apple2 = new Apple(2,"香蕉",new BigDecimal("2.89"),30);
Apple apple3 = new Apple(3,"荔枝",new BigDecimal("9.99"),40);
appleList.add(apple1);
appleList.add(apple12);
appleList.add(apple2);
appleList.add(apple3);
Map<Integer, Apple> appleMap = appleList.stream().collect(Collectors.toMap
(Apple::getId, a -> a,(k1,k2)->k1));

五、Collection的子類:List、Set

List:ArrayList、LinkedList 、Vector

List:有序容器,容許null元素,容許重複元素

Set:元素是無序的,不容許元素

最流行的是基於 HashMap 實現的 HashSet,由hashCode()和equals()保證元素的惟一性。**

能夠用set幫助去掉List中的重複元素,set的構造方法的參數能夠是List,構造後是一個去重的set

HashMap的補充:它不是Collection下的

Map可使用containsKey()/containsValue()來檢查其中是否含有某個key/value。

HashMap會利用對象的hashCode來快速找到key。

插入過程:經過一個hash函數肯定Entry的插入位置index=hash(key),可是數組的長度有限,可能會發生index衝突,當發生了衝突時,會使用頭插法,即爲新來的Entry指向舊的Entry,成爲一個鏈表。

每次插入時依次遍歷它的index下的單鏈表,若是存在Key一致的節點,那麼直接替換,而且返回新的值。

可是單鏈表不會一直增長元素,當元素個數超過8個時,會嘗試將單鏈錶轉化爲紅黑樹存儲。

爲什麼加載因子默認爲0.75?(0.75開始擴容)

答:經過源碼裏的javadoc註釋看到,元素在哈希表中分佈的桶頻率服從參數爲0.5的泊松分佈。

相關文章
相關標籤/搜索