微博遭鹿晗恋情暴击瘫痪,如何快速提高系统性能?
人生處處是驚喜。國慶假期最后一天,當(dāng)紅流量小生鹿晗于正午 12 點(diǎn),在微博公布戀情。
微博一經(jīng)發(fā)布后,瞬間刷爆朋友圈、各大網(wǎng)站頭條,還導(dǎo)致了微博一時間癱瘓。微博客服也很無奈表示:具體怎么造成的,大家心里也都有數(shù)。
然整個事件當(dāng)中,除了無數(shù)粉碎的少女心,最無辜躺槍還如此敬業(yè)的小哥哥,非這位微博搜索工程師丁振凱莫屬啦,結(jié)婚當(dāng)日,遇鹿晗公布戀情,不得不從酒席上離開處理微博異常后繼續(xù)婚禮,心疼小哥哥三秒鐘……
回顧一下,究竟是多大的流量使得曾豪言“微博服務(wù)器穩(wěn)定,能同時應(yīng)付三對劈腿的”壯志秒破功,具體數(shù)據(jù)如下圖所示:
按照微博明星勢力榜各個榜單計分方式:滿分100分,由閱讀數(shù)、互動數(shù)、社會影響力、愛慕值四項組成,所占比例分別為30%、30%、20%、20%。
由上可以看出,鹿晗所發(fā)微博的每一項到達(dá)到了峰值,那么在如此高流量的情況之下,作為開發(fā)者是否有好的方法來快速提高系統(tǒng)性能呢?
接下來,來自 CSDN 博客專家的“拿客_三產(chǎn)”,拿客站長,與我們分享目前最為炙手可熱的 Key-Value 數(shù)據(jù)庫、常用做緩存、Session 共享中間件、分布式鎖等的——Redis 技術(shù)。
說明:閱讀本文章需要一定 Web 開發(fā)經(jīng)驗,最好對 Redis 有一個基本的認(rèn)知,文章最后的附錄也會為大家提供一些相關(guān)的文章,本文章只是為了讓那些對 Redis 的應(yīng)用僅僅局限于 緩存 的開發(fā)人員了解到 Redis 更多可能的應(yīng)用場景,由于篇幅限制,文中很多場景只是闡述了實(shí)現(xiàn)的思想及部分原理,僅僅提供了部分功能的具體實(shí)現(xiàn)。
現(xiàn)代高并發(fā)復(fù)雜系統(tǒng)面臨的挑戰(zhàn)
現(xiàn)代系統(tǒng)隨著功能的復(fù)雜化,各種各樣需求層出不窮,面對愈加復(fù)雜話的業(yè)務(wù)系統(tǒng)、越來越龐大的用戶群體,以及用戶對體驗的要求越來越高,性能就變得更加重要。
拋開代碼邏輯、服務(wù)器性能的相關(guān)問題外,提高性能的方式有以下幾種:
動靜分離
負(fù)載均衡
分布式
集群化
緩存
限流處理
數(shù)據(jù)壓縮
其他
我們來分析一下負(fù)載均衡、分布式、集群化涉及的問題:
配置管理變得復(fù)雜,因此需要設(shè)置配置中心來解決該問題。
同一個用戶的請求會轉(zhuǎn)發(fā)至不同的 Web 服務(wù)器,從而導(dǎo)致 Session 丟失等問題。
同一個請求在分布式環(huán)境中需要不同服務(wù)來提供不同處理,從而需要分布式事務(wù)來確保數(shù)據(jù)的一致性。
分布式唯一 ID 問題。
另外針對不同部分系統(tǒng)中的一些特定問題又有其他的一些特殊業(yè)務(wù)需求:
IP統(tǒng)計
用戶登錄記錄統(tǒng)計
實(shí)時的排行榜
原子計數(shù)
最新評論
誠然,以上各種問題都有花樣繁多的解決方法,例如:
配置中心可以使用 Zookpeer、Redis 等實(shí)現(xiàn)。
Session 丟失可以使用 Session 同步、客戶端 token、Session 共享等解決,其中 Session 共享又可以細(xì)分不同實(shí)現(xiàn)方式。
面對層出不窮的概念,以及各種新興的技術(shù),我們往往會顯得力不從心,那么有沒有一個銀彈可以解決這些問題呢?
Redis 非銀彈卻無比接近
我這里為大家推薦的就是 Redis ,雖然它離真正意義的銀彈還是有些距離,但是他是為數(shù)不多的接近銀彈的解決方案:
Redis 使用 C 開發(fā),是一款內(nèi)存 K/V 數(shù)據(jù)庫,架構(gòu)設(shè)計極簡,性能卓著。
Redis 采用 單線程 多路復(fù)用的設(shè)計,避免了并發(fā)帶來的鎖性能損耗等問題。
Redis 安裝、測試、配置、運(yùn)維較其他產(chǎn)品更為容易。
Redis 是目前為止最受歡迎的 K/V 數(shù)據(jù)庫,支持持久化,value 支持多種數(shù)據(jù)結(jié)構(gòu)。
Redis 命令語法簡單,極易掌握。
Redis 提供了一種通用的協(xié)議,使得各種編程語言都能很方便的開發(fā)出與其交互的客戶端。
Redis 開放源碼,我們可以對其進(jìn)行二次開發(fā)來定制優(yōu)化。
Redis 目前有較好的社區(qū)維護(hù),版本迭代有所保障,新的功能也在有條不紊的添加完善。
Redis 有較好的主從復(fù)制、集群相關(guān)支持。
最新版本提供模塊化功能,可以方便的擴(kuò)展功能。
接下來我們就來說說怎么使用 Redis 解決之前提到的問題:
1.配置中心
Redis 本身就是內(nèi)存 K/V 數(shù)據(jù)庫,支持 哈希、集合、列表等五種數(shù)據(jù)結(jié)構(gòu),從而配置信息的存儲、讀取速度都能夠得到滿足,Redis 還提供訂閱/發(fā)布功能從而可以在配置發(fā)生改變時通知不同服務(wù)器來進(jìn)行更新相關(guān)配置。
2.分布式鎖
使用 Redis 的 SETNX 命令或者 SET 命令配合 NX 選項的方式以及過期時間等功能可以很方便的實(shí)現(xiàn)一個性能優(yōu)越的分布式鎖。
3.緩存
Redis 支持多種過期淘汰機(jī)制,本身性能的優(yōu)勢也使 Redis 在緩存方面得到廣泛使用。
4.Lua 腳本
Lua 是一種輕量小巧的腳本語言,用標(biāo)準(zhǔn)C語言編寫并開放源代碼。Redis 支持 Lua 腳本的運(yùn)行,從而可以擴(kuò)展 Redis 中的命令實(shí)現(xiàn)很多復(fù)雜功能。
Redis 支持使用 Lua 腳本來實(shí)現(xiàn)一些組合命令邏輯處理,從而可以使用 Redis 做為限流、分布式唯一 ID 相關(guān)技術(shù)的實(shí)現(xiàn)。
5.Redis 支持 BitMaps
位圖(bitmap)是一種非常常用的結(jié)構(gòu),在索引,數(shù)據(jù)壓縮等方面有廣泛應(yīng)用,能同時保證存儲空間和速度最優(yōu)化(而不必空間換時間)。
使用 Redis 的 BitMaps 做為用戶登錄記錄統(tǒng)計,不僅統(tǒng)計速度極快,而且內(nèi)存占用極低。
6.Redis 支持 HyperLogLog 算法
Redis HyperLogLog是一種使用隨機(jī)化的算法,以少量內(nèi)存提供集合中唯一元素數(shù)量的近似值。
HyperLogLog 可以接受多個元素作為輸入,并給出輸入元素的基數(shù)估算值:
HyperLogLog 的優(yōu)點(diǎn)是,即使輸入元素的數(shù)量或者體積非常非常大,計算基數(shù)所需的空間總是固定的、并且是很小的。
在 Redis 里面,每個 HyperLogLog 鍵只需要花費(fèi) 12 KB 內(nèi)存,就可以計算接近 2^64 個不同元素的基數(shù)。這和計算基數(shù)時,元素越多耗費(fèi)內(nèi)存就越多的集合形成鮮明對比。使用 HyperLogLog 算法,我們可以輕而易舉的實(shí)現(xiàn) IP 統(tǒng)計等對數(shù)據(jù)容許些許誤差的統(tǒng)計功能。
-
基數(shù):集合中不同元素的數(shù)量。比如 {‘a(chǎn)pple’, ‘banana’, ‘cherry’, ‘banana’, ‘a(chǎn)pple’} 的基數(shù)就是3。
-
估算值:算法給出的基數(shù)并不是精確的,可能會比實(shí)際稍微多一些或者稍微少一些,但會控制在合理的范圍之內(nèi)。
7.Redis 支持 Geo 功能
我們可以使用基于 Redis 來實(shí)現(xiàn)地理位置相關(guān)管理,附近的人、兩地理位置間距離計算等功能變得極為容易實(shí)現(xiàn)。
8.簡單消息隊列
Redis 列表 + 發(fā)布/訂閱功能可以很方便的實(shí)現(xiàn)一個簡單的消息隊列,將消息存入 Redis 列表中,通過 發(fā)布/訂閱功能通知指定成員,成員獲取到通知后可以根據(jù)通知內(nèi)容進(jìn)行對應(yīng)處理。
9.全文檢索
Redis 官方團(tuán)隊開發(fā)了 RediSearch 模塊,可以實(shí)現(xiàn)使用 Redis 來做全文檢索的功能。
10.分布式唯一ID
Redis 的設(shè)計使其可以避免并發(fā)的多種問題,使其命令都是原子執(zhí)行,這些特性都天生匹配分布式唯一ID生成器的要求。
而且通過與 Lua 腳本的結(jié)合使用更是能生成復(fù)雜的有某些規(guī)律的唯一ID。
部分代碼實(shí)現(xiàn)
下面我們以 Java代碼作為演示(編程語言實(shí)現(xiàn)方式原理類似只是具體實(shí)現(xiàn)方式有些許差別而已)講解幾個功能的實(shí)現(xiàn):
Session 共享
原理:將不同 Web 服務(wù)器的 Session 信息統(tǒng)一存儲在 Redis 中,并且獲取 Session 也是從 Redis 中獲取
實(shí)現(xiàn)方法:
方法一:基于 Tomcat 實(shí)現(xiàn) Sessioin 共享:
Tomcat 配置步驟(相關(guān)代碼資源可以從https://gitee.com/coderknock/Tomcat-Redis-Session-Manager-Demo 獲取):
將 commons-pool2-2.4.2.jar、jedis-2.9.0.jar、commons-pool2-2.4.2.jar 三個 jar 包放到 Tomcat 下的 lib 目錄下(注意:不是項目的 lib 目錄)。
修改 Tomcat conf 下 context.xml:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
方法二:基于 Fileter 、 自行實(shí)現(xiàn) HttpServletRequestWrapper 、 HttpSession :
關(guān)鍵代碼:
HttpSessionWrapper.java
javaimport com.alibaba.fastjson.JSON;import com.alibaba.fastjson.JSONException;import com.coderknock.jedis.executor.JedisExecutor;import com.coderknock.pojo.User;import org.apache.commons.lang3.StringUtils;import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;import javax.servlet.ServletContext;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.HttpSession;import javax.servlet.http.HttpSessionContext;import java.util.Enumeration;/*** <p></p>** @author 三產(chǎn)* @version 1.0* @date 2017-08-26* @QQGroup 213732117* @website http://www.coderknock.com* @copyright Copyright 2017 拿客 coderknock.com All rights reserved.* @since JDK 1.8*/public class HttpSessionWrapper implements HttpSession {protected final Logger logger = LogManager.getLogger(HttpSessionWrapper.class);private String sid = "";private HttpServletRequest request;private HttpServletResponse response;private final long creationTime = System.currentTimeMillis();private final long lastAccessedTime = System.currentTimeMillis();//過期時間單位秒private int expire_time = 60;public HttpSessionWrapper() {}public HttpSessionWrapper(String sid, HttpServletRequest request,HttpServletResponse response) {this.sid = sid;this.request = request;this.response = response;}public Object getAttribute(String name) {logger.info(getClass() + "getAttribute(),name:" + name);try {Object obj = JedisExecutor.execute(jedis -> {String jsonStr = jedis.get(sid + ":" + name);if (jsonStr != null || StringUtils.isNotEmpty(jsonStr)) {jedis.expire(sid + ":" + name, expire_time);// 重置過期時間}return jsonStr;});return obj;} catch (JSONException je) {logger.error(je);} catch (Exception e) {logger.error(e.getMessage());}return null;}public void setAttribute(String name, Object value) {logger.info(getClass() + "setAttribute(),name:" + name);try {JedisExecutor.executeNR(jedis -> {if (value instanceof String) {String value_ = (String) value;jedis.set(sid + ":" + name, value_);//普通字符串對象} else {jedis.set(sid + ":" + name, JSON.toJSONString(value));//序列化對象}jedis.expire(sid + ":" + name, expire_time);// 重置過期時間});} catch (Exception e) {logger.error(e);}}public void removeAttribute(String name) {logger.info(getClass() + "removeAttribute(),name:" + name);if (StringUtils.isNotEmpty(name)) {try {JedisExecutor.executeNR(jedis -> {jedis.del(sid + ":" + name);});} catch (Exception e) {logger.error(e);}}}//...... 省略部分代碼}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
SessionFilter.java
javaimport com.coderknock.wrapper.DefinedHttpServletRequestWrapper;import org.apache.commons.lang3.StringUtils;import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;import javax.servlet.*;import javax.servlet.http.Cookie;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.util.UUID;/*** <p></p>** @author 三產(chǎn)* @version 1.0* @date 2017-08-26* @QQGroup 213732117* @website http://www.coderknock.com* @copyright Copyright 2017 拿客 coderknock.com All rights reserved.* @since JDK 1.8*/public class SessionFilter implements Filter {protected final Logger logger = LogManager.getLogger(getClass());private static final String host = "host";private static final String port = "port";private static final String seconds = "seconds";public void init(FilterConfig filterConfig) throws ServletException {logger.debug("init filterConfig info");}public void doFilter(ServletRequest request, ServletResponse response,FilterChain chain) throws IOException, ServletException {//從cookie中獲取sessionId,如果此次請求沒有sessionId,重寫為這次請求設(shè)置一個sessionIdHttpServletRequest httpRequest = (HttpServletRequest) request;HttpServletResponse httpResponse = (HttpServletResponse) response;String sid = null;if (httpRequest.getCookies() != null) {for (Cookie cookie : httpRequest.getCookies()) {if (cookie.getName().equals("JSESSIONID")) {sid = cookie.getValue();break;}}}if (StringUtils.isEmpty(sid)) {try {Cookie cookie = new Cookie("JSESSIONID", httpRequest.getLocalAddr() + ":" + request.getLocalPort() + ":" + UUID.randomUUID().toString().replaceAll("-", ""));httpResponse.addCookie(cookie);} catch (Exception e) {e.printStackTrace();}}logger.info("JSESSIONID:" + sid);chain.doFilter(new DefinedHttpServletRequestWrapper(sid, httpRequest, httpResponse), response);}public void destroy() {}}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
排行榜
原理:通過 Redis 有序集合可以很便捷的實(shí)現(xiàn)該功能
關(guān)鍵命令:
ZADD key [NX|XX][CH][INCR] score member [score member ...]: 初始化排行榜中成員及其分?jǐn)?shù)。
ZINCRBY key increment member:為某個成員增加分?jǐn)?shù),如果該成員不存在則會添加該成員并設(shè)定分?jǐn)?shù)為 increment 。
ZUNIONSTORE destination numkeys key [key ...][WEIGHTS weight [weight ...]][AGGREGATE SUM|MIN|MAX]: 可以合并多個排行榜,該操作會將幾個集合的并集存儲到 destination 中,其中各個集合相同成員分?jǐn)?shù)會疊加或者取最大、最小、平均值等(根據(jù) [AGGREGATE SUM|MIN|MAX] 參數(shù)決定,默認(rèn)是疊加),從而可以實(shí)現(xiàn)根據(jù)多個分排行榜來計算總榜排行的功能。
ZREVRANGE key start stop [WITHSCORES]:該命令就是最關(guān)鍵的獲取排行信息的命令,可以獲取從高到低的成員。
Redis 命令演示(“#”之后為說明):
# 1、存儲幾個排行榜成員數(shù)據(jù)(這里可以理解為把自己系統(tǒng)已有數(shù)據(jù)加載到 Redis 中)ZADD testTop 23 member1 25 member2# 2、增加某個人的分?jǐn)?shù)(這里的分?jǐn)?shù)就是排行的依據(jù)可以是浮點(diǎn)類型)ZINCRBY testTop 20 member1 # 此時 testTop 中 member1 的分?jǐn)?shù)就編程了 43ZINCRBY testTop -10 member2 # 此時 testTop 中 member2 的分?jǐn)?shù)就編程了 15ZINCRBY testTop 20 member3 # 此時向 testTop 中添加了 member3 成員,分?jǐn)?shù)為 20# 3、查詢排行榜前兩名,并且查詢出其分?jǐn)?shù)【W(wǎng)ITHSCORES 選項用于顯示分?jǐn)?shù),不帶該參數(shù)則只會查出成員名稱】ZREVRANGE testTop 0 1 WITHSCORES#結(jié)果:# 1) "member1"# 2) "43"# 3) "member3"# 4) "20"# 假設(shè)此時還有一個 排行榜ZADD testTop2 100 member2 200 member3 123 member4# 將 testTop testTop2 合成一個總榜 topZUNIONSTORE top 2 testTop testTop2# 查詢總榜所有成員排行情況ZREVRANGE top 0 -1 WITHSCORES1) "member3"2) "220"3) "member4"4) "123"5) "member2"6) "115"7) "member1"8) "43"- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
Java 相關(guān)實(shí)現(xiàn)代碼(模擬了 sf.gg 的名望榜)可以查看。
https://gitee.com/coderknock/Redis-Top-And-Around
/src/test/java/TopDemo.java 有具體測試用例
Geo 相關(guān)功能
Redis 的 Geo 功能提供了查詢兩個成員距離、某個成員附近范圍成員等功能可以用其實(shí)現(xiàn)一個簡單的附近的人
Java 相關(guān)實(shí)現(xiàn)代碼可以查看:
https://gitee.com/coderknock/Redis-Top-And-Around
/src/test/java/GeoDemo.java 有具體測試用例。
緩存
原理:將經(jīng)常會訪問的數(shù)據(jù)根據(jù)一定規(guī)則設(shè)置一個 Key 后存入 Redis,每次查詢時先查詢 Redis 中是否包含匹配數(shù)據(jù),如果緩存不存在再查詢數(shù)據(jù)庫。
注意點(diǎn):對于不存在的數(shù)據(jù)應(yīng)該存入一個自己設(shè)定的空值并設(shè)置過期時間,這樣可以避免緩存擊穿(由于數(shù)據(jù)不存在,所以設(shè)置 Key 對應(yīng)的值為 null(Java中的表示形式),因為 Redis 會移除值為 null 的 key 這樣會導(dǎo)致,每次查詢還是會訪問數(shù)據(jù)庫)。
Java 相關(guān)實(shí)現(xiàn)代碼可以查看:
https://gitee.com/coderknock/Redis-Cache
結(jié)束語
本文只是問了發(fā)散大家的思維,如對具體功能實(shí)現(xiàn)由興趣可以在之后的交流中共同探討。
由于個人的局限性,文中可能存在錯誤表述,大家可以在評論區(qū)中提出共同探討。
附錄
Redis環(huán)境搭建
在線體驗:
http://try.redis.io/
Windows版本:
https://github.com/MSOpenTech/redis
Linux安裝:
https://www.coderknock.com/blog/2016/05/28/LinuxRedis.html
Redis 配置
https://www.coderknock.com/blog/2017/06/14/Redis%20%E9%85%8D%E7%BD%AE.html
Redis 支持的五大數(shù)據(jù)結(jié)構(gòu)
Redis 基礎(chǔ)知識擴(kuò)展閱讀
Redis 基礎(chǔ)知識擴(kuò)展閱讀:https://segmentfault.com/bookmark/1230000010694933
Redis 發(fā)布訂閱圖解
來源:http://blog.csdn.net/csdnnews/article/details/78188740
總結(jié)
以上是生活随笔為你收集整理的微博遭鹿晗恋情暴击瘫痪,如何快速提高系统性能?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 编译安装php的openssl组件时遇到
- 下一篇: 涡阳县的雪枫路是不是以彭雪枫命名的?