<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          SpringBoot 微信點(diǎn)餐開(kāi)源系統(tǒng)

          共 5301字,需瀏覽 11分鐘

           ·

          2021-02-14 14:04


          • 作者:Tommmmm

          • www.jianshu.com/p/ae14101989f2

          架構(gòu)

          前后端分離:

          Nginx 與 Tomcat 的關(guān)系在這篇文章,幾分鐘可以快速了解:

          https://www.jianshu.com/p/22dcb7ef9172

          補(bǔ)充:

          • setting.xml 文件的作用:settings.xml 是 maven 的全局配置文件。而 pom.xml 文件是所在項(xiàng)目的局部配置。Settings.xml 中包含類似本地倉(cāng)儲(chǔ)位置、修改遠(yuǎn)程倉(cāng)儲(chǔ)服務(wù)器、認(rèn)證信息等配置。

          • maven 的作用:借助 Maven,可將 jar 包僅僅保存在 “倉(cāng)庫(kù)” 中,有需要該文件時(shí),就引用該文件接口,不需要復(fù)制文件過(guò)來(lái)占用空間

          注:這個(gè) “倉(cāng)庫(kù)” 應(yīng)該就是本地安裝 maven 的目錄下的 Repository 的文件夾

          分布式鎖

          線程鎖:當(dāng)某個(gè)方法或代碼使用鎖,在同一時(shí)刻僅有一個(gè)線程執(zhí)行該方法或該代碼段。線程鎖只在同一 JVM 中有效,因?yàn)榫€程鎖的實(shí)現(xiàn)在根本上是依靠線程之間共享內(nèi)存實(shí)現(xiàn)的。如 synchronized

          進(jìn)程鎖:為了控制同一操作系統(tǒng)中多個(gè)進(jìn)程訪問(wèn)某個(gè)共享資源。

          分布式鎖:當(dāng)多個(gè)進(jìn)程不在同一個(gè)系統(tǒng)中,用分布式鎖控制多個(gè)進(jìn)程對(duì)資源的訪問(wèn)。

          分布式鎖一般有三種實(shí)現(xiàn)方式:

          1. 數(shù)據(jù)庫(kù)樂(lè)觀鎖;

          2. 基于 Redis 的分布式鎖;

          3. 基于 ZooKeeper 的分布式鎖。

          樂(lè)觀鎖的實(shí)現(xiàn):使用版本標(biāo)識(shí)來(lái)確定讀到的數(shù)據(jù)與提交時(shí)的數(shù)據(jù)是否一致。提交后修改版本標(biāo)識(shí),不一致時(shí)可以采取丟棄和再次嘗試的策略。

          CAS:可以閱讀這篇文章:

          https://www.jianshu.com/p/456bb1ea9627

          分布式鎖基于 Redis 的實(shí)現(xiàn):(本系統(tǒng)鎖才用的)

          基本命令:

          • SETNX(SET if Not exist):當(dāng)且僅當(dāng) key 不存在,將 key 的值設(shè)為 value ,并返回 1;若給定的 key 已經(jīng)存在,則 SETNX 不做任何動(dòng)作,并返回 0。

          • GETSET:將給定 key 的值設(shè)為 value ,并返回 key 的舊值。先根據(jù) key 獲取到舊的 value,再 set 新的 value。

          • EXPIRE 為給定 key 設(shè)置生存時(shí)間, 當(dāng) key 過(guò)期時(shí),它會(huì)被自動(dòng)刪除。

          加鎖方式:

          這里的 jedis 是 Java 對(duì) Redis 的集成

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

          錯(cuò)誤的加鎖方式 1:

          如果程序在執(zhí)行完 setnx() 之后突然崩潰,導(dǎo)致鎖沒(méi)有設(shè)置過(guò)期時(shí)間。那么將會(huì)發(fā)生死鎖。

          1. Long?result?=?jedis.setnx(Key,?value);

          2. ????if?(result?==?1)?{

          3. ????????//?若在這里程序突然崩潰,則無(wú)法設(shè)置過(guò)期時(shí)間,將發(fā)生死鎖

          4. ????????jedis.expire(Key,?expireTime);

          5. ????}

          錯(cuò)誤的加鎖方式 2:

          分布式鎖才用(Key,過(guò)期時(shí)間)的方式,如果鎖存在,那么獲取它的過(guò)期時(shí)間,如果鎖的確已經(jīng)過(guò)期了,那么獲得鎖,并且設(shè)置新的過(guò)期時(shí)間

          錯(cuò)誤分析:不同的客戶端之間需要同步好時(shí)間。

          1. ?long?expires?=?System.currentTimeMillis()?+?expireTime;

          2. ????String?expiresStr?=?String.valueOf(expires);


          3. ????//?如果當(dāng)前鎖不存在,返回加鎖成功

          4. ????if?(jedis.setnx(lockKey,?expiresStr)?==?1)?{

          5. ????????return?true;

          6. ????}


          7. ????//?如果鎖存在,獲取鎖的過(guò)期時(shí)間

          8. ????String?currentValueStr?=?jedis.get(lockKey);

          9. ????if?(currentValueStr?!=?null?&&?Long.parseLong(currentValueStr)?System.currentTimeMillis())?{

          10. ????????//?鎖已過(guò)期,獲取上一個(gè)鎖的過(guò)期時(shí)間,并設(shè)置現(xiàn)在鎖的過(guò)期時(shí)間

          11. ????????String?oldValueStr?=?jedis.getSet(lockKey,?expiresStr);

          12. ????????if?(oldValueStr?!=?null?&&?oldValueStr.equals(currentValueStr))?{

          13. ????????????//?考慮多線程并發(fā)的情況,只有一個(gè)線程的設(shè)置值和當(dāng)前值相同,它才有權(quán)利加鎖

          14. ????????????return?true;

          15. ????????}

          16. ????}


          17. ????//?其他情況,一律返回加鎖失敗

          18. ????return?false;

          解鎖:判斷鎖的擁有者后可以使用 jedis.del(lockKey) 來(lái)釋放鎖。

          分布式鎖基于 Zookeeper 的實(shí)現(xiàn)

          Zookeeper 簡(jiǎn)介:Zookeeper 提供一個(gè)多層級(jí)的節(jié)點(diǎn)命名空間(節(jié)點(diǎn)稱為 znode),每個(gè)節(jié)點(diǎn)都用一個(gè)以斜杠(/)分隔的路徑表示,而且每個(gè)節(jié)點(diǎn)都有父節(jié)點(diǎn)(根節(jié)點(diǎn)除外)。

          例如,/foo/doo 這個(gè)表示一個(gè) znode,它的父節(jié)點(diǎn)為 / foo,父父節(jié)點(diǎn)為 /,而 / 為根節(jié)點(diǎn)沒(méi)有父節(jié)點(diǎn)。

          client 不論連接到哪個(gè) Server,展示給它都是同一個(gè)視圖,這是 zookeeper 最重要的性能。

          Zookeeper 的核心是原子廣播,這個(gè)機(jī)制保證了各個(gè) Server 之間的同步。實(shí)現(xiàn)這個(gè)機(jī)制的協(xié)議叫做 Zab 協(xié)議。Zab 協(xié)議有兩種模式,它們分別是恢復(fù)模式(選主)和廣播模式(同步)。*當(dāng)服務(wù)啟動(dòng)或者在領(lǐng)導(dǎo)者崩潰后,Zab 就進(jìn)入了恢復(fù)模式*,當(dāng)領(lǐng)導(dǎo)者被選舉出來(lái),且大多數(shù) Server 完成了和 leader 的狀態(tài)同步以后,恢復(fù)模式就結(jié)束了。狀態(tài)同步保證了 leader 和 Server 具有相同的系統(tǒng)狀態(tài)。

          為了保證事務(wù)的順序一致性,zookeeper 采用了遞增的事務(wù) id 號(hào)(zxid)來(lái)標(biāo)識(shí)事務(wù),實(shí)現(xiàn)中 zxid 是一個(gè) 64 位的數(shù)字。

          Zookeeper 的分布式鎖原理

          獲取分布式鎖的流程:

          1. 在獲取分布式鎖的時(shí)候在 locker 節(jié)點(diǎn) (locker 節(jié)點(diǎn)是 Zookeeper 的指定節(jié)點(diǎn)) 下創(chuàng)建臨時(shí)順序節(jié)點(diǎn),釋放鎖的時(shí)候刪除該臨時(shí)節(jié)點(diǎn)。

          2. 客戶端調(diào)用 createNode 方法在 locker 下創(chuàng)建臨時(shí)順序節(jié)點(diǎn),然后調(diào)用 getChildren(“l(fā)ocker”) 來(lái)獲取 locker 下面的所有子節(jié)點(diǎn),注意此時(shí)不用設(shè)置任何 Watcher。

          3. 客戶端獲取到所有的子節(jié)點(diǎn) path 之后,如果發(fā)現(xiàn)自己創(chuàng)建的子節(jié)點(diǎn)序號(hào)最小,那么就認(rèn)為該客戶端獲取到了鎖。

          4. 如果發(fā)現(xiàn)自己創(chuàng)建的節(jié)點(diǎn)并非 locker 所有子節(jié)點(diǎn)中最小的,說(shuō)明自己還沒(méi)有獲取到鎖,此時(shí)客戶端需要找到比自己小的那個(gè)節(jié)點(diǎn),然后對(duì)其調(diào)用 exist() 方法,同時(shí)對(duì)其注冊(cè)事件監(jiān)聽(tīng)器。

          5. 之后,讓這個(gè)被關(guān)注的節(jié)點(diǎn)刪除,則客戶端的 Watcher 會(huì)收到相應(yīng)通知,此時(shí)再次判斷自己創(chuàng)建的節(jié)點(diǎn)是否是 locker 子節(jié)點(diǎn)中序號(hào)最小的,如果是則獲取到了鎖,如果不是則重復(fù)以上步驟繼續(xù)獲取到比自己小的一個(gè)節(jié)點(diǎn)并注冊(cè)監(jiān)聽(tīng)。

          我的解釋:

          A 在 Locker 下創(chuàng)建了 Noden —> 循環(huán) (每次獲取 Locker 下的所有子節(jié)點(diǎn) —> 對(duì)這些節(jié)點(diǎn)按節(jié)點(diǎn)自增號(hào)排序順序 —> 判斷自己創(chuàng)建的 Noden 是否是第一個(gè)節(jié)點(diǎn) —> 如果是則獲得了分布式鎖 —> 如果不是監(jiān)聽(tīng)上一個(gè)節(jié)點(diǎn) Node_n-1 等它釋放掉分布式鎖。)

          @ControllerAdvice 處理全局異常
          Mybatis 注解方式的使用:
          @insert 用注解方式寫 SQL 語(yǔ)句

          分布式系統(tǒng)的下的 Session

          1、分布式系統(tǒng):多節(jié)點(diǎn),節(jié)點(diǎn)發(fā)送數(shù)據(jù)交互,不共享主內(nèi)存,但通過(guò)網(wǎng)絡(luò)發(fā)送消息合作。

          分布式:不同功能模塊的節(jié)點(diǎn)

          集群:相同功能的節(jié)點(diǎn)

          2、Session 與 token

          服務(wù)端在 HTTP 頭里設(shè)置 SessionID 而客戶端將其保存在 cookie

          而使用 Token 時(shí)需要手動(dòng)在 HTTP 頭里設(shè)置,服務(wù)器收到請(qǐng)求后取出 cookie 進(jìn)行驗(yàn)證。

          都是一個(gè)用戶一個(gè)標(biāo)志

          3、分布式系統(tǒng)中的 Session 問(wèn)題:

          高并發(fā):通過(guò)設(shè)計(jì)保證系統(tǒng)能夠同時(shí)并行處理很多請(qǐng)求。

          當(dāng)高并發(fā)量的請(qǐng)求到達(dá)服務(wù)端的時(shí)候通過(guò)負(fù)載均衡的方式分發(fā)到集群中的某個(gè)服務(wù)器,這樣就有可能導(dǎo)致同一個(gè)用戶的多次請(qǐng)求被分發(fā)到集群的不同服務(wù)器上,就會(huì)出現(xiàn)取不到 session 數(shù)據(jù)的情況。

          根據(jù)訪問(wèn)不同的 URL,負(fù)載到不同的服務(wù)器上去

          三臺(tái)機(jī)器,A1 部署類目,A2 部署商品,A3 部署單服務(wù)

          通用方案:用 Redis 保存 Session 信息,服務(wù)器需要時(shí)都去找 Redis 要。登錄時(shí)保存好 key-value,登出時(shí)讓他失效

          垂直擴(kuò)展:IP 哈希 IP 的哈希值相同的訪問(wèn)同一臺(tái)服務(wù)器

          session 的一致性:只要用戶不重啟瀏覽器,每次 http 短連接請(qǐng)求,理論上服務(wù)端都能定位到 session,保持會(huì)話。

          Redis 作為分布式鎖

          高并發(fā):通過(guò)設(shè)計(jì)保證系統(tǒng)能夠同時(shí)并行處理很多請(qǐng)求。

          同步:Java 中的同步指的是通過(guò)人為的控制和調(diào)度,保證共享資源的多線程訪問(wèn)成為線程安全。

          線程的 Block 狀態(tài):

          a. 調(diào)用 join() 和 sleep() 方法,sleep() 時(shí)間結(jié)束或被打斷

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

          此外,在 runnable 狀態(tài)的線程是處于被調(diào)度的線程,Thread 類中的 yield 方法可以讓一個(gè) running 狀態(tài)的線程轉(zhuǎn)入 runnable。

          Q:為什么 wait,notify 和 notifyAll 必須與 synchronized 一起使用?Obj.wait()、Obj.notify 必須在 synchronized(Obj){…} 語(yǔ)句塊內(nèi)。

          A:wait 就是說(shuō)線程在獲取對(duì)象鎖后,主動(dòng)釋放對(duì)象鎖,同時(shí)本線程休眠。

          Q:Synchronized:

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

          公平和非公平鎖的隊(duì)列都基于鎖內(nèi)部維護(hù)的一個(gè)雙向鏈表,表結(jié)點(diǎn) Node 的值就是每一個(gè)請(qǐng)求當(dāng)前鎖的線程。公平鎖則在于每次都是依次從隊(duì)首取值。

          ReentrantLock 重入性:

          重入鎖可以看這兩篇文章,都比較簡(jiǎn)單

          https://www.jianshu.com/p/587a4559442b
          https://www.jianshu.com/p/1c52f17efaab

          Spring + Redis 緩存的兩個(gè)重要注解:

          • @cacheable 只會(huì)執(zhí)行一次,當(dāng)標(biāo)記在一個(gè)方法上時(shí)表示該方法是支持緩存的,Spring 會(huì)在其被調(diào)用后將其返回值緩存起來(lái),以保證下次利用同樣的參數(shù)來(lái)執(zhí)行該方法時(shí)可以直接從緩存中獲取結(jié)果。

          • @cacheput:與 @Cacheable 不同的是使用 @CachePut 標(biāo)注的方法在執(zhí)行前不會(huì)去檢查緩存中是否存在之前執(zhí)行過(guò)的結(jié)果,而是每次都會(huì)執(zhí)行該方法,并將執(zhí)行結(jié)果以鍵值對(duì)的形式存入指定的緩存中。

          對(duì)數(shù)據(jù)庫(kù)加鎖(樂(lè)觀鎖 與 悲觀鎖)

          悲觀鎖依賴數(shù)據(jù)庫(kù)實(shí)現(xiàn):

          1. select?*?from?account?where?name=”Erica”?for?update

          這條 sql 語(yǔ)句鎖定了 account 表中所有符合檢索條件(name=”Erica”)的記錄,使該記錄在修改期間其它線程不得占有。

          代碼層加鎖:

          1. String?hql?="from?TUser?as?user?where?user.;

          2. Query?query?=?session.createQuery(hql);

          3. query.setLockMode("user",LockMode.UPGRADE);?//加鎖

          4. List?userList?=?query.list();//執(zhí)行查詢,獲取數(shù)據(jù)

          其它

          @Data 類似于自動(dòng)生成了 Getter()、Setter()、ToString() 等方法。

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

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

          For - each 寫法:

          for each 語(yǔ)句是 java5 新增,在遍歷數(shù)組、集合的時(shí)候,for each 擁有不錯(cuò)的性能。

          1. public?static?void?main(String[]?args)?{

          2. ????????String[]?names?=?{"beibei",?"jingjing"};

          3. ????????for?(String?name?:?names)?{

          4. ????????????System.out.println(name);

          5. ????????}

          6. ????}

          for each 雖然能遍歷數(shù)組或者集合,但是只能用來(lái)遍歷,無(wú)法在遍歷的過(guò)程中對(duì)數(shù)組或者集合進(jìn)行修改。

          BindingResult:一個(gè) @Valid 的參數(shù)后必須緊挨著一個(gè) BindingResult 參數(shù),否則 spring 會(huì)在校驗(yàn)不通過(guò)時(shí)直接拋出異常。

          1. @Data

          2. public?class?OrderForm?{


          3. ????@NotEmpty(message?=?"姓名必填")

          4. ????private?String?name;

          5. }

          后臺(tái):

          1. @RequestMapping("save")??

          2. ????public?String?save(?@Valid?OrderForm?order,BindingResult?result)?{??

          3. ????????//??

          4. ????????if(result.hasErrors()){??

          5. ????????????List<ObjectError>?ls=result.getAllErrors();??

          6. ????????????for?(int?i?=?0;?i?

          7. ????????????????log.error("參數(shù)不正確,OrderForm={}",?order);

          8. ????????????????throw?new?SellException(

          9. ?????????????????…………?,

          10. ?????????????result.getFeildError.getDefaultMessage()

          11. ??????????????)

          12. ????????????????System.out.println("error:"+ls.get(i));??

          13. ????????????}??

          14. ????????}??

          15. ????????return?"adduser";??

          16. ????}

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

          4、List 轉(zhuǎn)為 Map

          1. public?class?Apple?{

          2. ????private?Integer?id;

          3. ????private?String?name;

          4. ????private?BigDecimal?money;

          5. ????private?Integer?num;

          6. ???/*構(gòu)造函數(shù)*/

          7. }

          1. List<Apple>?appleList?=?new?ArrayList<>();//存放apple對(duì)象集合

          2. Apple?apple1?=??new?Apple(1,"蘋果1",new?BigDecimal("3.25"),10);

          3. Apple?apple12?=?new?Apple(1,"蘋果2",new?BigDecimal("1.35"),20);

          4. Apple?apple2?=??new?Apple(2,"香蕉",new?BigDecimal("2.89"),30);

          5. Apple?apple3?=??new?Apple(3,"荔枝",new?BigDecimal("9.99"),40);

          6. appleList.add(apple1);

          7. appleList.add(apple12);

          8. appleList.add(apple2);

          9. appleList.add(apple3);

          1. Map<Integer,?Apple>?appleMap?=?

          2. appleList.stream().collect(Collectors.toMap(Apple::getId,?a?->?a,(k1,k2)->k1));

          5、Collection 的子類:List、Set

          List:ArrayList、LinkedList 、Vector

          List:有序容器,允許 null 元素,允許重復(fù)元素

          Set:元素是無(wú)序的,不允許元素

          最流行的是基于 HashMap 實(shí)現(xiàn)的 HashSet,由 [hashCode() 和 equals()]保證元素的唯一性。

          可以用 set 幫助去掉 List 中的重復(fù)元素,set 的構(gòu)造方法的參數(shù)可以是 List,構(gòu)造后是一個(gè)去重的 set。

          HashMap 的補(bǔ)充:它不是 Collection 下的

          Map 可以使用 containsKey()/containsValue() 來(lái)檢查其中是否含有某個(gè) key/value。

          HashMap 會(huì)利用對(duì)象的 hashCode 來(lái)快速找到 key。

          插入過(guò)程:通過(guò)一個(gè) hash 函數(shù)確定 Entry 的插入位置 index=hash(key),但是數(shù)組的長(zhǎng)度有限,可能會(huì)發(fā)生 index 沖突,當(dāng)發(fā)生了沖突時(shí),會(huì)使用頭插法,即為新來(lái)的 Entry 指向舊的 Entry,成為一個(gè)鏈表。

          每次插入時(shí)依次遍歷它的 index 下的單鏈表,如果存在 Key 一致的節(jié)點(diǎn),那么直接替換,并且返回新的值。

          但是單鏈表不會(huì)一直增加元素,當(dāng)元素個(gè)數(shù)超過(guò) 8 個(gè)時(shí),會(huì)嘗試將單鏈表轉(zhuǎn)化為紅黑樹存儲(chǔ)。

          為何加載因子默認(rèn)為 0.75?*(0.75 開(kāi)始擴(kuò)容)*

          答:通過(guò)源碼里的 javadoc 注釋看到,元素在哈希表中分布的桶頻率服從參數(shù)為 0.5 的泊松分布。

          源碼地址:

          https://github.com/923310233/wxOrder

          ——————END——————


          歡迎關(guān)注“Java引導(dǎo)者”,我們分享最有價(jià)值的Java的干貨文章,助力您成為有思想的Java開(kāi)發(fā)工程師!

          瀏覽 43
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  一卡二卡国产 | 日本成人麻豆三级 | 国产精品九九九九九九 | 青草福利在线 | 私人玩物七七 |