搜搜公眾號"小易哥學呀學",回復"redis.pdf"
⑵ RedisTokenStore 源碼解析 以及內存泄漏問題
前端時間,正好在做公司許可權相關的架構問題,然後選擇了Spring OAuth2來作為公司許可權框架,先記錄下目前遇到原生問題吧,後續有時間再來整理這個框架的整體脈絡;
RedisTokenStore 主要是來做token持久化到redis的工具類
我們先來看下緩存到redis中有哪些key
ACCESS :用來存放 AccessToken 對象(登錄的token值,還有登錄過期時間,token刷新值)
AUTH_TO_ACCESS :緩存的也是AccessToken 對象,是可以根據用戶名和client_id來查找當前用戶的AccessToken
AUTH :用來存放用戶信息(OAuth2Authentication),有許可權信息,用戶信息等
ACCESS_TO_REFRESH :可以根據該值,通過AccessToken找到refreshToken
REFRESH :用來存放refreshToken
REFRESH_TO_ACCESS :根據refreshToken來找到AccessToken
CLIENT_ID_TO_ACCESS :存放當前client_id有多少AccessToken
UNAME_TO_ACCESS :當沒有做單點登錄的話,可以使用該key,根據用戶名查找當前用戶有多少AccessToken可以使用
根據client_id和用戶名,來獲取當前用戶的accessToken,如果緩存中的OAuth2Authentication已經過期,或者雷勇有變化,則會重新更新緩存;
緩存OAuth2AccessToken 和 OAuth2Authentication 對象,該方法主要在登錄時調用,會把上面說的所有key值都緩存起來;
再看下,移除accessToken時
目前主要是看這幾個方法,其他方法也挺簡單的,主要是一些redis緩存操作的;
client_id_to_access 和 uname_to_access 使用的是set集合,眾所周知redis的set集合的過期時間是按照整個key來設置的;
每次登陸時,會先根據client_id和用戶名去緩存中查找是否有可使用的AccessToken,如果有則返回緩存中的值,沒有則生成新的;
所以每次登陸都會往這兩個集合中放入新的accessToken,如果當某個用戶在AccessToken有效期內沒有操作,則當前用戶的登陸信息會被動下線,access 和 auth 中緩存的值都會過期,再次登陸時就查找不到了;
但是如果當前平台用戶量不小,那麼一直都會有人操作,client_id_to_access 這個集合就會一直續期,那麼過期了的accessToken就會一直存在該集合中,且不會減少,造成內存泄漏;
1.自己實現TokenStore,修改client_id_to_access 數據結構
2.寫個定時任務,定期掃描client_id_to_access ,清除掉已經過期的token
3.如果不需要知道當前有哪些用戶登錄,或者該功能已經用了其他方式實現的,可以直接去掉這兩個redis key
⑶ MySQL與Redis資料庫連接池介紹(圖示+源碼+代碼演示)
資料庫連接池(Connection pooling)是程序啟動時建立足夠的資料庫連接,並將這些連接組成一個連接池,由程序動態地對池中的連接進行申請,使用,釋放。
簡單的說:創建資料庫連接是一個很耗時的操作,也容易對資料庫造成安全隱患。所以,在程序初始化的時候,集中創建多個資料庫連接,並把他們集中管理,供程序使用,可以保證較快的資料庫讀寫速度,還更加安全可靠。
不使用資料庫連接池
如果不使用資料庫連接池,對於每一次SQL操作,都要走一遍下面完整的流程:
1.TCP建立連接的三次握手(客戶端與 MySQL伺服器的連接基於TCP協議)
2.MySQL認證的三次我收
3.真正的SQL執行
4.MySQL的關閉
5.TCP的四次握手關閉
可以看出來,為了執行一條SQL,需要進行大量的初始化與關閉操作
使用資料庫連接池
如果使用資料庫連接池,那麼會 事先申請(初始化)好 相關的資料庫連接,然後在之後的SQL操作中會復用這些資料庫連接,操作結束之後資料庫也不會斷開連接,而是將資料庫對象放回到資料庫連接池中
資源重用:由於資料庫連接得到重用,避免了頻繁的創建、釋放連接引起的性能開銷,在減少系統消耗的基礎上,另一方面也增進了系統運行環境的平穩性(減少內存碎片以及資料庫臨時進程/線程的數量)。
更快的系統響應速度:資料庫連接池在初始化過程中,往往已經創建了若干資料庫連接置於池中備用。 此時連接的初始化工作均已完成。對於業務請求處理而言,直接利用現有可用連接,避免了從資料庫連接初始化和釋放過程的開銷,從而縮減了系統整體響應時間。
統一的連接管理,避免資料庫連接泄露:在較為完備的資料庫連接池實現中,可根據預先的連接佔用超時設定,強制收回被佔用連接。從而避免了常規資料庫連接操作中可能出現的資源泄露。
如果說你的伺服器CPU是4核i7的,連接池大小應該為((4*2)+1)=9
相關視頻推薦
90分鍾搞懂資料庫連接池技術|linux後台開發
《tcp/ip詳解卷一》: 150行代碼拉開協議棧實現的篇章
學習地址:C/C++Linux伺服器開發/後台架構師【零聲教育】-學習視頻教程-騰訊課堂
需要C/C++ Linux伺服器架構師學習資料加qun 812855908 獲取(資料包括 C/C++,Linux,golang技術,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協程,DPDK,ffmpeg 等),免費分享
源碼下載
下載方式:https://github.com/dongyusheng/csdn-code/tree/master/db_pool(Github中下載)
db_pool目錄下有兩個目錄,mysql_pool目錄為MySQL連接池代碼,redis_pool為redis連接池代碼
下面介紹mysql_pool
CDBConn解析
概念: 代表一個數據連接對象實例
相關成員:
m_pDBPool:該資料庫連接對象所屬的資料庫連接池
構造函數: 綁定自己所屬於哪個資料庫連接池
Init()函數: 創建資料庫連接句柄
CDBPool解析
概念:代表一個資料庫連接池
相關成員:
Init()函數:常見指定數量的資料庫實例句柄,然後添加到m_free_list中,供後面使用
GetDBConn()函數: 用於從空閑隊列中返回可以使用的資料庫連接句柄
RelDBConn()函數: 程序使用完該資料庫句柄之後,將句柄放回到空閑隊列中
測試之前,將代碼中的資料庫地址、埠、賬號密碼等改為自己的(代碼中有好幾處)
進入MySQL, 創建mysql_pool_test資料庫
進入到mysql_pool目錄下, 創建一個build目錄並進入 :
之後就會在目錄下生成如下的可執行文件
輸入如下兩條命令進行測試: 可以看到不使用資料庫連接池,整個操作耗時4秒左右;使用連接池之後,整個操作耗時2秒左右,提升了一倍
源碼下載
下面介紹redis_pool
測試
進入到redis_pool目錄下, 創建一個build目錄並進入 :
然後輸入如下的命令進行編譯
之後就會在目錄下生成如下的可執行文件
輸入如下的命令進行測試: 可以看到不使用資料庫連接池,整個操作耗時182ms;使用連接池之後,整個操作耗時21ms,提升了很多
進入redis,可以看到我們新建的key:
⑷ redis源碼解讀:單線程的redis是如何實現高速緩存的
redis可能是最近幾年最火的緩存資料庫方案了,在各個高並發領域都有應用。
這篇文章,我們將從源代碼的角度來分析一下,為何如此一個高性能,高應用的緩存,會是單線程的方案,當然一個方案的高性能,高並發是多方面的綜合因素,其它的因素我們將在後續解讀。後續分析主要以LINUX操作系統為基礎,這也是redis應用最廣的平台。
單線程最大的受限是什麼?就是CPU,現在伺服器一般已經是多CPU,而單線程只能使用到其中的一個核。
redis作為一個網路內存緩存資料庫,在實現高性能時,主要有4個點。
1.網路高並發,高流量的數據處理。
一個非同步,高效,且對CPU要求不高的網路模型,這個模型主要是由OS來提供的,目前在LINUX最主流使用的是EPOLL,這個網上介紹很多,主要是基於事件驅動的一個非同步模型。
2.程序內部的合理構架,調用邏輯,內存管理。
redis在採用純C實現時,整體調用邏輯很短,但在內存方面,適當的合並了一些對象和對齊,比如sds等,在底層使用了內存池,在不同情況下使用的不太一樣。
但整體處理上沒有NGINX的內池設計巧妙,當然二者不太一樣,NGINX是基於請求釋放的邏輯來設計的,因此針對請求,可以一次申請大塊,分量使用,再最後統一釋放。
3.數據復制的代價,不管是讀取數據或是寫入數據,一般都是需要有數據復制的過程。
數據復制其實就是一次內存,真正的代價是在於存在大VALUE,當value值長度超過16KB時,性能會開始下降。因為單線程的原因,如果存在一個超大VALUE,比如20MB,則會因為這個請求卡住整個線程,導致後續的請求進不來,雖然後面的請求是能快速處理的小請求。
4.redis中數據結構中演算法的代價,有些結構在大數據量時,代價是很高的。
很多時間,大家忽略了演算法的運算代碼,因為像memcached等這類是完全的KV緩存,不存在什麼演算法,除了一個KEY的查找定位HASH演算法。
而redis不一樣,提供了不少高階的數據對象,這些對象具有上層的一些演算法能力,而這些能力是需要比如GEO模塊。
⑸ [redis 源碼走讀] sentinel 哨兵 - 節點發現流程
承接上一章 《 [redis 源碼走讀] sentinel 哨兵 - 原理 》,本章通過 strace 命令從底層抓取 sentinel 工作日誌,熟悉節點通信流程,閱讀相關源碼。
節點之間通過 TCP 建立聯系,下圖展示了 sentinel A 節點與其它節點的關系。
通過 strace 命令查看 socket 的發送和接收數據日誌內容,我們基本可以掌握 sentinel/master/slave 這三個角色是怎麼聯系起來的。
這樣 sentinel 只需要配置 master 的信息,通過 INFO 命令和訂閱頻道 __sentinel__:hello 就能將集群中所有角色的節點緊密聯系在一起。
根據 strace 日誌參考上述對應連接關系圖。
通過上述分析,我們基本了解了節點之間的通信流程時序,下面來分析一下源碼。
sentinel 進程對 sentinel / master / slave 三個角色用數據結構 sentinelRedisInstance 進行管理。
sentinel 進程啟動,載入配置,創建對應節點的管理實例 sentinelRedisInstance 。
定時器定期對其它節點進行監控管理。sentinel 利用 hiredis 作為 redis client,鏈接其它節點進行相互通信。
sentinel 非同步重連其它節點。
sentinel 定期發送命令:PING / INFO / PUBLISH。每種命令發送的時間間隔不一樣;不同場景下,同一個命令發送時間間隔可能會改變。
命令發送對象 :
sentinel 通過 master / slave 的 INFO 回復,主要下面幾件事:
如果文章不錯,給個點贊唄 ~ 謝謝。👍
⑹ window 下安裝redis後代碼怎麼使用
1下載安裝文件,選擇穩定版本 2解壓後找到bin目錄下的release下的redis-2.8.17 3點擊安裝exe文件,進行安裝。選擇好路徑,一直到安裝結束即可。 4點擊Service查看Redis服務是否正確的安裝。Windows--》Service.msc。默認的埠為6379。服務已啟動。 5使用客戶端工具進行連接,出現如下畫面即成功。 6使用CMD工具,安裝另一個Redis實例服務,埠為6369.需要提前建好6369埠使用的conf文件 如:C:\Users\Gray>E:\redis-2.8.17\redis-server.exe --service-installE:\redis-2.8.17\redis6369.conf --service-name RedisServer6369 --port 6369 試驗了幾次都沒有提示成功的信息,但是查看服務成功了,而且用客戶端連接也成功了。 7查看6369埠的redis服務 8使用客戶端連接6369 redis服務,出現如下界面表示成功 9至此,大功告成。
⑺ Redis源碼分析之事件循環
本篇我們來講Redis的事件循環,Redis的事件循環會根據系統選擇evport、epoll、kqueue或select來進行IO多路復用,我們這里只分析epoll。
首先我們來看一下Redis的IO多路復用對事件循環(aeEventLoop)提供的介面。
以epoll(ae_epoll.c)為例,先來看一下Redis的IO多路復用的使用過程:
首先需要創建,即調用aeApiCreate:
aeApiState結構體有兩個成員,events和epfd。events用於存儲就緒的epoll事件,epfd存儲epoll的文件描述符。aeApiCreate的主要邏輯是為aeApiState分配存儲空間,調用epoll_create系統調用創建epoll並獲取描述符,最後將aeApiState賦值給aeEventLoop的apidata。
然後在有新的文件描述符(比如接受了一個新連接)需要加入到epoll中時,調用aeApiAddEvent:
參數fd是需要監視的文件描述符,mask標明是需要監視可讀還是可寫事件。aeApiAddEvent的主要邏輯是調用系統調用epoll_ctl注冊或修改添加事件的監聽類型到epoll。
然後在有文件描述符失效或者需要修改監聽類型時,調用aeApiDelEvent:
參數fd是需要刪除的文件描述符,mask標明是需要刪除可讀還是可寫事件。aeApiDelEvent主要邏輯是調用系統調用epoll_ctl刪除或修改刪除事件的監聽類型到epoll。
然後需要檢查是否有就緒的事件,調用aeApiPoll:
tvp是等待時間,一般而言,這個值是0(不是NULL)代表沒有就緒事件立即返回。主要邏輯是調用系統調用epoll_wait拿到就緒事件保存到events中,然後將events中的就緒事件復制到事件循環aeEventLoop的fired中,最後返回就緒事件的數量。
我們來分析一下Redis的事件循環(ae.c)。
先看主要介面:
創建過程:
初始化aeEventLoop和aeApiState並返回aeEventLoop。
注冊文件事件:
存儲到events中並調用aeApiAddEvent注冊到epoll中。
注冊定時事件:
創建aeTimeEvent並將其插入到定時事件鏈表的頭部。
主循環:
不停的調用aeProcessEvents拉取並處理事件。
接下來看aeProcessEvents的邏輯:
再看一下定時事件的觸發,也就是processTimeEvents的邏輯:
遍歷注冊的定時事件,找出到期的事件並調用處理函數,如果處理函數返回了下次執行的時間,則更新下次觸發的時間,否則刪除該事件。
⑻ Redis實現優先順序消息隊列及源碼
上午寫了一篇RabbitMQ做優先順序隊列的文章,但是RabbitMQ這種專業的消息隊列,面對不大的業務是有些殺雞焉用牛刀的感覺,而且使用RabbitMQ需要的成本相對較高。
所以我中午抽空寫了這個Redis實現優先順序消息隊列的例子。
相比RabbitMQ,更加簡潔,更易於理解。
源碼地址: https://github.com/SkylerSkr/RedisPriorityMQ
謝謝大家支持!希望多提意見!
⑼ redis之管道應用場景及源碼分析
我們都知道,redis的通信是建立在tcp基礎上的,也就是說每一次命令(get、set)都需要經過tcp三次握手,而且redis一般都是部署在區域網內,網路開銷非常小,針對頻次較低的操作,網路開銷都是可以忽略的。
在redis通信基礎中 我已經講到了。每一次操作redis的時候我們都需要和服務端建立連接,針對量小的情況下網路延遲都是可以忽略的,但是針對大批量的業務,就會產生雪崩效應。假如一次操作耗時2ms,理論上100萬次操作就會有2ms*100萬ms延遲,中間加上伺服器處理開銷,耗時可能更多.對應客戶端來講,這種長時間的耗時是不能接受的。所以為了解決這個問題,redis的管道pipeline就派上用場了。 恰好公司的對賬業務使用了redis的sdiff功能,數據量比較大,剛開始沒有pipeline導致延遲非常嚴重。後來wireshark抓包分析原因確實發現不停的建立tcp連接(發送數據,接收數據)。使用pipeline後性能大幅度提升。
可想而知,使用pipeline的性能要比不使用管道快很多倍。
本文就先到這里了。。。
⑽ windows環境下Redis+Spring緩存實例
一、Redis了解
1.1、Redis介紹:
redis是一個key-value存儲系統。和Memcached類似,它支持存儲的value類型相對更多,包括string(字元串)、list(鏈表)、set(集合)、zset(sorted set –有序集合)和hash(哈希類型)。這些數據類型都支持push/pop、add/remove及取交集並集和差集及更豐富的操作,而且這些操作都是原子性的。在此基礎上,redis支持各種不同方式的排序。與memcached一樣,為了保證效率,數據都是緩存在內存中。區別的是redis會周期性的把更新的數據寫入磁碟或者把修改操作寫入追加的記錄文件,並且在此基礎上實現了master-slave(主從)同步。
Redis資料庫完全在內存中,使用磁碟僅用於持久性。相比許多鍵值數據存儲,Redis擁有一套較為豐富的數據類型。Redis可以將數據復制到任意數量的從伺服器。
1.2、Redis優點:
(1)異常快速:Redis的速度非常快,每秒能執行約11萬集合,每秒約81000+條記錄。
(2)支持豐富的數據類型:Redis支持最大多數開發人員已經知道像列表,集合,有序集合,散列數據類型。這使得它非常容易解決各種各樣的問題,因為我們知道哪些問題是可以處理通過它的數據類型更好。
(3)操作都是原子性:所有Redis操作是原子的,這保證了如果兩個客戶端同時訪問的Redis伺服器將獲得更新後的值。
(4)多功能實用工具:Redis是一個多實用的工具,可以在多個用例如緩存,消息,隊列使用(Redis原生支持發布/訂閱),任何短暫的數據,應用程序,如Web應用程序會話,網頁命中計數等。
1.3、Redis缺點:
(1)單線程
(2)耗內存
二、64位windows下Redis安裝
Redis官方是不支持windows的,但是Microsoft Open Tech group 在 GitHub上開發了一個Win64的版本,下載地址:https://github.com/MSOpenTech/redis/releases。注意只支持64位哈。
小寶鴿是下載了Redis-x64-3.0.500.msi進行安裝。安裝過程中全部採取默認即可。
安裝完成之後可能已經幫你開啟了Redis對應的服務,博主的就是如此。查看資源管理如下,說明已經開啟:
已經開啟了對應服務的,我們讓它保持,下面例子需要用到。如果沒有開啟的.,我們命令開啟,進入Redis的安裝目錄(博主的是C:Program FilesRedis),然後如下命令開啟:
redis-server redis.windows.conf
OK,下面我們進行實例。
三、詳細實例
本工程採用的環境:Eclipse + maven + spring + junit
3.1、添加相關依賴(spring+junit+redis依賴),pom.xml:
4.0.0 com.luo redis_project 0.0.1-SNAPSHOT 3.2.8.RELEASE 4.10 org.springframework spring-core ${spring.version} org.springframework spring-webmvc ${spring.version} org.springframework spring-context ${spring.version} org.springframework spring-context-support ${spring.version} org.springframework spring-aop ${spring.version} org.springframework spring-aspects ${spring.version} org.springframework spring-tx ${spring.version} org.springframework spring-jdbc ${spring.version} org.springframework spring-web ${spring.version} junit junit ${junit.version} test org.springframework spring-test ${spring.version} test org.springframework.data spring-data-redis 1.6.1.RELEASE redis.clients jedis 2.7.3
3.2、spring配置文件application.xml:
<"1.0" encoding="UTF-8"> classpath:properties/*.properties
3.3、Redis配置參數,redis.properties:
#redis中心#綁定的主機地址redis.host=127.0.0.1#指定Redis監聽埠,默認埠為6379redis.port=6379#授權密碼(本例子沒有使用)redis.password=123456 #最大空閑數:空閑鏈接數大於maxIdle時,將進行回收redis.maxIdle=100 #最大連接數:能夠同時建立的「最大鏈接個數」redis.maxActive=300 #最大等待時間:單位msredis.maxWait=1000 #使用連接時,檢測連接是否成功 redis.testOnBorrow=true#當客戶端閑置多長時間後關閉連接,如果指定為0,表示關閉該功能redis.timeout=10000
3.4、添加介面及對應實現RedisTestService.java和RedisTestServiceImpl.java:
package com.luo.service; public interface RedisTestService { public String getTimestamp(String param);}
package com.luo.service.impl; import org.springframework.stereotype.Service;import com.luo.service.RedisTestService; @Servicepublic class RedisTestServiceImpl implements RedisTestService { public String getTimestamp(String param) { Long timestamp = System.currentTimeMillis(); return timestamp.toString(); } }
3.5、本例採用spring aop切面方式進行緩存,配置已在上面spring配置文件中,對應實現為MethodCacheInterceptor.java:
package com.luo.redis.cache; import java.io.Serializable;import java.util.concurrent.TimeUnit;import org.aopalliance.intercept.MethodInterceptor;import org.aopalliance.intercept.MethodInvocation;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.core.ValueOperations; public class MethodCacheInterceptor implements MethodInterceptor { private RedisTemplate redisTemplate; private Long defaultCacheExpireTime = 10l; // 緩存默認的過期時間,這里設置了10秒 public Object invoke(MethodInvocation invocation) throws Throwable { Object value = null; String targetName = invocation.getThis().getClass().getName(); String methodName = invocation.getMethod().getName(); Object[] arguments = invocation.getArguments(); String key = getCacheKey(targetName, methodName, arguments); try { // 判斷是否有緩存 if (exists(key)) { return getCache(key); } // 寫入緩存 value = invocation.proceed(); if (value != null) { final String tkey = key; final Object tvalue = value; new Thread(new Runnable() { public void run() { setCache(tkey, tvalue, defaultCacheExpireTime); } }).start(); } } catch (Exception e) { e.printStackTrace(); if (value == null) { return invocation.proceed(); } } return value; } /** * 創建緩存key * * @param targetName * @param methodName * @param arguments */ private String getCacheKey(String targetName, String methodName, Object[] arguments) { StringBuffer sbu = new StringBuffer(); sbu.append(targetName).append("_").append(methodName); if ((arguments != null) && (arguments.length != 0)) { for (int i = 0; i < arguments.length; i++) { sbu.append("_").append(arguments[i]); } } return sbu.toString(); } /** * 判斷緩存中是否有對應的value * * @param key * @return */ public boolean exists(final String key) { return redisTemplate.hasKey(key); } /** * 讀取緩存 * * @param key * @return */ public Object getCache(final String key) { Object result = null; ValueOperations operations = redisTemplate .opsForValue(); result = operations.get(key); return result; } /** * 寫入緩存 * * @param key * @param value * @return */ public boolean setCache(final String key, Object value, Long expireTime) { boolean result = false; try { ValueOperations operations = redisTemplate .opsForValue(); operations.set(key, value); redisTemplate.expire(key, expireTime, TimeUnit.SECONDS); result = true; } catch (Exception e) { e.printStackTrace(); } return result; } public void setRedisTemplate( RedisTemplate redisTemplate) { this.redisTemplate = redisTemplate; }}
3.6、單元測試相關類:
package com.luo.baseTest; import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; //指定bean注入的配置文件 @ContextConfiguration(locations = { "classpath:application.xml" }) //使用標準的JUnit @RunWith注釋來告訴JUnit使用Spring TestRunner @RunWith(SpringJUnit4ClassRunner.class) public class SpringTestCase extends { }
package com.luo.service; import org.junit.Test;import org.springframework.beans.factory.annotation.Autowired; import com.luo.baseTest.SpringTestCase; public class RedisTestServiceTest extends SpringTestCase { @Autowired private RedisTestService redisTestService; @Test public void getTimestampTest() throws InterruptedException{ System.out.println("第一次調用:" + redisTestService.getTimestamp("param")); Thread.sleep(2000); System.out.println("2秒之後調用:" + redisTestService.getTimestamp("param")); Thread.sleep(11000); System.out.println("再過11秒之後調用:" + redisTestService.getTimestamp("param")); } }
3.7、運行結果:
四、源碼下載:redis-project().rar
以上就是本文的全部內容,希望對大家的學習有所幫助。