摘要:處理器根據取出的數據對模板進行渲染處理器向客戶端返回渲染后的內容作為請求的相應。于此相反,如果令牌的數量沒有超過限制,那么程序會先休眠一秒,之后在重新進行檢查。找出目前已有令牌的數量。
購物網站的redis相關實現
1、使用Redis構建文章投票網站(Java)
本文主要內容:1、登錄cookie
2、購物車cookie
3、緩存數據庫行
4、測試
必備知識點WEB應用就是通過HTTP協議對網頁瀏覽器發出的請求進行相應的服務器或者服務(Service).
一個WEB服務器對請求進行響應的典型步驟如下:
1、服務器對客戶端發來的請求(request)進行解析.
2、請求被轉發到一個預定義的處理器(handler)
3、處理器可能會從數據庫中取出數據。
4、處理器根據取出的數據對模板(template)進行渲染(rander)
5、處理器向客戶端返回渲染后的內容作為請求的相應。
以上展示了典型的web服務器運作方式,這種情況下的web請求是無狀態的(stateless),
服務器本身不會記住與過往請求有關的任何信息,這使得失效的服務器可以很容易的替換掉。
每當我們登錄互聯網服務的時候,這些服務都會使用cookie來記錄我們的身份。
cookies由少量數據組成,網站要求我們瀏覽器存儲這些數據,并且在每次服務發出請求時再將這些數據傳回服務。
對于用來登錄的cookie ,有兩種常見的方法可以將登錄信息存儲在cookie里:
簽名cookie通常會存儲用戶名,還有用戶ID,用戶最后一次登錄的時間,以及網站覺得有用的其他信息。
令牌cookie會在cookie里存儲一串隨機字節作為令牌,服務器可以根據令牌在數據庫中查找令牌的擁有者。
簽名cookie和令牌cookie的優點和缺點:
* ------------------------------------------------------------------------------------------------ * | cookie類型 | 優點 | 缺點 | * ------------------------------------------------------------------------------------------------- * | 簽名 | 驗證cookkie所需的一切信息都存儲在cookie | 正確的處理簽名很難,很容易忘記 | | | * | cookie | 還可以包含額外的信息 | 對數據簽名或者忘記驗證數據簽名, | * | | 對這些前面也很容易 | 從而造成安全漏洞 | * ------------------------------------------------------------------------------------------------- * | 令牌 | 添加信息非常容易,cookie體積小。 | 需要在服務器中存儲更多信息, | | | * | cookie | 移動端和較慢的客戶端可以更快的發送請求 | 使用關系型數據庫,載入存儲代價高 | | | * -------------------------------------------------------------------------------------------------
因為該網站沒有實現簽名cookie的需求,所以使用令牌cookie來引用關系型數據庫表中負責存儲用戶登錄信息的條目。
除了登錄信息,還可以將用戶的訪問時長和已瀏覽商品的數量等信息存儲到數據庫中,有利于更好的像用戶推銷商品
/** * 使用Redis重新實現登錄cookie,取代目前由關系型數據庫實現的登錄cookie功能 * 1、將使用一個散列來存儲登錄cookie令牌與與登錄用戶之間的映射。 * 2、需要根據給定的令牌來查找與之對應的用戶,并在已經登錄的情況下,返回該用戶id。 */ public String checkToken(Jedis conn, String token) { //1、String token = UUID.randomUUID().toString(); //2、嘗試獲取并返回令牌對應的用戶 return conn.hget("login:", token); }
/** * 1、每次用戶瀏覽頁面的時候,程序需都會對用戶存儲在登錄散列里面的信息進行更新, * 2、并將用戶的令牌和當前時間戳添加到記錄最近登錄用戶的集合里。 * 3、如果用戶正在瀏覽的是一個商品,程序還會將商品添加到記錄這個用戶最近瀏覽過的商品有序集合里面, * 4、如果記錄商品的數量超過25個時,對這個有序集合進行修剪。 */ public void updateToken(Jedis conn, String token, String user, String item) { //1、獲取當前時間戳 long timestamp = System.currentTimeMillis() / 1000; //2、維持令牌與已登錄用戶之間的映射。 conn.hset("login:", token, user); //3、記錄令牌最后一次出現的時間 conn.zadd("recent:", timestamp, token); if (item != null) { //4、記錄用戶瀏覽過的商品 conn.zadd("viewed:" + token, timestamp, item); //5、移除舊記錄,只保留用戶最近瀏覽過的25個商品 conn.zremrangeByRank("viewed:" + token, 0, -26); //6、為有序集key的成員member的score值加上增量increment。通過傳遞一個負數值increment 讓 score 減去相應的值, conn.zincrby("viewed:", -1, item); } }
/** *存儲會話數據所需的內存會隨著時間的推移而不斷增加,所有我們需要定期清理舊的會話數據。 * 1、清理會話的程序由一個循環構成,這個循環每次執行的時候,都會檢查存儲在最近登錄令牌的有序集合的大小。 * 2、如果有序集合的大小超過了限制,那么程序會從有序集合中移除最多100個最舊的令牌, * 3、并從記錄用戶登錄信息的散列里移除被刪除令牌對應的用戶信息, * 4、并對存儲了這些用戶最近瀏覽商品記錄的有序集合中進行清理。 * 5、于此相反,如果令牌的數量沒有超過限制,那么程序會先休眠一秒,之后在重新進行檢查。 */ public class CleanSessionsThread extends Thread { private Jedis conn; private int limit = 10000; private boolean quit ; public CleanSessionsThread(int limit) { this.conn = new Jedis("localhost"); this.conn.select(14); this.limit = limit; } public void quit() { quit = true; } public void run() { while (!quit) { //1、找出目前已有令牌的數量。 long size = conn.zcard("recent:"); //2、令牌數量未超過限制,休眠1秒,并在之后重新檢查 if (size <= limit) { try { sleep(1000); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); } continue; } long endIndex = Math.min(size - limit, 100); //3、獲取需要移除的令牌ID Set(2)使用redis實現購物車tokenSet = conn.zrange("recent:", 0, endIndex - 1); String[] tokens = tokenSet.toArray(new String[tokenSet.size()]); ArrayList sessionKeys = new ArrayList (); for (String token : tokens) { //4、為那些將要被刪除的令牌構建鍵名 sessionKeys.add("viewed:" + token); } //5、移除最舊的令牌 conn.del(sessionKeys.toArray(new String[sessionKeys.size()])); //6、移除被刪除令牌對應的用戶信息 conn.hdel("login:", tokens); //7、移除用戶最近瀏覽商品記錄。 conn.zrem("recent:", tokens); } } }
/** * 使用cookie實現購物車——就是將整個購物車都存儲到cookie里面, * 優點:無需對數據庫進行寫入就可以實現購物車功能, * 缺點:怎是程序需要重新解析和驗證cookie,確保cookie的格式正確。并且包含商品可以正常購買 * 還有一缺點:因為瀏覽器每次發送請求都會連cookie一起發送,所以如果購物車的體積較大, * 那么請求發送和處理的速度可能降低。 * ----------------------------------------------------------------- * 1、每個用戶的購物車都是一個散列,存儲了商品ID與商品訂單數量之間的映射。 * 2、如果用戶訂購某件商品的數量大于0,那么程序會將這件商品的ID以及用戶訂購該商品的數量添加到散列里。 * 3、如果用戶購買的商品已經存在于散列里面,那么新的訂單數量會覆蓋已有的。 * 4、相反,如果某用戶訂購某件商品數量不大于0,那么程序將從散列里移除該條目 * 5、需要對之前的會話清理函數進行更新,讓它在清理會話的同時,將舊會話對應的用戶購物車也一并刪除。 */ public void addToCart(Jedis conn, String session, String item, int count) { if (count <= 0) { //1、從購物車里面移除指定的商品 conn.hdel("cart:" + session, item); } else { //2、將指定的商品添加到購物車 conn.hset("cart:" + session, item, String.valueOf(count)); } }
5、需要對之前的會話清理函數進行更新,讓它在清理會話的同時,將舊會話對應的用戶購物車也一并刪除。
只是比CleanSessionsThread多了一行代碼,偽代碼如下:
long endIndex = Math.min(size - limit, 100); //3、獲取需要移除的令牌ID Set(3)數據行緩存tokenSet = conn.zrange("recent:", 0, endIndex - 1); String[] tokens = tokenSet.toArray(new String[tokenSet.size()]); ArrayList sessionKeys = new ArrayList (); for (String token : tokens) { //4、為那些將要被刪除的令牌構建鍵名 sessionKeys.add("viewed:" + token); //新增加的這兩行代碼用于刪除舊會話對應的購物車。 sessionKeys.add("cart:" + sess); } //5、移除最舊的令牌 conn.del(sessionKeys.toArray(new String[sessionKeys.size()])); //6、移除被刪除令牌對應的用戶信息 conn.hdel("login:", tokens); //7、移除用戶最近瀏覽商品記錄。 conn.zrem("recent:", tokens);
/** * 為了應對促銷活動帶來的大量負載,需要對數據行進行緩存,具體做法是: * 1、編寫一個持續運行的守護進程,讓這個函數指定的數據行緩存到redis里面,并不定期的更新。 * 2、緩存函數會將數據行編碼為JSON字典并存儲在Redis字典里。其中數據列的名字會被映射為JSON的字典, * 而數據行的值則被映射為JSON字典的值。 * ----------------------------------------------------------------------------------------- * 程序使用兩個有序集合來記錄應該在何時對緩存進行更新: * 1、第一個為調用有序集合,他的成員為數據行的ID,而分支則是一個時間戳, * 這個時間戳記錄了應該在何時將指定的數據行緩存到Redis里面 * 2、第二個有序集合為延時有序集合,他的成員也是數據行的ID, * 而分值則記錄了指定數據行的緩存需要每隔多少秒更新一次。 * ---------------------------------------------------------------------------------------------- * 為了讓緩存函數定期的緩存數據行,程序首先需要將hangID和給定的延遲值添加到延遲有序集合里面, * 然后再將行ID和當前指定的時間戳添加到調度有序集合里面。 */ public void scheduleRowCache(Jedis conn, String rowId, int delay) { //1、先設置數據行的延遲值 conn.zadd("delay:", delay, rowId); //2、立即對需要行村的數據進行調度 conn.zadd("schedule:", System.currentTimeMillis() / 1000, rowId); }
/** * 1、通過組合使用調度函數和持續運行緩存函數,實現類一種重讀進行調度的自動緩存機制, * 并且可以隨心所欲的控制數據行緩存的更新頻率: * 2、如果數據行記錄的是特價促銷商品的剩余數量,并且參與促銷活動的用戶特別多的話,那么最好每隔幾秒更新一次數據行緩存: * 另一方面,如果數據并不經常改變,或者商品缺貨是可以接受的,那么可以每隔幾分鐘更新一次緩存。 */ public class CacheRowsThread extends Thread { private Jedis conn; private boolean quit; public CacheRowsThread() { this.conn = new Jedis("localhost"); this.conn.select(14); } public void quit() { quit = true; } public void run() { Gson gson = new Gson(); while (!quit) { //1、嘗試獲取下一個需要被緩存的數據行以及該行的調度時間戳,返回一個包含0個或一個元組列表 Set(4)測試range = conn.zrangeWithScores("schedule:", 0, 0); Tuple next = range.size() > 0 ? range.iterator().next() : null; long now = System.currentTimeMillis() / 1000; //2、暫時沒有行需要被緩存,休眠50毫秒。 if (next == null || next.getScore() > now) { try { sleep(50); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); } continue; } //3、提前獲取下一次調度的延遲時間, String rowId = next.getElement(); double delay = conn.zscore("delay:", rowId); if (delay <= 0) { //4、不必在緩存這個行,將它從緩存中移除 conn.zrem("delay:", rowId); conn.zrem("schedule:", rowId); conn.del("inv:" + rowId); continue; } //5、繼續讀取數據行 Inventory row = Inventory.get(rowId); //6、更新調度時間,并設置緩存值。 conn.zadd("schedule:", now + delay, rowId); conn.set("inv:" + rowId, gson.toJson(row)); } } }
PS:需要好好補償英語了!!需要全部的可以到這里下載官方翻譯Java版
public class Chapter02 { public static final void main(String[] args) throws InterruptedException { new Chapter02().run(); } public void run() throws InterruptedException { Jedis conn = new Jedis("localhost"); conn.select(14); testLoginCookies(conn); testShopppingCartCookies(conn); testCacheRows(conn); testCacheRequest(conn); } public void testLoginCookies(Jedis conn) throws InterruptedException { System.out.println(" ----- testLoginCookies -----"); String token = UUID.randomUUID().toString(); updateToken(conn, token, "username", "itemX"); System.out.println("We just logged-in/updated token: " + token); System.out.println("For user: "username""); System.out.println(); System.out.println("What username do we get when we look-up that token?"); String r = checkToken(conn, token); System.out.println(r); System.out.println(); assert r != null; System.out.println("Let"s drop the maximum number of cookies to 0 to clean them out"); System.out.println("We will start a thread to do the cleaning, while we stop it later"); CleanSessionsThread thread = new CleanSessionsThread(0); thread.start(); Thread.sleep(1000); thread.quit(); Thread.sleep(2000); if (thread.isAlive()) { throw new RuntimeException("The clean sessions thread is still alive?!?"); } long s = conn.hlen("login:"); System.out.println("The current number of sessions still available is: " + s); assert s == 0; } public void testShopppingCartCookies(Jedis conn) throws InterruptedException { System.out.println(" ----- testShopppingCartCookies -----"); String token = UUID.randomUUID().toString(); System.out.println("We"ll refresh our session..."); updateToken(conn, token, "username", "itemX"); System.out.println("And add an item to the shopping cart"); addToCart(conn, token, "itemY", 3); Map參考r = conn.hgetAll("cart:" + token); System.out.println("Our shopping cart currently has:"); for (Map.Entry entry : r.entrySet()) { System.out.println(" " + entry.getKey() + ": " + entry.getValue()); } System.out.println(); assert r.size() >= 1; System.out.println("Let"s clean out our sessions and carts"); CleanFullSessionsThread thread = new CleanFullSessionsThread(0); thread.start(); Thread.sleep(1000); thread.quit(); Thread.sleep(2000); if (thread.isAlive()) { throw new RuntimeException("The clean sessions thread is still alive?!?"); } r = conn.hgetAll("cart:" + token); System.out.println("Our shopping cart now contains:"); for (Map.Entry entry : r.entrySet()) { System.out.println(" " + entry.getKey() + ": " + entry.getValue()); } assert r.size() == 0; } public void testCacheRows(Jedis conn) throws InterruptedException { System.out.println(" ----- testCacheRows -----"); System.out.println("First, let"s schedule caching of itemX every 5 seconds"); scheduleRowCache(conn, "itemX", 5); System.out.println("Our schedule looks like:"); Set s = conn.zrangeWithScores("schedule:", 0, -1); for (Tuple tuple : s) { System.out.println(" " + tuple.getElement() + ", " + tuple.getScore()); } assert s.size() != 0; System.out.println("We"ll start a caching thread that will cache the data..."); CacheRowsThread thread = new CacheRowsThread(); thread.start(); Thread.sleep(1000); System.out.println("Our cached data looks like:"); String r = conn.get("inv:itemX"); System.out.println(r); assert r != null; System.out.println(); System.out.println("We"ll check again in 5 seconds..."); Thread.sleep(5000); System.out.println("Notice that the data has changed..."); String r2 = conn.get("inv:itemX"); System.out.println(r2); System.out.println(); assert r2 != null; assert !r.equals(r2); System.out.println("Let"s force un-caching"); scheduleRowCache(conn, "itemX", -1); Thread.sleep(1000); r = conn.get("inv:itemX"); System.out.println("The cache was cleared? " + (r == null)); assert r == null; thread.quit(); Thread.sleep(2000); if (thread.isAlive()) { throw new RuntimeException("The database caching thread is still alive?!?"); } } }
Redis實戰
Redis實戰相關代碼,目前有Java,JS,node,Python
2.Redis 命令參考
代碼地址https://github.com/guoxiaoxu/...
后記如果你有耐心讀到這里,請允許我說明下:
1、因為技術能力有限,沒有梳理清另外兩小節,待我在琢磨琢磨。后續補上。
2、看老外寫的書像看故事一樣,越看越精彩。不知道你們有這種感覺么?
3、越學越發現自己需要補充的知識太多了,給我力量吧,歡迎點贊。
4、感謝所有人,感謝SegmentFault,讓你見證我脫變的過程吧。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/61942.html
摘要:處理器根據取出的數據對模板進行渲染處理器向客戶端返回渲染后的內容作為請求的相應。于此相反,如果令牌的數量沒有超過限制,那么程序會先休眠一秒,之后在重新進行檢查。找出目前已有令牌的數量。 購物網站的redis相關實現 1、使用Redis構建文章投票網站(Java) 本文主要內容: 1、登錄cookie 2、購物車cookie 3、緩存數據庫行 4、測試 必備知識點 WEB應用就是通...
摘要:文章投票網站的相關實現需求要構建一個文章投票網站,文章需要在一天內至少獲得張票,才能優先顯示在當天文章列表前列。文章發布期滿一周后,用戶不能在對它投票。此命令會覆蓋哈希表中已存在的域。 文章投票網站的redis相關Java實現 需求: 1、要構建一個文章投票網站,文章需要在一天內至少獲得200張票,才能優先顯示在當天文章列表前列。 2、但是為了避免發布時間較久的文章由于累計的票數較多...
閱讀 1895·2021-11-15 11:46
閱讀 1091·2021-10-26 09:49
閱讀 1825·2021-10-14 09:42
閱讀 3384·2021-09-26 09:55
閱讀 838·2019-08-30 13:58
閱讀 1039·2019-08-29 16:40
閱讀 3474·2019-08-26 10:27
閱讀 611·2019-08-23 18:18