开源项目工时系统_SpringBoot 微信点餐开源系统!综合运用项目,值得一看!
架構
前后端分離:
Nginx 與 Tomcat 的關系在這篇文章,幾分鐘可以快速了解:
https://www.jianshu.com/p/22dcb7ef9172
補充:
setting.xml 文件的作用:settings.xml 是 maven 的全局配置文件。而 pom.xml 文件是所在項目的局部配置。Settings.xml 中包含類似本地倉儲位置、修改遠程倉儲服務器、認證信息等配置。
maven 的作用:借助 Maven,可將 jar 包僅僅保存在 “倉庫” 中,有需要該文件時,就引用該文件接口,不需要復制文件過來占用空間。
注:這個 “倉庫” 應該就是本地安裝 maven 的目錄下的 Repository 的文件夾
分布式鎖
線程鎖:當某個方法或代碼使用鎖,在同一時刻僅有一個線程執行該方法或該代碼段。線程鎖只在同一 JVM 中有效,因為線程鎖的實現在根本上是依靠線程之間共享內存實現的。如 synchronized
進程鎖:為了控制同一操作系統中多個進程訪問某個共享資源。
分布式鎖:當多個進程不在同一個系統中,用分布式鎖控制多個進程對資源的訪問。
分布式鎖一般有三種實現方式:
數據庫樂觀鎖;
基于 Redis 的分布式鎖;
基于 ZooKeeper 的分布式鎖。
樂觀鎖的實現:使用版本標識來確定讀到的數據與提交時的數據是否一致。提交后修改版本標識,不一致時可以采取丟棄和再次嘗試的策略。
CAS:可以閱讀這篇文章:
https://www.jianshu.com/p/456bb1ea9627
分布式鎖基于 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 下創建了 Noden —> 循環 (每次獲取 Locker 下的所有子節點 —> 對這些節點按節點自增號排序順序 —> 判斷自己創建的 Noden 是否是第一個節點 —> 如果是則獲得了分布式鎖 —> 如果不是監聽上一個節點 Node_n-1 等它釋放掉分布式鎖。)
@ControllerAdvice 處理全局異常
Mybatis 注解方式的使用:
@insert 用注解方式寫 SQL 語句
分布式系統的下的 Session
1、分布式系統:多節點,節點發送數據交互,不共享主內存,但通過網絡發送消息合作。
分布式:不同功能模塊的節點
集群:相同功能的節點
2、Session 與 token
服務端在 HTTP 頭里設置 SessionID 而客戶端將其保存在 cookie
而使用 Token 時需要手動在 HTTP 頭里設置,服務器收到請求后取出 cookie 進行驗證。
都是一個用戶一個標志
3、分布式系統中的 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 的值就是每一個請求當前鎖的線程。公平鎖則在于每次都是依次從隊首取值。
ReentrantLock 重入性:
重入鎖可以看這兩篇文章,都比較簡單
https://www.jianshu.com/p/587a4559442b
https://www.jianshu.com/p/1c52f17efaab
Spring + Redis 緩存的兩個重要注解:
@cacheable 只會執行一次,當標記在一個方法上時表示該方法是支持緩存的,Spring 會在其被調用后將其返回值緩存起來,以保證下次利用同樣的參數來執行該方法時可以直接從緩存中獲取結果。
@cacheput:與 @Cacheable 不同的是使用 @CachePut 標注的方法在執行前不會去檢查緩存中是否存在之前執行過的結果,而是每次都會執行該方法,并將執行結果以鍵值對的形式存入指定的緩存中。
對數據庫加鎖(樂觀鎖 與 悲觀鎖)
悲觀鎖依賴數據庫實現:
select?*?from?account?where?name=”Erica”?for?update
這條 sql 語句鎖定了 account 表中所有符合檢索條件(name=”Erica”)的記錄,使該記錄在修改期間其它線程不得占有。
代碼層加鎖:
String?hql?="from?TUser?as?user?where?user.;
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?
????????????????log.error("參數不正確,OrderForm={}",?order);
????????????????throw?new?SellException(
?????????????????…………?,
?????????????result.getFeildError.getDefaultMessage()
??????????????)
????????????????System.out.println("error:"+ls.get(i));??
????????????}??
????????}??
????????return?"adduser";??
????}
result.getFeildError.getDefaultMessage() 可拋出 “姓名必填” 的異常。
4、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));
5、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 的泊松分布。
源碼地址:
https://github.com/923310233/wxOrder
(完)
歷史推薦
1、作為阿里的面試官,我有話想說:面試,面試官比候選人還難!
2、計算機仿真程序告訴你為什么現在不能出門(5分鐘視頻)
3、Java程序員最常用的20%技術總結
4、一只蝙蝠的自述在朋友圈火了
公眾號ID|javabaiwen
小編微信|204998835
_每天分享技術干貨
視頻 | 電子書 | 面試題?|?開發經驗
總結
以上是生活随笔為你收集整理的开源项目工时系统_SpringBoot 微信点餐开源系统!综合运用项目,值得一看!的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 数字手机什么时候开始
- 下一篇: 油烟机时间怎么调