摘要:處理器根據(jù)取出的數(shù)據(jù)對(duì)模板進(jìn)行渲染處理器向客戶(hù)端返回渲染后的內(nèi)容作為請(qǐng)求的相應(yīng)。于此相反,如果令牌的數(shù)量沒(méi)有超過(guò)限制,那么程序會(huì)先休眠一秒,之后在重新進(jìn)行檢查。找出目前已有令牌的數(shù)量。
購(gòu)物網(wǎng)站的redis相關(guān)實(shí)現(xiàn)
1、使用Redis構(gòu)建文章投票網(wǎng)站(Java)
本文主要內(nèi)容:1、登錄cookie
2、購(gòu)物車(chē)cookie
3、緩存數(shù)據(jù)庫(kù)行
4、測(cè)試
必備知識(shí)點(diǎn)WEB應(yīng)用就是通過(guò)HTTP協(xié)議對(duì)網(wǎng)頁(yè)瀏覽器發(fā)出的請(qǐng)求進(jìn)行相應(yīng)的服務(wù)器或者服務(wù)(Service).
一個(gè)WEB服務(wù)器對(duì)請(qǐng)求進(jìn)行響應(yīng)的典型步驟如下:
1、服務(wù)器對(duì)客戶(hù)端發(fā)來(lái)的請(qǐng)求(request)進(jìn)行解析.
2、請(qǐng)求被轉(zhuǎn)發(fā)到一個(gè)預(yù)定義的處理器(handler)
3、處理器可能會(huì)從數(shù)據(jù)庫(kù)中取出數(shù)據(jù)。
4、處理器根據(jù)取出的數(shù)據(jù)對(duì)模板(template)進(jìn)行渲染(rander)
5、處理器向客戶(hù)端返回渲染后的內(nèi)容作為請(qǐng)求的相應(yīng)。
以上展示了典型的web服務(wù)器運(yùn)作方式,這種情況下的web請(qǐng)求是無(wú)狀態(tài)的(stateless),
服務(wù)器本身不會(huì)記住與過(guò)往請(qǐng)求有關(guān)的任何信息,這使得失效的服務(wù)器可以很容易的替換掉。
每當(dāng)我們登錄互聯(lián)網(wǎng)服務(wù)的時(shí)候,這些服務(wù)都會(huì)使用cookie來(lái)記錄我們的身份。
cookies由少量數(shù)據(jù)組成,網(wǎng)站要求我們?yōu)g覽器存儲(chǔ)這些數(shù)據(jù),并且在每次服務(wù)發(fā)出請(qǐng)求時(shí)再將這些數(shù)據(jù)傳回服務(wù)。
對(duì)于用來(lái)登錄的cookie ,有兩種常見(jiàn)的方法可以將登錄信息存儲(chǔ)在cookie里:
簽名cookie通常會(huì)存儲(chǔ)用戶(hù)名,還有用戶(hù)ID,用戶(hù)最后一次登錄的時(shí)間,以及網(wǎng)站覺(jué)得有用的其他信息。
令牌cookie會(huì)在cookie里存儲(chǔ)一串隨機(jī)字節(jié)作為令牌,服務(wù)器可以根據(jù)令牌在數(shù)據(jù)庫(kù)中查找令牌的擁有者。
簽名cookie和令牌cookie的優(yōu)點(diǎn)和缺點(diǎn):
* ------------------------------------------------------------------------------------------------ * | cookie類(lèi)型 | 優(yōu)點(diǎn) | 缺點(diǎn) | * ------------------------------------------------------------------------------------------------- * | 簽名 | 驗(yàn)證cookkie所需的一切信息都存儲(chǔ)在cookie | 正確的處理簽名很難,很容易忘記 | | | * | cookie | 還可以包含額外的信息 | 對(duì)數(shù)據(jù)簽名或者忘記驗(yàn)證數(shù)據(jù)簽名, | * | | 對(duì)這些前面也很容易 | 從而造成安全漏洞 | * ------------------------------------------------------------------------------------------------- * | 令牌 | 添加信息非常容易,cookie體積小。 | 需要在服務(wù)器中存儲(chǔ)更多信息, | | | * | cookie | 移動(dòng)端和較慢的客戶(hù)端可以更快的發(fā)送請(qǐng)求 | 使用關(guān)系型數(shù)據(jù)庫(kù),載入存儲(chǔ)代價(jià)高 | | | * -------------------------------------------------------------------------------------------------
因?yàn)樵摼W(wǎng)站沒(méi)有實(shí)現(xiàn)簽名cookie的需求,所以使用令牌cookie來(lái)引用關(guān)系型數(shù)據(jù)庫(kù)表中負(fù)責(zé)存儲(chǔ)用戶(hù)登錄信息的條目。
除了登錄信息,還可以將用戶(hù)的訪問(wèn)時(shí)長(zhǎng)和已瀏覽商品的數(shù)量等信息存儲(chǔ)到數(shù)據(jù)庫(kù)中,有利于更好的像用戶(hù)推銷(xiāo)商品
/** * 使用Redis重新實(shí)現(xiàn)登錄cookie,取代目前由關(guān)系型數(shù)據(jù)庫(kù)實(shí)現(xiàn)的登錄cookie功能 * 1、將使用一個(gè)散列來(lái)存儲(chǔ)登錄cookie令牌與與登錄用戶(hù)之間的映射。 * 2、需要根據(jù)給定的令牌來(lái)查找與之對(duì)應(yīng)的用戶(hù),并在已經(jīng)登錄的情況下,返回該用戶(hù)id。 */ public String checkToken(Jedis conn, String token) { //1、String token = UUID.randomUUID().toString(); //2、嘗試獲取并返回令牌對(duì)應(yīng)的用戶(hù) return conn.hget("login:", token); }
/** * 1、每次用戶(hù)瀏覽頁(yè)面的時(shí)候,程序需都會(huì)對(duì)用戶(hù)存儲(chǔ)在登錄散列里面的信息進(jìn)行更新, * 2、并將用戶(hù)的令牌和當(dāng)前時(shí)間戳添加到記錄最近登錄用戶(hù)的集合里。 * 3、如果用戶(hù)正在瀏覽的是一個(gè)商品,程序還會(huì)將商品添加到記錄這個(gè)用戶(hù)最近瀏覽過(guò)的商品有序集合里面, * 4、如果記錄商品的數(shù)量超過(guò)25個(gè)時(shí),對(duì)這個(gè)有序集合進(jìn)行修剪。 */ public void updateToken(Jedis conn, String token, String user, String item) { //1、獲取當(dāng)前時(shí)間戳 long timestamp = System.currentTimeMillis() / 1000; //2、維持令牌與已登錄用戶(hù)之間的映射。 conn.hset("login:", token, user); //3、記錄令牌最后一次出現(xiàn)的時(shí)間 conn.zadd("recent:", timestamp, token); if (item != null) { //4、記錄用戶(hù)瀏覽過(guò)的商品 conn.zadd("viewed:" + token, timestamp, item); //5、移除舊記錄,只保留用戶(hù)最近瀏覽過(guò)的25個(gè)商品 conn.zremrangeByRank("viewed:" + token, 0, -26); //6、為有序集key的成員member的score值加上增量increment。通過(guò)傳遞一個(gè)負(fù)數(shù)值increment 讓 score 減去相應(yīng)的值, conn.zincrby("viewed:", -1, item); } }
/** *存儲(chǔ)會(huì)話數(shù)據(jù)所需的內(nèi)存會(huì)隨著時(shí)間的推移而不斷增加,所有我們需要定期清理舊的會(huì)話數(shù)據(jù)。 * 1、清理會(huì)話的程序由一個(gè)循環(huán)構(gòu)成,這個(gè)循環(huán)每次執(zhí)行的時(shí)候,都會(huì)檢查存儲(chǔ)在最近登錄令牌的有序集合的大小。 * 2、如果有序集合的大小超過(guò)了限制,那么程序會(huì)從有序集合中移除最多100個(gè)最舊的令牌, * 3、并從記錄用戶(hù)登錄信息的散列里移除被刪除令牌對(duì)應(yīng)的用戶(hù)信息, * 4、并對(duì)存儲(chǔ)了這些用戶(hù)最近瀏覽商品記錄的有序集合中進(jìn)行清理。 * 5、于此相反,如果令牌的數(shù)量沒(méi)有超過(guò)限制,那么程序會(huì)先休眠一秒,之后在重新進(jìn)行檢查。 */ 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ù)量未超過(guò)限制,休眠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實(shí)現(xiàn)購(gòu)物車(chē)tokenSet = conn.zrange("recent:", 0, endIndex - 1); String[] tokens = tokenSet.toArray(new String[tokenSet.size()]); ArrayList sessionKeys = new ArrayList (); for (String token : tokens) { //4、為那些將要被刪除的令牌構(gòu)建鍵名 sessionKeys.add("viewed:" + token); } //5、移除最舊的令牌 conn.del(sessionKeys.toArray(new String[sessionKeys.size()])); //6、移除被刪除令牌對(duì)應(yīng)的用戶(hù)信息 conn.hdel("login:", tokens); //7、移除用戶(hù)最近瀏覽商品記錄。 conn.zrem("recent:", tokens); } } }
/** * 使用cookie實(shí)現(xiàn)購(gòu)物車(chē)——就是將整個(gè)購(gòu)物車(chē)都存儲(chǔ)到cookie里面, * 優(yōu)點(diǎn):無(wú)需對(duì)數(shù)據(jù)庫(kù)進(jìn)行寫(xiě)入就可以實(shí)現(xiàn)購(gòu)物車(chē)功能, * 缺點(diǎn):怎是程序需要重新解析和驗(yàn)證cookie,確保cookie的格式正確。并且包含商品可以正常購(gòu)買(mǎi) * 還有一缺點(diǎn):因?yàn)闉g覽器每次發(fā)送請(qǐng)求都會(huì)連cookie一起發(fā)送,所以如果購(gòu)物車(chē)的體積較大, * 那么請(qǐng)求發(fā)送和處理的速度可能降低。 * ----------------------------------------------------------------- * 1、每個(gè)用戶(hù)的購(gòu)物車(chē)都是一個(gè)散列,存儲(chǔ)了商品ID與商品訂單數(shù)量之間的映射。 * 2、如果用戶(hù)訂購(gòu)某件商品的數(shù)量大于0,那么程序會(huì)將這件商品的ID以及用戶(hù)訂購(gòu)該商品的數(shù)量添加到散列里。 * 3、如果用戶(hù)購(gòu)買(mǎi)的商品已經(jīng)存在于散列里面,那么新的訂單數(shù)量會(huì)覆蓋已有的。 * 4、相反,如果某用戶(hù)訂購(gòu)某件商品數(shù)量不大于0,那么程序?qū)纳⒘欣镆瞥摋l目 * 5、需要對(duì)之前的會(huì)話清理函數(shù)進(jìn)行更新,讓它在清理會(huì)話的同時(shí),將舊會(huì)話對(duì)應(yīng)的用戶(hù)購(gòu)物車(chē)也一并刪除。 */ public void addToCart(Jedis conn, String session, String item, int count) { if (count <= 0) { //1、從購(gòu)物車(chē)?yán)锩嬉瞥付ǖ纳唐? conn.hdel("cart:" + session, item); } else { //2、將指定的商品添加到購(gòu)物車(chē) conn.hset("cart:" + session, item, String.valueOf(count)); } }
5、需要對(duì)之前的會(huì)話清理函數(shù)進(jìn)行更新,讓它在清理會(huì)話的同時(shí),將舊會(huì)話對(duì)應(yīng)的用戶(hù)購(gòu)物車(chē)也一并刪除。
只是比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、為那些將要被刪除的令牌構(gòu)建鍵名 sessionKeys.add("viewed:" + token); //新增加的這兩行代碼用于刪除舊會(huì)話對(duì)應(yīng)的購(gòu)物車(chē)。 sessionKeys.add("cart:" + sess); } //5、移除最舊的令牌 conn.del(sessionKeys.toArray(new String[sessionKeys.size()])); //6、移除被刪除令牌對(duì)應(yīng)的用戶(hù)信息 conn.hdel("login:", tokens); //7、移除用戶(hù)最近瀏覽商品記錄。 conn.zrem("recent:", tokens);
/** * 為了應(yīng)對(duì)促銷(xiāo)活動(dòng)帶來(lái)的大量負(fù)載,需要對(duì)數(shù)據(jù)行進(jìn)行緩存,具體做法是: * 1、編寫(xiě)一個(gè)持續(xù)運(yùn)行的守護(hù)進(jìn)程,讓這個(gè)函數(shù)指定的數(shù)據(jù)行緩存到redis里面,并不定期的更新。 * 2、緩存函數(shù)會(huì)將數(shù)據(jù)行編碼為JSON字典并存儲(chǔ)在Redis字典里。其中數(shù)據(jù)列的名字會(huì)被映射為JSON的字典, * 而數(shù)據(jù)行的值則被映射為JSON字典的值。 * ----------------------------------------------------------------------------------------- * 程序使用兩個(gè)有序集合來(lái)記錄應(yīng)該在何時(shí)對(duì)緩存進(jìn)行更新: * 1、第一個(gè)為調(diào)用有序集合,他的成員為數(shù)據(jù)行的ID,而分支則是一個(gè)時(shí)間戳, * 這個(gè)時(shí)間戳記錄了應(yīng)該在何時(shí)將指定的數(shù)據(jù)行緩存到Redis里面 * 2、第二個(gè)有序集合為延時(shí)有序集合,他的成員也是數(shù)據(jù)行的ID, * 而分值則記錄了指定數(shù)據(jù)行的緩存需要每隔多少秒更新一次。 * ---------------------------------------------------------------------------------------------- * 為了讓緩存函數(shù)定期的緩存數(shù)據(jù)行,程序首先需要將hangID和給定的延遲值添加到延遲有序集合里面, * 然后再將行ID和當(dāng)前指定的時(shí)間戳添加到調(diào)度有序集合里面。 */ public void scheduleRowCache(Jedis conn, String rowId, int delay) { //1、先設(shè)置數(shù)據(jù)行的延遲值 conn.zadd("delay:", delay, rowId); //2、立即對(duì)需要行村的數(shù)據(jù)進(jìn)行調(diào)度 conn.zadd("schedule:", System.currentTimeMillis() / 1000, rowId); }
/** * 1、通過(guò)組合使用調(diào)度函數(shù)和持續(xù)運(yùn)行緩存函數(shù),實(shí)現(xiàn)類(lèi)一種重讀進(jìn)行調(diào)度的自動(dòng)緩存機(jī)制, * 并且可以隨心所欲的控制數(shù)據(jù)行緩存的更新頻率: * 2、如果數(shù)據(jù)行記錄的是特價(jià)促銷(xiāo)商品的剩余數(shù)量,并且參與促銷(xiāo)活動(dòng)的用戶(hù)特別多的話,那么最好每隔幾秒更新一次數(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、嘗試獲取下一個(gè)需要被緩存的數(shù)據(jù)行以及該行的調(diào)度時(shí)間戳,返回一個(gè)包含0個(gè)或一個(gè)元組列表 Set(4)測(cè)試range = conn.zrangeWithScores("schedule:", 0, 0); Tuple next = range.size() > 0 ? range.iterator().next() : null; long now = System.currentTimeMillis() / 1000; //2、暫時(shí)沒(méi)有行需要被緩存,休眠50毫秒。 if (next == null || next.getScore() > now) { try { sleep(50); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); } continue; } //3、提前獲取下一次調(diào)度的延遲時(shí)間, String rowId = next.getElement(); double delay = conn.zscore("delay:", rowId); if (delay <= 0) { //4、不必在緩存這個(gè)行,將它從緩存中移除 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)度時(shí)間,并設(shè)置緩存值。 conn.zadd("schedule:", now + delay, rowId); conn.set("inv:" + rowId, gson.toJson(row)); } } }
PS:需要好好補(bǔ)償英語(yǔ)了!!需要全部的可以到這里下載官方翻譯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實(shí)戰(zhàn)
Redis實(shí)戰(zhàn)相關(guān)代碼,目前有Java,JS,node,Python
2.Redis 命令參考
代碼地址https://github.com/guoxiaoxu/...
后記如果你有耐心讀到這里,請(qǐng)?jiān)试S我說(shuō)明下:
1、因?yàn)榧夹g(shù)能力有限,沒(méi)有梳理清另外兩小節(jié),待我在琢磨琢磨。后續(xù)補(bǔ)上。
2、看老外寫(xiě)的書(shū)像看故事一樣,越看越精彩。不知道你們有這種感覺(jué)么?
3、越學(xué)越發(fā)現(xiàn)自己需要補(bǔ)充的知識(shí)太多了,給我力量吧,歡迎點(diǎn)贊。
4、感謝所有人,感謝SegmentFault,讓你見(jiàn)證我脫變的過(guò)程吧。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/61942.html
摘要:處理器根據(jù)取出的數(shù)據(jù)對(duì)模板進(jìn)行渲染處理器向客戶(hù)端返回渲染后的內(nèi)容作為請(qǐng)求的相應(yīng)。于此相反,如果令牌的數(shù)量沒(méi)有超過(guò)限制,那么程序會(huì)先休眠一秒,之后在重新進(jìn)行檢查。找出目前已有令牌的數(shù)量。 購(gòu)物網(wǎng)站的redis相關(guān)實(shí)現(xiàn) 1、使用Redis構(gòu)建文章投票網(wǎng)站(Java) 本文主要內(nèi)容: 1、登錄cookie 2、購(gòu)物車(chē)cookie 3、緩存數(shù)據(jù)庫(kù)行 4、測(cè)試 必備知識(shí)點(diǎn) WEB應(yīng)用就是通...
摘要:文章投票網(wǎng)站的相關(guān)實(shí)現(xiàn)需求要構(gòu)建一個(gè)文章投票網(wǎng)站,文章需要在一天內(nèi)至少獲得張票,才能優(yōu)先顯示在當(dāng)天文章列表前列。文章發(fā)布期滿(mǎn)一周后,用戶(hù)不能在對(duì)它投票。此命令會(huì)覆蓋哈希表中已存在的域。 文章投票網(wǎng)站的redis相關(guān)Java實(shí)現(xiàn) 需求: 1、要構(gòu)建一個(gè)文章投票網(wǎng)站,文章需要在一天內(nèi)至少獲得200張票,才能優(yōu)先顯示在當(dāng)天文章列表前列。 2、但是為了避免發(fā)布時(shí)間較久的文章由于累計(jì)的票數(shù)較多...
閱讀 1892·2021-11-15 11:46
閱讀 1087·2021-10-26 09:49
閱讀 1824·2021-10-14 09:42
閱讀 3381·2021-09-26 09:55
閱讀 834·2019-08-30 13:58
閱讀 1037·2019-08-29 16:40
閱讀 3471·2019-08-26 10:27
閱讀 609·2019-08-23 18:18