开源项目工时系统_SpringBoot 微信点餐开源系统!综合运用项目,值得一看!
架構
前后端分離:
Nginx 與 Tomcat 的關系在這篇文章,幾分鐘可以快速了解:
https://www.jianshu.com/p/22dcb7ef9172
補充:
setting.xml 文件的作用:settings.xml 是 maven 的全局配置文件。而 pom.xml 文件是所在項目的局部配置。Settings.xml 中包含類似本地倉儲位置、修改遠程倉儲服務器、認證信息等配置。
maven 的作用:借助 Maven,可將 jar 包僅僅保存在 “倉庫” 中,有需要該文件時,就引用該文件接口,不需要復制文件過來占用空間。
注:這個 “倉庫” 應該就是本地安裝 maven 的目錄下的 Repository 的文件夾
分布式鎖
線程鎖:當某個方法或代碼使用鎖,在同一時刻僅有一個線程執(zhí)行該方法或該代碼段。線程鎖只在同一 JVM 中有效,因為線程鎖的實現(xiàn)在根本上是依靠線程之間共享內(nèi)存實現(xiàn)的。如 synchronized
進程鎖:為了控制同一操作系統(tǒng)中多個進程訪問某個共享資源。
分布式鎖:當多個進程不在同一個系統(tǒng)中,用分布式鎖控制多個進程對資源的訪問。
分布式鎖一般有三種實現(xiàn)方式:
數(shù)據(jù)庫樂觀鎖;
基于 Redis 的分布式鎖;
基于 ZooKeeper 的分布式鎖。
樂觀鎖的實現(xiàn):使用版本標識來確定讀到的數(shù)據(jù)與提交時的數(shù)據(jù)是否一致。提交后修改版本標識,不一致時可以采取丟棄和再次嘗試的策略。
CAS:可以閱讀這篇文章:
https://www.jianshu.com/p/456bb1ea9627
分布式鎖基于 Redis 的實現(xiàn):(本系統(tǒng)鎖才用的)
基本命令:
SETNX(SET if Not exist):當且僅當 key 不存在,將 key 的值設為 value ,并返回 1;若給定的 key 已經(jīng)存在,則 SETNX 不做任何動作,并返回 0。
GETSET:將給定 key 的值設為 value ,并返回 key 的舊值。先根據(jù) key 獲取到舊的 value,再 set 新的 value。
EXPIRE 為給定 key 設置生存時間, 當 key 過期時,它會被自動刪除。
加鎖方式:
這里的 jedis 是 Java 對 Redis 的集成
jedis.set(String?key,?String?value,?String?nxxx,?String?expx,?int?time)
錯誤的加鎖方式 1:
如果程序在執(zhí)行完 setnx() 之后突然崩潰,導致鎖沒有設置過期時間。那么將會發(fā)生死鎖。
Long?result?=?jedis.setnx(Key,?value);
????if?(result?==?1)?{
????????//?若在這里程序突然崩潰,則無法設置過期時間,將發(fā)生死鎖
????????jedis.expire(Key,?expireTime);
????}
錯誤的加鎖方式 2:
分布式鎖才用(Key,過期時間)的方式,如果鎖存在,那么獲取它的過期時間,如果鎖的確已經(jīng)過期了,那么獲得鎖,并且設置新的過期時間
錯誤分析:不同的客戶端之間需要同步好時間。
?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())?{
????????//?鎖已過期,獲取上一個鎖的過期時間,并設置現(xiàn)在鎖的過期時間
????????String?oldValueStr?=?jedis.getSet(lockKey,?expiresStr);
????????if?(oldValueStr?!=?null?&&?oldValueStr.equals(currentValueStr))?{
????????????//?考慮多線程并發(fā)的情況,只有一個線程的設置值和當前值相同,它才有權利加鎖
????????????return?true;
????????}
????}
????//?其他情況,一律返回加鎖失敗
????return?false;
解鎖:判斷鎖的擁有者后可以使用 jedis.del(lockKey) 來釋放鎖。
分布式鎖基于 Zookeeper 的實現(xiàn)
Zookeeper 簡介:Zookeeper 提供一個多層級的節(jié)點命名空間(節(jié)點稱為 znode),每個節(jié)點都用一個以斜杠(/)分隔的路徑表示,而且每個節(jié)點都有父節(jié)點(根節(jié)點除外)。
例如,/foo/doo 這個表示一個 znode,它的父節(jié)點為 / foo,父父節(jié)點為 /,而 / 為根節(jié)點沒有父節(jié)點。
client 不論連接到哪個 Server,展示給它都是同一個視圖,這是 zookeeper 最重要的性能。
Zookeeper 的核心是原子廣播,這個機制保證了各個 Server 之間的同步。實現(xiàn)這個機制的協(xié)議叫做 Zab 協(xié)議。Zab 協(xié)議有兩種模式,它們分別是恢復模式(選主)和廣播模式(同步)。*當服務啟動或者在領導者崩潰后,Zab 就進入了恢復模式*,當領導者被選舉出來,且大多數(shù) Server 完成了和 leader 的狀態(tài)同步以后,恢復模式就結束了。狀態(tài)同步保證了 leader 和 Server 具有相同的系統(tǒng)狀態(tài)。
為了保證事務的順序一致性,zookeeper 采用了遞增的事務 id 號(zxid)來標識事務,實現(xiàn)中 zxid 是一個 64 位的數(shù)字。
Zookeeper 的分布式鎖原理
獲取分布式鎖的流程:
在獲取分布式鎖的時候在 locker 節(jié)點 (locker 節(jié)點是 Zookeeper 的指定節(jié)點) 下創(chuàng)建臨時順序節(jié)點,釋放鎖的時候刪除該臨時節(jié)點。
客戶端調(diào)用 createNode 方法在 locker 下創(chuàng)建臨時順序節(jié)點,然后調(diào)用 getChildren(“l(fā)ocker”) 來獲取 locker 下面的所有子節(jié)點,注意此時不用設置任何 Watcher。
客戶端獲取到所有的子節(jié)點 path 之后,如果發(fā)現(xiàn)自己創(chuàng)建的子節(jié)點序號最小,那么就認為該客戶端獲取到了鎖。
如果發(fā)現(xiàn)自己創(chuàng)建的節(jié)點并非 locker 所有子節(jié)點中最小的,說明自己還沒有獲取到鎖,此時客戶端需要找到比自己小的那個節(jié)點,然后對其調(diào)用 exist() 方法,同時對其注冊事件監(jiān)聽器。
之后,讓這個被關注的節(jié)點刪除,則客戶端的 Watcher 會收到相應通知,此時再次判斷自己創(chuàng)建的節(jié)點是否是 locker 子節(jié)點中序號最小的,如果是則獲取到了鎖,如果不是則重復以上步驟繼續(xù)獲取到比自己小的一個節(jié)點并注冊監(jiān)聽。
我的解釋:
A 在 Locker 下創(chuàng)建了 Noden —> 循環(huán) (每次獲取 Locker 下的所有子節(jié)點 —> 對這些節(jié)點按節(jié)點自增號排序順序 —> 判斷自己創(chuàng)建的 Noden 是否是第一個節(jié)點 —> 如果是則獲得了分布式鎖 —> 如果不是監(jiān)聽上一個節(jié)點 Node_n-1 等它釋放掉分布式鎖。)
@ControllerAdvice 處理全局異常
Mybatis 注解方式的使用:
@insert 用注解方式寫 SQL 語句
分布式系統(tǒng)的下的 Session
1、分布式系統(tǒng):多節(jié)點,節(jié)點發(fā)送數(shù)據(jù)交互,不共享主內(nèi)存,但通過網(wǎng)絡發(fā)送消息合作。
分布式:不同功能模塊的節(jié)點
集群:相同功能的節(jié)點
2、Session 與 token
服務端在 HTTP 頭里設置 SessionID 而客戶端將其保存在 cookie
而使用 Token 時需要手動在 HTTP 頭里設置,服務器收到請求后取出 cookie 進行驗證。
都是一個用戶一個標志
3、分布式系統(tǒng)中的 Session 問題:
高并發(fā):通過設計保證系統(tǒng)能夠同時并行處理很多請求。
當高并發(fā)量的請求到達服務端的時候通過負載均衡的方式分發(fā)到集群中的某個服務器,這樣就有可能導致同一個用戶的多次請求被分發(fā)到集群的不同服務器上,就會出現(xiàn)取不到 session 數(shù)據(jù)的情況。
根據(jù)訪問不同的 URL,負載到不同的服務器上去
三臺機器,A1 部署類目,A2 部署商品,A3 部署單服務
通用方案:用 Redis 保存 Session 信息,服務器需要時都去找 Redis 要。登錄時保存好 key-value,登出時讓他失效
垂直擴展:IP 哈希 IP 的哈希值相同的訪問同一臺服務器
session 的一致性:只要用戶不重啟瀏覽器,每次 http 短連接請求,理論上服務端都能定位到 session,保持會話。
Redis 作為分布式鎖
高并發(fā):通過設計保證系統(tǒng)能夠同時并行處理很多請求。
同步:Java 中的同步指的是通過人為的控制和調(diào)度,保證共享資源的多線程訪問成為線程安全。
線程的 Block 狀態(tài):
a. 調(diào)用 join() 和 sleep() 方法,sleep() 時間結束或被打斷
b.wait(),使該線程處于等待池, 直到 notify()/notifyAll():不釋放資源
此外,在 runnable 狀態(tài)的線程是處于被調(diào)度的線程,Thread 類中的 yield 方法可以讓一個 running 狀態(tài)的線程轉入 runnable。
Q:為什么 wait,notify 和 notifyAll 必須與 synchronized 一起使用?Obj.wait()、Obj.notify 必須在 synchronized(Obj){…} 語句塊內(nèi)。
A:wait 就是說線程在獲取對象鎖后,主動釋放對象鎖,同時本線程休眠。
Q:Synchronized:
A:Synchronized 就是非公平鎖,它無法保證等待的線程獲取鎖的順序。
公平和非公平鎖的隊列都基于鎖內(nèi)部維護的一個雙向鏈表,表結點 Node 的值就是每一個請求當前鎖的線程。公平鎖則在于每次都是依次從隊首取值。
ReentrantLock 重入性:
重入鎖可以看這兩篇文章,都比較簡單
https://www.jianshu.com/p/587a4559442b
https://www.jianshu.com/p/1c52f17efaab
Spring + Redis 緩存的兩個重要注解:
@cacheable 只會執(zhí)行一次,當標記在一個方法上時表示該方法是支持緩存的,Spring 會在其被調(diào)用后將其返回值緩存起來,以保證下次利用同樣的參數(shù)來執(zhí)行該方法時可以直接從緩存中獲取結果。
@cacheput:與 @Cacheable 不同的是使用 @CachePut 標注的方法在執(zhí)行前不會去檢查緩存中是否存在之前執(zhí)行過的結果,而是每次都會執(zhí)行該方法,并將執(zhí)行結果以鍵值對的形式存入指定的緩存中。
對數(shù)據(jù)庫加鎖(樂觀鎖 與 悲觀鎖)
悲觀鎖依賴數(shù)據(jù)庫實現(xiàn):
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();//執(zhí)行查詢,獲取數(shù)據(jù)
其它
@Data 類似于自動生成了 Getter()、Setter()、ToString() 等方法。
JAVA1.8 的新特性 StreamAPI:Collectors 中提供了將流中的元素累積到匯聚結果的各種方式
List<Menu>?menus=Menu.getMenus.stream().collect(Collectors.toList())
For - each 寫法:
for each 語句是 java5 新增,在遍歷數(shù)組、集合的時候,for each 擁有不錯的性能。
public?static?void?main(String[]?args)?{
????????String[]?names?=?{"beibei",?"jingjing"};
????????for?(String?name?:?names)?{
????????????System.out.println(name);
????????}
????}
for each 雖然能遍歷數(shù)組或者集合,但是只能用來遍歷,無法在遍歷的過程中對數(shù)組或者集合進行修改。
BindingResult:一個 @Valid 的參數(shù)后必須緊挨著一個 BindingResult 參數(shù),否則 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("參數(shù)不正確,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;
???/*構造函數(shù)*/
}
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 實現(xiàn)的 HashSet,由 [hashCode() 和 equals()]保證元素的唯一性。
可以用 set 幫助去掉 List 中的重復元素,set 的構造方法的參數(shù)可以是 List,構造后是一個去重的 set。
HashMap 的補充:它不是 Collection 下的
Map 可以使用 containsKey()/containsValue() 來檢查其中是否含有某個 key/value。
HashMap 會利用對象的 hashCode 來快速找到 key。
插入過程:通過一個 hash 函數(shù)確定 Entry 的插入位置 index=hash(key),但是數(shù)組的長度有限,可能會發(fā)生 index 沖突,當發(fā)生了沖突時,會使用頭插法,即為新來的 Entry 指向舊的 Entry,成為一個鏈表。
每次插入時依次遍歷它的 index 下的單鏈表,如果存在 Key 一致的節(jié)點,那么直接替換,并且返回新的值。
但是單鏈表不會一直增加元素,當元素個數(shù)超過 8 個時,會嘗試將單鏈表轉化為紅黑樹存儲。
為何加載因子默認為 0.75?*(0.75 開始擴容)*
答:通過源碼里的 javadoc 注釋看到,元素在哈希表中分布的桶頻率服從參數(shù)為 0.5 的泊松分布。
源碼地址:
https://github.com/923310233/wxOrder
(完)
歷史推薦
1、作為阿里的面試官,我有話想說:面試,面試官比候選人還難!
2、計算機仿真程序告訴你為什么現(xiàn)在不能出門(5分鐘視頻)
3、Java程序員最常用的20%技術總結
4、一只蝙蝠的自述在朋友圈火了
公眾號ID|javabaiwen
小編微信|204998835
_每天分享技術干貨
視頻 | 電子書 | 面試題?|?開發(fā)經(jīng)驗
總結
以上是生活随笔為你收集整理的开源项目工时系统_SpringBoot 微信点餐开源系统!综合运用项目,值得一看!的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 数字手机什么时候开始
- 下一篇: 油烟机时间怎么调