摘要:處理器根據(jù)取出的數(shù)據(jù)對模板進行渲染處理器向客戶端返回渲染后的內(nèi)容作為請求的相應。于此相反,如果令牌的數(shù)量沒有超過限制,那么程序會先休眠一秒,之后在重新進行檢查。找出目前已有令牌的數(shù)量。
購物網(wǎng)站的redis相關實現(xiàn)
1、使用Redis構建文章投票網(wǎng)站(Java)
本文主要內(nèi)容:1、登錄cookie
2、購物車cookie
3、緩存數(shù)據(jù)庫行
4、測試
必備知識點WEB應用就是通過HTTP協(xié)議對網(wǎng)頁瀏覽器發(fā)出的請求進行相應的服務器或者服務(Service).
一個WEB服務器對請求進行響應的典型步驟如下:
1、服務器對客戶端發(fā)來的請求(request)進行解析.
2、請求被轉(zhuǎn)發(fā)到一個預定義的處理器(handler)
3、處理器可能會從數(shù)據(jù)庫中取出數(shù)據(jù)。
4、處理器根據(jù)取出的數(shù)據(jù)對模板(template)進行渲染(rander)
5、處理器向客戶端返回渲染后的內(nèi)容作為請求的相應。
以上展示了典型的web服務器運作方式,這種情況下的web請求是無狀態(tài)的(stateless),
服務器本身不會記住與過往請求有關的任何信息,這使得失效的服務器可以很容易的替換掉。
每當我們登錄互聯(lián)網(wǎng)服務的時候,這些服務都會使用cookie來記錄我們的身份。
cookies由少量數(shù)據(jù)組成,網(wǎng)站要求我們?yōu)g覽器存儲這些數(shù)據(jù),并且在每次服務發(fā)出請求時再將這些數(shù)據(jù)傳回服務。
對于用來登錄的cookie ,有兩種常見的方法可以將登錄信息存儲在cookie里:
簽名cookie通常會存儲用戶名,還有用戶ID,用戶最后一次登錄的時間,以及網(wǎng)站覺得有用的其他信息。
令牌cookie會在cookie里存儲一串隨機字節(jié)作為令牌,服務器可以根據(jù)令牌在數(shù)據(jù)庫中查找令牌的擁有者。
簽名cookie和令牌cookie的優(yōu)點和缺點:
* ------------------------------------------------------------------------------------------------ * | cookie類型 | 優(yōu)點 | 缺點 | * ------------------------------------------------------------------------------------------------- * | 簽名 | 驗證cookkie所需的一切信息都存儲在cookie | 正確的處理簽名很難,很容易忘記 | | | * | cookie | 還可以包含額外的信息 | 對數(shù)據(jù)簽名或者忘記驗證數(shù)據(jù)簽名, | * | | 對這些前面也很容易 | 從而造成安全漏洞 | * ------------------------------------------------------------------------------------------------- * | 令牌 | 添加信息非常容易,cookie體積小。 | 需要在服務器中存儲更多信息, | | | * | cookie | 移動端和較慢的客戶端可以更快的發(fā)送請求 | 使用關系型數(shù)據(jù)庫,載入存儲代價高 | | | * -------------------------------------------------------------------------------------------------
因為該網(wǎng)站沒有實現(xiàn)簽名cookie的需求,所以使用令牌cookie來引用關系型數(shù)據(jù)庫表中負責存儲用戶登錄信息的條目。
除了登錄信息,還可以將用戶的訪問時長和已瀏覽商品的數(shù)量等信息存儲到數(shù)據(jù)庫中,有利于更好的像用戶推銷商品
/** * 使用Redis重新實現(xiàn)登錄cookie,取代目前由關系型數(shù)據(jù)庫實現(xiàn)的登錄cookie功能 * 1、將使用一個散列來存儲登錄cookie令牌與與登錄用戶之間的映射。 * 2、需要根據(jù)給定的令牌來查找與之對應的用戶,并在已經(jīng)登錄的情況下,返回該用戶id。 */ public String checkToken(Jedis conn, String token) { //1、String token = UUID.randomUUID().toString(); //2、嘗試獲取并返回令牌對應的用戶 return conn.hget("login:", token); }
/** * 1、每次用戶瀏覽頁面的時候,程序需都會對用戶存儲在登錄散列里面的信息進行更新, * 2、并將用戶的令牌和當前時間戳添加到記錄最近登錄用戶的集合里。 * 3、如果用戶正在瀏覽的是一個商品,程序還會將商品添加到記錄這個用戶最近瀏覽過的商品有序集合里面, * 4、如果記錄商品的數(shù)量超過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、記錄令牌最后一次出現(xiàn)的時間 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。通過傳遞一個負數(shù)值increment 讓 score 減去相應的值, conn.zincrby("viewed:", -1, item); } }
/** *存儲會話數(shù)據(jù)所需的內(nèi)存會隨著時間的推移而不斷增加,所有我們需要定期清理舊的會話數(shù)據(jù)。 * 1、清理會話的程序由一個循環(huán)構成,這個循環(huán)每次執(zhí)行的時候,都會檢查存儲在最近登錄令牌的有序集合的大小。 * 2、如果有序集合的大小超過了限制,那么程序會從有序集合中移除最多100個最舊的令牌, * 3、并從記錄用戶登錄信息的散列里移除被刪除令牌對應的用戶信息, * 4、并對存儲了這些用戶最近瀏覽商品記錄的有序集合中進行清理。 * 5、于此相反,如果令牌的數(shù)量沒有超過限制,那么程序會先休眠一秒,之后在重新進行檢查。 */ 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、找出目前已有令牌的數(shù)量。 long size = conn.zcard("recent:"); //2、令牌數(shù)量未超過限制,休眠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實現(xiàn)購物車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實現(xiàn)購物車——就是將整個購物車都存儲到cookie里面, * 優(yōu)點:無需對數(shù)據(jù)庫進行寫入就可以實現(xiàn)購物車功能, * 缺點:怎是程序需要重新解析和驗證cookie,確保cookie的格式正確。并且包含商品可以正常購買 * 還有一缺點:因為瀏覽器每次發(fā)送請求都會連cookie一起發(fā)送,所以如果購物車的體積較大, * 那么請求發(fā)送和處理的速度可能降低。 * ----------------------------------------------------------------- * 1、每個用戶的購物車都是一個散列,存儲了商品ID與商品訂單數(shù)量之間的映射。 * 2、如果用戶訂購某件商品的數(shù)量大于0,那么程序會將這件商品的ID以及用戶訂購該商品的數(shù)量添加到散列里。 * 3、如果用戶購買的商品已經(jīng)存在于散列里面,那么新的訂單數(shù)量會覆蓋已有的。 * 4、相反,如果某用戶訂購某件商品數(shù)量不大于0,那么程序?qū)纳⒘欣镆瞥摋l目 * 5、需要對之前的會話清理函數(shù)進行更新,讓它在清理會話的同時,將舊會話對應的用戶購物車也一并刪除。 */ 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、需要對之前的會話清理函數(shù)進行更新,讓它在清理會話的同時,將舊會話對應的用戶購物車也一并刪除。
只是比CleanSessionsThread多了一行代碼,偽代碼如下:
long endIndex = Math.min(size - limit, 100); //3、獲取需要移除的令牌ID Set(3)數(shù)據(jù)行緩存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);
/** * 為了應對促銷活動帶來的大量負載,需要對數(shù)據(jù)行進行緩存,具體做法是: * 1、編寫一個持續(xù)運行的守護進程,讓這個函數(shù)指定的數(shù)據(jù)行緩存到redis里面,并不定期的更新。 * 2、緩存函數(shù)會將數(shù)據(jù)行編碼為JSON字典并存儲在Redis字典里。其中數(shù)據(jù)列的名字會被映射為JSON的字典, * 而數(shù)據(jù)行的值則被映射為JSON字典的值。 * ----------------------------------------------------------------------------------------- * 程序使用兩個有序集合來記錄應該在何時對緩存進行更新: * 1、第一個為調(diào)用有序集合,他的成員為數(shù)據(jù)行的ID,而分支則是一個時間戳, * 這個時間戳記錄了應該在何時將指定的數(shù)據(jù)行緩存到Redis里面 * 2、第二個有序集合為延時有序集合,他的成員也是數(shù)據(jù)行的ID, * 而分值則記錄了指定數(shù)據(jù)行的緩存需要每隔多少秒更新一次。 * ---------------------------------------------------------------------------------------------- * 為了讓緩存函數(shù)定期的緩存數(shù)據(jù)行,程序首先需要將hangID和給定的延遲值添加到延遲有序集合里面, * 然后再將行ID和當前指定的時間戳添加到調(diào)度有序集合里面。 */ public void scheduleRowCache(Jedis conn, String rowId, int delay) { //1、先設置數(shù)據(jù)行的延遲值 conn.zadd("delay:", delay, rowId); //2、立即對需要行村的數(shù)據(jù)進行調(diào)度 conn.zadd("schedule:", System.currentTimeMillis() / 1000, rowId); }
/** * 1、通過組合使用調(diào)度函數(shù)和持續(xù)運行緩存函數(shù),實現(xiàn)類一種重讀進行調(diào)度的自動緩存機制, * 并且可以隨心所欲的控制數(shù)據(jù)行緩存的更新頻率: * 2、如果數(shù)據(jù)行記錄的是特價促銷商品的剩余數(shù)量,并且參與促銷活動的用戶特別多的話,那么最好每隔幾秒更新一次數(shù)據(jù)行緩存: * 另一方面,如果數(shù)據(jù)并不經(jīng)常改變,或者商品缺貨是可以接受的,那么可以每隔幾分鐘更新一次緩存。 */ 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、嘗試獲取下一個需要被緩存的數(shù)據(jù)行以及該行的調(diào)度時間戳,返回一個包含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、提前獲取下一次調(diào)度的延遲時間, 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、繼續(xù)讀取數(shù)據(jù)行 Inventory row = Inventory.get(rowId); //6、更新調(diào)度時間,并設置緩存值。 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實戰(zhàn)
Redis實戰(zhàn)相關代碼,目前有Java,JS,node,Python
2.Redis 命令參考
代碼地址https://github.com/guoxiaoxu/...
后記如果你有耐心讀到這里,請允許我說明下:
1、因為技術能力有限,沒有梳理清另外兩小節(jié),待我在琢磨琢磨。后續(xù)補上。
2、看老外寫的書像看故事一樣,越看越精彩。不知道你們有這種感覺么?
3、越學越發(fā)現(xiàn)自己需要補充的知識太多了,給我力量吧,歡迎點贊。
4、感謝所有人,感謝SegmentFault,讓你見證我脫變的過程吧。
文章版權歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/71001.html
摘要:處理器根據(jù)取出的數(shù)據(jù)對模板進行渲染處理器向客戶端返回渲染后的內(nèi)容作為請求的相應。于此相反,如果令牌的數(shù)量沒有超過限制,那么程序會先休眠一秒,之后在重新進行檢查。找出目前已有令牌的數(shù)量。 購物網(wǎng)站的redis相關實現(xiàn) 1、使用Redis構建文章投票網(wǎng)站(Java) 本文主要內(nèi)容: 1、登錄cookie 2、購物車cookie 3、緩存數(shù)據(jù)庫行 4、測試 必備知識點 WEB應用就是通...
摘要:文章投票網(wǎng)站的相關實現(xiàn)需求要構建一個文章投票網(wǎng)站,文章需要在一天內(nèi)至少獲得張票,才能優(yōu)先顯示在當天文章列表前列。文章發(fā)布期滿一周后,用戶不能在對它投票。此命令會覆蓋哈希表中已存在的域。 文章投票網(wǎng)站的redis相關Java實現(xiàn) 需求: 1、要構建一個文章投票網(wǎng)站,文章需要在一天內(nèi)至少獲得200張票,才能優(yōu)先顯示在當天文章列表前列。 2、但是為了避免發(fā)布時間較久的文章由于累計的票數(shù)較多...
閱讀 2643·2021-11-23 09:51
閱讀 905·2021-09-24 10:37
閱讀 3627·2021-09-02 15:15
閱讀 1971·2019-08-30 13:03
閱讀 1892·2019-08-29 15:41
閱讀 2637·2019-08-29 14:12
閱讀 1436·2019-08-29 11:19
閱讀 3312·2019-08-26 13:39