Redis:快速提高系統(tǒng)性能的銀彈
來源:原創(chuàng) 時(shí)間:2017-11-20 瀏覽:0 次現(xiàn)代高并發(fā)雜亂體系面臨的應(yīng)戰(zhàn)
現(xiàn)代體系跟著功用的雜亂化,各式各樣需求層出不窮,面臨愈加雜亂話的事務(wù)體系、越來越巨大的用戶集體,以及用戶對(duì)體會(huì)的要求越來越高,功用就變得愈加重要。
拋開代碼邏輯、效勞器功用的相關(guān)問題外,進(jìn)步功用的辦法有以下幾種:
動(dòng)態(tài)別離
負(fù)載均衡
分布式
集群化
緩存
限流處理
數(shù)據(jù)壓縮
其他
我們來剖析一下負(fù)載均衡、分布式、集群化觸及的問題:
裝備辦理變得雜亂,因而需求設(shè)置裝備中心來處理該問題。
同一個(gè)用戶的懇求會(huì)轉(zhuǎn)發(fā)至不同的 Web 效勞器,然后導(dǎo)致 Session 丟掉等問題。
同一個(gè)懇求在分布式環(huán)境中需求不同效勞來供給不同處理,然后需求分布式事務(wù)來確保數(shù)據(jù)的一致性。
分布式僅有 ID 問題。
別的針對(duì)不同部分體系中的一些特定問題又有其他的一些特別事務(wù)需求:
IP核算
用戶登錄記載核算
實(shí)時(shí)的排行榜
原子計(jì)數(shù)
最新談?wù)?/span>
固然,以上各種問題都有花樣繁多的處理辦法,例如:
裝備中心能夠運(yùn)用 Zookpeer、Redis 等完成。
Session 丟掉能夠運(yùn)用 Session 同步、客戶端 token、Session 同享等處理,其間 Session 同享又能夠細(xì)分不同完成辦法。
面臨層出不窮的概念,以及各種新式的技能,我們往往會(huì)顯得無能為力,那么有沒有一個(gè)銀彈能夠處理這些問題呢?
Redis 非銀彈卻無比挨近
我這兒為我們引薦的就是 Redis ,盡管它離真實(shí)含義的銀彈仍是有些間隔,可是他是為數(shù)不多的挨近銀彈的處理方案:
Redis 運(yùn)用 C 開發(fā),是一款內(nèi)存 K/V 數(shù)據(jù)庫,架構(gòu)規(guī)劃極簡(jiǎn),功用卓著。
Redis 選用 單線程 多路復(fù)用的規(guī)劃,防止了并發(fā)帶來的鎖功用損耗等問題。
Redis 裝置、測(cè)驗(yàn)、裝備、運(yùn)維較其他產(chǎn)品更為簡(jiǎn)略。
Redis 是現(xiàn)在為止最受歡迎的 K/V 數(shù)據(jù)庫,支撐耐久化,value 支撐多種數(shù)據(jù)結(jié)構(gòu)。
Redis 指令語法簡(jiǎn)略,極易把握。
Redis 供給了一種通用的協(xié)議,使得各種編程言語都能很便利的開宣布與其交互的客戶端。
Redis 開放源碼,我們能夠?qū)ζ溥M(jìn)行二次開發(fā)來定制優(yōu)化。
Redis 現(xiàn)在有較好的社區(qū)保護(hù),版別迭代有所確保,新的功用也在有條有理的添加完善。
Redis 有較好的主從復(fù)制、集群相關(guān)支撐。
最新版別供給模塊化功用,能夠便利的擴(kuò)展功用。
接下來我們就來說說怎樣運(yùn)用 Redis 處理之前說到的問題:
裝備中心
Redis 自身就是內(nèi)存 K/V 數(shù)據(jù)庫,支撐 哈希、調(diào)集、列表等五種數(shù)據(jù)結(jié)構(gòu),然后裝備信息的存儲(chǔ)、讀取速度都能夠得到滿意,Redis 還供給訂閱/發(fā)布功用然后能夠在裝備發(fā)作改動(dòng)時(shí)告訴不同效勞器來進(jìn)行更新相關(guān)裝備。
分布式鎖
運(yùn)用 Redis 的 SETNX 指令或許 SET 指令合作 NX 選項(xiàng)的辦法以及過期時(shí)刻等功用能夠很便利的完成一個(gè)功用優(yōu)越的分布式鎖。
緩存
Redis 支撐多種過期篩選機(jī)制,自身功用的優(yōu)勢(shì)也使 Redis 在緩存方面得到廣泛運(yùn)用。
Lua 腳本
Lua 是一種輕量細(xì)巧的腳本言語,用規(guī)范C言語編寫并開放源代碼。Redis 支撐 Lua 腳本的運(yùn)轉(zhuǎn),然后能夠擴(kuò)展 Redis 中的指令完成許多雜亂功用。
Redis 支撐運(yùn)用 Lua 腳正本完成一些組合指令邏輯處理,然后能夠運(yùn)用 Redis 做為限流、分布式僅有 ID 相關(guān)技能的完成。
Redis 支撐 BitMaps
位圖(bitmap)是一種十分常用的結(jié)構(gòu),在索引,數(shù)據(jù)壓縮等方面有廣泛應(yīng)用,能一起確保存儲(chǔ)空間和速度最優(yōu)化(而不用空間換時(shí)刻)。
運(yùn)用 Redis 的 BitMaps 做為用戶登錄記載核算,不只核算速度極快,而且內(nèi)存占用極低。
Redis 支撐 HyperLogLog 算法
Redis HyperLogLog是一種運(yùn)用隨機(jī)化的算法,以少數(shù)內(nèi)存供給調(diào)集中僅有元素?cái)?shù)量的近似值。
HyperLogLog 能夠承受多個(gè)元素作為輸入,并給出輸入元素的基數(shù)預(yù)算值:
HyperLogLog 的長(zhǎng)處是,即便輸入元素的數(shù)量或許體積十分十分大,核算基數(shù)所需的空間總是固定的、而且是很小的。
在 Redis 里邊,每個(gè) HyperLogLog 鍵只需求花費(fèi) 12 KB 內(nèi)存,就能夠核算挨近 2^64 個(gè)不同元素的基數(shù)。這和核算基數(shù)時(shí),元素越多消耗內(nèi)存就越多的調(diào)集構(gòu)成鮮明對(duì)比。運(yùn)用 HyperLogLog 算法,我們能夠垂手可得的完成 IP 核算等對(duì)數(shù)據(jù)容許少許差錯(cuò)的核算功用。
基數(shù):調(diào)集中不同元素的數(shù)量。比方 {‘apple’, ‘banana’, ‘cherry’, ‘banana’, ‘apple’} 的基數(shù)就是3。
預(yù)算值:算法給出的基數(shù)并不是準(zhǔn)確的,可能會(huì)比實(shí)踐略微多一些或許略微少一些,但會(huì)控制在合理的規(guī)模之內(nèi)。
Redis 支撐 Geo 功用
我們能夠運(yùn)用根據(jù) Redis 來完成地理位置相關(guān)辦理,鄰近的人、兩地理位置間間隔核算等功用變得極為簡(jiǎn)略完成。
簡(jiǎn)略音訊行列
Redis 列表 + 發(fā)布/訂閱功用能夠很便利的完成一個(gè)簡(jiǎn)略的音訊行列,將音訊存入 Redis 列表中,經(jīng)過 發(fā)布/訂閱功用告訴指定成員,成員獲取到告訴后能夠根據(jù)告訴內(nèi)容進(jìn)行對(duì)應(yīng)處理。
全文檢索
Redis 官方團(tuán)隊(duì)開發(fā)了 RediSearch 模塊,能夠完成運(yùn)用 Redis 來做全文檢索的功用。
分布式僅有ID
Redis 的規(guī)劃使其能夠防止并發(fā)的多種問題,使其指令都是原子履行,這些特性都天然生成匹配分布式僅有ID生成器的要求。
而且經(jīng)過與 Lua 腳本的結(jié)合運(yùn)用更是能生成雜亂的有某些規(guī)則的僅有ID。
部分代碼完成
下面我們以 Java代碼作為演示(編程言語完成辦法原理相似僅僅詳細(xì)完成辦法有少許不同罷了)解說幾個(gè)功用的完成:
Session 同享
原理:將不同 Web 效勞器的 Session 信息一致存儲(chǔ)在 Redis 中,而且獲取 Session 也是從 Redis 中獲取
完成辦法:
辦法一:根據(jù) Tomcat 完成 Sessioin 同享:
Tomcat 裝備過程(相關(guān)代碼資源能夠從https://gitee.com/coderknock/Tomcat-Redis-Session-Manager-Demo 獲?。?/span>
將 commons-pool2-2.4.2.jar、jedis-2.9.0.jar、commons-pool2-2.4.2.jar 三個(gè) jar 包放到 Tomcat 下的 lib 目錄下(留意:不是項(xiàng)目的 lib 目錄)。
修正 Tomcat conf 下 context.xml:
XML
......
port="6379"
database="0"
maxInactiveInterval="60"
password="admin123" />
......
辦法二:根據(jù) Fileter 、 自行完成 HttpServletRequestWrapper 、 HttpSession :
要害代碼:
HttpSessionWrapper.java
java
import 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;
/**
*
*
* @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();
//過期時(shí)刻單位秒
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);// 重置過期時(shí)刻
}
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_);//一般字符串目標(biāo)
} else {
jedis.set(sid + ":" + name, JSON.toJSONString(value));//序列化目標(biāo)
}
jedis.expire(sid + ":" + name, expire_time);// 重置過期時(shí)刻
});
} 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);
}
}
}
//...... 省掉部分代碼
}
SessionFilter.java
java
import 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;
/**
*
*
* @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è)置一個(gè)sessionId
HttpServletRequest 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() {
}
}
排行榜
原理:經(jīng)過 Redis 有序調(diào)集能夠很快捷的完成該功用
要害指令:
ZADD key [NX|XX][CH][INCR] score member [score member ...]: 初始化排行榜中成員及其分?jǐn)?shù)。
ZINCRBY key increment member:為某個(gè)成員添加分?jǐn)?shù),如果該成員不存在則會(huì)添加該成員并設(shè)定分?jǐn)?shù)為 increment 。
ZUNIONSTORE destination numkeys key [key ...][WEIGHTS weight [weight ...]][AGGREGATE SUM|MIN|MAX]: 能夠兼并多個(gè)排行榜,該操作會(huì)將幾個(gè)調(diào)集的并集存儲(chǔ)到 destination 中,其間各個(gè)調(diào)集相同成員分?jǐn)?shù)會(huì)疊加或許取最大、最小、平均值等(根據(jù) [AGGREGATE SUM|MIN|MAX] 參數(shù)決議,默許是疊加),然后能夠完成根據(jù)多個(gè)分排行榜來核算總榜排行的功用。
ZREVRANGE key start stop [WITHSCORES]:該指令就是最要害的獲取排行信息的指令,能夠獲取從高到低的成員。
Redis 指令演示(“#”之后為闡明):
# 1、存儲(chǔ)幾個(gè)排行榜成員數(shù)據(jù)(這兒能夠理解為把自己體系已有數(shù)據(jù)加載到 Redis 中)
ZADD testTop 23 member1 25 member2
# 2、添加某個(gè)人的分?jǐn)?shù)(這兒的分?jǐn)?shù)就是排行的根據(jù)能夠是浮點(diǎn)類型)
ZINCRBY testTop 20 member1 # 此刻 testTop 中 member1 的分?jǐn)?shù)就編程了 43
ZINCRBY testTop -10 member2 # 此刻 testTop 中 member2 的分?jǐn)?shù)就編程了 15
ZINCRBY testTop 20 member3 # 此刻向 testTop 中添加了 member3 成員,分?jǐn)?shù)為 20
# 3、查詢排行榜前兩名,而且查詢出其分?jǐn)?shù)【W(wǎng)ITHSCORES 選項(xiàng)用于顯現(xiàn)分?jǐn)?shù),不帶該參數(shù)則只會(huì)查出成員稱號(hào)】
ZREVRANGE testTop 0 1 WITHSCORES
#成果:
# 1) "member1"
# 2) "43"
# 3) "member3"
# 4) "20"
# 假定此刻還有一個(gè) 排行榜
ZADD testTop2 100 member2 200 member3 123 member4
# 將 testTop testTop2 組成一個(gè)總榜 top
ZUNIONSTORE top 2 testTop testTop2
# 查詢總榜一切成員排行狀況
ZREVRANGE top 0 -1 WITHSCORES
1) "member3"
2) "220"
3) "member4"
4) "123"
5) "member2"
6) "115"
7) "member1"
8) "43"