日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

Redis与Java - 实践

發布時間:2025/3/17 java 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Redis与Java - 实践 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Redis與Java - 實踐

標簽 : Java與NoSQL


Transaction

Redis事務(transaction)是一組命令的集合,同命令一樣也是Redis的最小執行單位, Redis保證一個事務內的命令執行不被其他命令影響.

`MULTI`SADD user:1:following 2SADD user:2:follower 1 `EXEC`

vs. RDBMS

事務操作MySQLRedis
開啟start transactionMULTI
語句DML普通命令
取消rollbackDISCARD
執行commitEXEC
  • MySQL的rollback與Redis的DISCARD有一定的區別.
    假設現在已經成功執行了事務內的前2條語句, 第3條語句出錯:
  • MySQLrollback后,前2條的語句影響消失.
  • Redis可以分為兩種情況:
    • 語法錯誤: 事務中斷, 所有語句均得不到執行;
    • 運行錯誤: (如語法正確,但適用數據類型不對: 像ZADD操作List), EXEC會執行前2條語句, 并跳過第3條語句.
      這樣的部分成功會導致數據不一致, 而這一點需要由開發人員負責, 比如提前規劃好緩存key的設計.

樂觀鎖與WATCH

悲觀鎖(Pessimistic Lock): 很悲觀,每次讀寫數據都認為別人會修改,所以每次讀數據都會上鎖,這樣如果別人也想讀寫這條數據就會阻塞, 直到加鎖的人把鎖釋放. 傳統的RDBMS中用到了很多這種鎖機制, 如行鎖、表鎖、讀鎖、寫鎖等.
樂觀鎖(Optimistic Lock): 顧名思義非常樂觀, 每次讀寫數據時候都認為別人不會修改,所以不再上鎖,但在更新數據時會判斷一下在此期間有沒有人更新了這條數據, 這個判斷過程可以使用版本號等機制實現, 而Redis默認就對樂觀鎖提供了支持 –WATCH命令.

WATCH命令可以監控一個/多個key, 一旦其中有一個被修改/刪除, 則之后的事務就不會執行,如用WATCH命令來模擬搶票場景:

SET ticket 1 # 現在假設只有一張票了 `WATCH` ticket # 監控票數變化 `MULTI`DECRBY username 400DECR ticket[DECR ticket] # 現在假設有另外一個用戶直接把這張票買走了 `EXEC`-> `(nil)` # 則這條事務執行就不會成功

小結

  • WATCH命令的作用只是當被監控的key值修改后阻止事務執行,并不能阻止其他Client修改. 所以一旦EXEC執行失敗, 可以重新執行整個方法或使用UNWATCH命令取消監控.

  • 樂觀鎖適用于讀多寫少情景,即沖突真的很少發生,這樣可以省去大量鎖的開銷. 但如果經常產生沖突,上層應用需要不斷的retry,反倒是降低了性能,所以這種情況悲觀鎖比較適用.


Expire & Cache

Redis可以使用EXPIRE命令設置key的過期時間, 到期后Redis會自動刪除它.

命令作用
EXPIRE key secondsSet a timeout on key.
TTL keyGet the time to live for a key
PERSIST keyRemove the expiration for a key

除了PERSIST命令之外,SET/GETSET為key賦值的同時也會清除key的過期時間.另外如果WATCH監控了一個擁有過期時間的key,key到期自動刪除并不會被WATCH認為該key被修改.

  • 緩存DB數據
    為了提高網站負載能力,常需要將一些訪問頻率較高但對CPU/IO消耗較大的操作結果緩存起來,并希望讓這些緩存過期自動刪除, 下面我們就使用Redis緩存DB數據, 場景介紹可以參考:Memcached - In Action:緩存DB查詢數據.
/*** @author jifang.* @since 2016/6/13 20:08.*/ public class RedisDAO {private static final int _1M = 60 * 1000;private static final DataSource dataSource;private static final Jedis redis;static {Properties properties = new Properties();try {properties.load(ClassLoader.getSystemResourceAsStream("db.properties"));} catch (IOException ignored) {}/** 初始化連接池 **/HikariConfig config = new HikariConfig();config.setDriverClassName(properties.getProperty("mysql.driver.class"));config.setJdbcUrl(properties.getProperty("mysql.url"));config.setUsername(properties.getProperty("mysql.user"));config.setPassword(properties.getProperty("mysql.password"));dataSource = new HikariDataSource(config);/** 初始化Redis **/redis = new Jedis(properties.getProperty("redis.host"), Integer.valueOf(properties.getProperty("redis.port")));}public List<Map<String, Object>> executeQuery(String sql) {List<Map<String, Object>> result;try {/** 首先請求Redis **/String key = sql.replace(' ', '-');String string = redis.get(key);// 如果key未命中, 再請求DBif (string == null || string.trim().isEmpty()) {ResultSet resultSet = dataSource.getConnection().createStatement().executeQuery(sql);/** 獲得列數/列名 **/ResultSetMetaData meta = resultSet.getMetaData();int columnCount = meta.getColumnCount();List<String> columnName = new ArrayList<>();for (int i = 1; i <= columnCount; ++i) {columnName.add(meta.getColumnName(i));}/** 填充實體 **/result = new ArrayList<>();while (resultSet.next()) {Map<String, Object> entity = new HashMap<>(columnCount);for (String name : columnName) {entity.put(name, resultSet.getObject(name));}result.add(entity);}/**寫入Redis**/String value = JSON.toJSONString(result);redis.set(key, value, "NX", "PX", _1M);} else {result = JSON.parseObject(string, List.class);}} catch (SQLException e) {throw new RuntimeException(e);}return result;}public static void main(String[] args) {RedisDAO dao = new RedisDAO();List<Map<String, Object>> execute = dao.executeQuery("select * from user");System.out.println(execute);} }

當服務器內存有限時,如果大量使用緩存而且過期時間較長會導致Redis占滿內存; 另一方面為了防止占用內存過大而設置過期時間過短, 則有可能導致緩存命中率過低而使系統整體性能下降.因此為緩存設計一個合理的過期時間是很糾結的, 在Redis中可以限制能夠使用的最大內存,并讓Redis按照一定規則的淘汰不再需要的key: 修改maxmemory參數,當超過限制會依據maxmemory-policy參數指定的策略來刪除不需要的key:

maxmemory-policy規則說明
volatile-lru只對設置了過期時間的key使用LRU算法刪除
allkey-lru使用LRU刪除一個key
volatile-random只對設置了過期時間的key隨機刪除一個key
allkey-random隨機刪除一個key
volatile-ttl刪除過期時間最近的一個key
noevication不刪除key, 只返回錯誤(默認)

Sort

Redis的SORT命令可以對List、Set、Sorted-Set類型排序, 并且可以完成與RDBMS 連接查詢 類似的任務:

SORT key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC|DESC] [ALPHA] [STORE destination] 參數描述
ALPHASORT默認會將所有元素轉換成雙精度浮點數比較,無法轉換則會提示錯誤,而使用ALPHA參數可實現按字典序比較.
DESC降序排序(SORT默認升序排序).
LIMIT指定返回結果范圍.
STORESORT默認直接返回排序結果, STORE可將排序后結果保存為List.

注: SORT在對Sorted-Set排序時會忽略元素分數,只針對元素自身值排序.


BY

很多情況下key實際存儲的是對象ID, 有時單純對ID自身排序意義不大,這就用到了BY參數, 對ID關聯的對象的某個屬性進行排序:

[BY pattern]

pattern可以是字符串類型key或Hash類型key的某個字段(表示為鍵名 -> 字段名).如果提供了BY參數, SORT將使用ID值替換參考key中的第一個*并獲取其值,然后根據該值對元素排序.

SORT mi.blog:1:my BY mi.blog:*:data->time DESC
  • 注意:
    • 當pattern不包含*時, SORT將不會執行排序操作;
    • 當ID元素的參考key不存在時,默認設置為0;
    • 如果幾個ID元素的pattern值相同,則會再比較元素本身值排序.

GET

GET參數不影響排序過程,它的作用是使SORT返回結果不再是元素自身的值,而是GET參數指定的鍵值:

[GET pattern [GET pattern ...]]

同BY一樣, GET參數也支持String類型和Hash類型, 并使用*作為占位符.

SORT mi.blog:1:my BY mi.blog:*:data->time GET mi.blog:*:data->content GET mi.blog:*:data->time

注: GET參數獲取自身值需要使用#: GET #


性能

SORT的時間復雜度為O(N+M*log(M)):

where N is the number of elements in the list or set to sort, and M the number of returned elements. When the elements are not sorted, complexity is currently O(N) as there is a copy step that will be avoided in next releases.
  • 所以開發過程中使用SORT需要注意:
  • 盡可能減小待排序key中元素數量(減小N);
  • 使用LIMIT參數限制結果集大小(減小M);
  • 如果待排序數據量較大,盡可能使用STORE將結果緩存.

Message

1. 消息隊列

消息隊列就是”傳遞消息的隊列”,與消息隊列進行交互的實體有兩類, 一是生產者: 將需要處理的消息放入隊列; 一是消費者: 不斷從消息隊列中讀出消息并處理.

使用消息隊列有如下好處:
松耦合: 生產者和消費者無需知道彼此的實現細節, 只需按照協商好的消息格式讀/寫, 即可實現不同進程間通信,這就使得生產者和消費者可以由不同的團隊使用不同的開發語言編寫.
易擴展: 消費者可以有多個,且可以分布在不同的Server中, 降低單臺Server負載, 橫向擴展業務.

Redis提供了BRPOP/BLPOP命令來實現消息隊列:

命令描述
BRPOP key [key ...] timeoutRemove and get the last element in a list, or block until one is available
BLPOP key [key ...] timeoutRemove and get the first element in a list, or block until one is available
BRPOPLPUSH source destination timeoutPop a value from a list, push it to another list and return it; or block until one is available

注: 若Redis同時監聽多個key, 且每個key均有元素可取,則Redis按照從左到右的順序去挨個讀取key的第一個元素.


2. 消息訂閱

前面的BRPOP/BLPOP實現的消息隊列有一個限制: 如果一個隊列被多個消費者監聽, 生產者發布一條消息只會被其中一個消費者獲取. 因此Redis還提供了一組命令實現“發布/訂閱”模式, 同樣可用于進程間通信:

“發布/訂閱”模式也包含兩種角色: 發布者與訂閱者. 訂閱者可以訂閱一個/多個頻道, 而發布者可向指定頻道發送消息, 所有訂閱此頻道的訂閱者都會收到此消息.

命令描述
PUBLISH channel messagePost a message to a channel
SUBSCRIBE channel [channel ...]Listen for messages published to the given channels
UNSUBSCRIBE [channel [channel ...]]Stop listening for messages posted to the given channels
PSUBSCRIBE pattern [pattern ...]Listen for messages published to channels matching the given patterns
PUNSUBSCRIBE [pattern [pattern ...]]Stop listening for messages posted to channels matching the given patterns
  • MessagesQueue
/*** @author jifang* @since 16/7/11 下午2:36.*/ public class MessageQueue<T> {private Jedis redis;private String chanel;public MessageQueue(Jedis redis, String chanel) {this.redis = redis;this.chanel = chanel;}public Long publish(T message) {String json = JSON.toJSONString(message);return redis.publish(chanel, json);}public void subscribe(final MessageHandler<T> handler) {redis.subscribe(new JedisPubSub() {@Overridepublic void onMessage(String channel, String message) {for (Type type : handler.getClass().getGenericInterfaces()) {if (type instanceof ParameterizedType) {ParameterizedType pType = (ParameterizedType) type;Type handlerClass = pType.getActualTypeArguments()[0];T result = JSONObject.parseObject(message, handlerClass);handler.handle(result);}}}}, chanel);} } public interface MessageHandler<T> {void handle(T object); }

注: 發送的消息不會持久化,一個訂閱者只能接收到后續發布的消息,之前發送的消息就接收不到了.


持久化

Redis支持兩種持久化方式: RDB與AOF. RDB: Redis根據指定的規則“定時”將內存數據快照到硬盤; AOF:Redis在每次執行命令后將命令本身記錄下來存放到硬盤.兩種持久化方式可結合使用.


RDB

  • 快照執行過程:
    • Redis使用fork()函數復制一份當前進程副本;
    • 父進程繼續接收并處理客戶端請求, 而子進程將所有內存數據寫入磁盤臨時文件;
    • 當子進程將所有數據寫完會用該臨時文件替換舊的RDB文件, 至此一次快照完成(可以看到自始至終RDB文件都是完整的).

Redis會在以下幾種情況下對數據進行快照:

  • 根據配置規則
    配置由兩個參數構成: 時間窗口M和改動key個數N; 當時間M內被改動的key的個數大于N時, 即符合自動快照條件:
save 900 1 save 300 10 save 60 10000
  • 用戶執行SAVE/BGSAVE/FLUSHALL命令:
    除了讓Redis自動快照, 當進行服務重啟/手動遷移以及備份時也需要我們手動執行快照.
命令描述
SAVESAVE命令會使Redis同步地執行快照操作(過程中會阻塞所有來自客戶端的請求, 因此盡量避免線上使用)
BGSAVE在后臺異步執行快照操作,Redis還可繼續響應請求
FLUSHALLFLUSHALL會清空所有數據,無論是否觸發了自動快照條件(只要有配置了),Redis都會執行一次快照
LASTSAVE獲取最近一次成功執行快照時間
  • 執行復制
    當設置了主從模式, Redis會在復制初始化時執行快照,即使沒有配置自動快照條件.

通過RDB方式實現持久化, Redis在啟動后會讀取RDB快照文件, 將數據從硬盤導入內存, 但如果在持久化過程中Redis異常退出, 就會丟失最后一次快照以后更改的所有數據.


RDB其他配置參數

dir ./ # 設置工作目錄,RDB文件(以及后面的AOF文件)會寫入該目錄 dbfilename dump.rdb # 設置RDB文件名 rdbcompression yes # 導出RDB是否壓縮 rdbchecksum yes # 存儲和加載RDB校驗完整性 stop-writes-on-bgsave-error yes # 后臺備份進程出錯時,主進程停止寫入.

AOF

AOF將Redis執行的每一條命令追加到硬盤文件中.然后在啟動Redis時逐條執行AOF文件中的命令將數據載入內存.

Redis默認沒有開啟AOF, 需要以如下參數啟用:

appendonly yes no-appendfsync-on-rewrite yes: # 正在導出RDB快照的過程中,停止同步AOF.

AOF重寫

開啟AOF后, Redis會將每一條有可能更改數據的命令寫入AOF文件,這樣就導致AOF文件越來越大,即使有可能內存中實際存儲的數據并沒多少. 因此Redis每當達到一定條件就自動重寫AOF文件,這個條件可以在配置文件中設置:

auto-aof-rewrite-percentage 100 # 比起上次重寫時的大小,AOF增長率100%時重寫 auto-aof-rewrite-min-size 64mb # AOF大小超過64M時重寫

此外, 我們還可以使用BGREWRITEAOF命令手動執行AOF重寫.


硬盤數據同步

執行AOF持久化時, 由于操作系統緩存機制, 數據并沒有真正寫入磁盤,而是進入了磁盤緩存, 默認情況下系統每30S執行一次同步操作, 將緩存內容真正寫入磁盤, 如果在這30S的系統異常退出則會導致磁盤緩存數據丟失, 如果應用無法忍受這樣的損失, 可通過appendfsync參數設置同步機制:

# appendfsync always # 每次執行寫入都執行同步 appendfsync everyse # 每秒執行一次同步操作 # appendfsync no # 不主動進行同步, 而是完全由操作系統執行.

集群

1. Replication

復制(replication)中,Redis的角色可以分為兩類, Master:可以執行讀/寫操作,當寫操作導致數據修改時會自動將數據同步給Slave; Slave:一般是只讀的,并接受Master同步過來的數據(Slave自身也可以作為Master存在, 如圖):

  • replication復制時序
    • Slave啟動后向Master發送SYNC命令;Master收到后在后臺保存RDB快照, 并將快照期間接收到的所有命令緩存.
    • 快照執行完, Master將快照文件與所有緩存的命令發送給Slave;
    • Slave接收并載入快照, 然后執行所有收到的緩存命令,這一過程稱為復制初始化.
    • 復制初始化完成后,Master每接收到寫命令就同步給Slave,從而保證主從數據一致.

  • 通過Redis的復制功能可以實現以下應用:
    • 讀寫分離:
      通過復制可實現讀寫分離, 以提高服務器的負載能力, 可以通過復制建立多個Slave節點, Master只進行寫操作, 而由Slave負責讀操作, 這種一主多從的結構很適合讀多寫少的場景.
    • Slave持久化
      持久化是一個相對耗時的操作, 為了提高性能, 可以通過復制功能建立一個/多個Slave, 并在Salve中啟用持久化, Master禁用持久化. 當Master崩潰后:
    • 在Slave使用SLAVEOF NO ONE命令將Slave提升成Master繼續服務;
    • 啟用之前崩潰的Master, 然后使用SLAVEOF將其設置為新Master的Slave, 即可將數據同步回來.

注意: 當開啟復制且Master關閉持久化時, Master崩潰后一定不能直接重啟Master, 這是因為當Master重啟后, 因為沒有開啟持久化, 所以Redis內的所有數據都會被清空, 這時Salve從Master接受數據, 所有的Slave也會被清空, 導致Slave持久化失去意義.

關于Redis復制的詳細介紹以及配置方式可參考博客:Redis研究 -主從復制.


2. Sentinel

當Master遭遇異常中斷服務后, 需要手動選擇一個Slave升級為Master, 以使系統能夠繼續提供服務. 然而整個過程相對麻煩且需要人工介入, 難以實現自動化. 為此Redis提供了哨兵Sentinel.

Sentinel哨兵是Redis高可用性解決方案之一: 由一個/多個Sentinel實例組成的Sentinel系統可以監視任意多個Master以及下屬Slave, 并在監控到Master進入下線狀態時, 自動將其某個Slave提升為新的Master, 然后由新的Master代替已下線的Master繼續處理命令請求.

  • 如圖: 若此時Master:server1進入下線狀態, 那么Slave: server2,server3,server4對Master的復制將被迫中止,并且Sentinel系統也會察覺到server1已下線, 當下線時長超過用戶設定的下線時長時, Sentinel系統就會對server1執行故障轉移操作:

    • Sentinel會挑選server1下屬的其中一臺Slave, 將其提升為新Master;
    • 然后Sentinel向server1下屬的所有Slave發送新的復制指令,讓他們成為新Master的Salve, 當所有Salve都開始復制新Master時, 故障轉移操作完成.
    • 另外, Sentinel還會繼續監視已下線的server1, 并在他重新上線時, 將其設置為新Master的Slave.

關于Redis哨兵的詳細介紹以及配置方式可參考博客:Redis Sentinel(哨兵):集群解決方案.


3. Cluster

Cluster是Redis提供的另一高可用性解決方案:Redis集群通過分片(sharding)來進行數據共享, 并提供復制故障轉移功能.

一個 Redis 集群通常由多個節點組成, 最初每個節點都是相互獨立的,要組建一個真正可工作的集群, 必須將各個獨立的節點連接起來.連接各個節點的工作可以使用CLUSTER MEET命令完成:

CLUSTER MEET <ip> <port>

向一個節點發送CLUSTER MEET命令,可以使其與ip+port所指定的節點進行握手,當握手成功時, 就會將目標節點添加到當前節點所在的集群中.

  • 案例
    假設現在有三個獨立的節點 127.0.0.1:7000 、 127.0.0.1:7001 、 127.0.0.1:7002:

    • 通過向節點 7000 發送CLUSTER MEET 127.0.0.1 7001命令,可將節點7001添加到節點7000所在的集群中:
    • 繼續向節點7000發送CLUSTER MEET 127.0.0.1 7002命令,同樣也可將節點7002也拉進來:
    • 至此, 握手成功的三個節點處于同一個集群:

關于Redis-Cluster的詳細介紹以及更多配置方式可參考博客:redis-cluster研究和使用.


管理

1. 數據庫密碼

通過在配置文件中使用requirepass參數可為Redis設置密碼:

requirepass ?∑′??¥¨??π

這樣客戶端每次連接都需要發送密碼,否則Redis拒絕執行客戶端命令:

AUTH ?∑′??¥¨??π

2. 重命名

Redis支持在配置文件中將命令重命名, 以保證只有自己的應用可以使用該命令:

rename-command FLUSHALL qwertyuiop

如果希望禁用某個命令,可將命令重命名為空字符串.


3. 工具

  • SLOWLOG
    當一條命令執行超過時間限制時,Redis會將其執行時間等信息加入耗時統計日志, 超時時間等可通過以下配置實現:
slowlog-log-slower-than 10000 # 超時限制(單位微秒) slowlog-max-len 128 # 記錄條數限制
  • MONITOR : 監控Redis執行的所有命令

    注意: MONITOR命令非常影響Redis性能, 一個客戶端使用MONITOR會降低Redis將近一半的負載能力. Instagram團隊開發了一個基于MONITOR命令的Redis查詢分析工具redis-faina, 可根據MONITOR的監控結果分析出最常用的命令/訪問最頻繁的key等信息, 詳細可參考博客:關于 Redis 的性能分析工具 Redis Faina.

  • 其他常用管理工具

TIME # 系統時間戳與微秒數 DBSIZE # 當前數據庫的key數量 INFO # Redis服務器信息 CONFIG GET # 獲取配置信息 CONFIG SET # 設置配置信息 CONFIG REWRITE # 把值寫到配置文件 CONFIG RESTART # 更新INFO命令信息 CLIENT LIST # 客戶端列表 CLIENT KILL # 關閉某個客戶端 CLIENT SETNAME # 為客戶端設置名字 CLIENT GETNAME # 獲取客戶端名字 DEBUG OBJECT key # 調試選項,查看一個key的信息 DEBUG SEGFAULT # 模擬段錯誤,使服務器崩潰 OBJECT (refcount|encoding|idletime) key
參考&拓展
高可用、開源的Redis緩存集群方案
Twemproxy——針對MemCached與Redis的代理
Redis 3.0正式版發布,正式支持Redis集群
Redis應用實踐:小紅書海量Redis存儲之道
Redis內存優化實踐
視頻: Raft 教程
使用Redis作為時間序列數據庫:原因及方法
Redis復制與可擴展集群搭建

總結

以上是生活随笔為你收集整理的Redis与Java - 实践的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。