Redis系列二之事務及消息通知


一、事務

  Redis中的事務是一組命令的集合。一個事務中的命令要么都執行,要么都不執行。

  1、事務簡介

  事務的原理是先將一個事務的命令發送給Redis,然后再讓Redis依次執行這些命令。下面看一個示例:

  

  首先,使用multi命令告訴Redis:下面我給你的命令屬於同一個事務,你先不要執行,而是暫時存起來。

  然后,我們發送兩個set命令來實現賦值,可以看到redis沒有執行這些命令,而是返回queued表示這兩條命令已經進入等待執行的事務隊列中。

  當所有要在同一事務中執行的命令都發給Redis后,用exec命令告訴Redis將等待執行的事務隊列中所有的命令按照發送順序依次執行。

  2、錯誤處理

  當一個事務中某個命令執行出錯時,Redis會怎樣處理呢?這里有二種情況:

  情況一:語法錯誤,指錯命令不存在或者命令參數的個數不對

  

  可以看到,跟在multi命令后執行了三個命令,第二個和第三個命令有語法錯誤,執行exec后redis就會執行返回錯誤。

  情況二:運行錯誤,指在命令執行時出現的錯誤。

  

  這種錯誤在實際執行前redis是無法發現的,所以在事務里這樣的命令是會被redis接受和執行的,如果事務里一條命令出現了運行錯誤,事務里其他的命令依然會繼續執行(包括出錯命令之后的命令)。

  注意:Redis事務沒有關系型數據庫提供的回滾功能。

  3、Watch命令

  watch命令可以監控一個或多個鍵,一旦其中一個鍵被修改或刪除,之后的事務就不會執行,監控一直持續到exec命令(但是不能保證其他客戶不修改這一鍵值)。

  

 

  上例中執行watch命令后,事務執行前修改了key值,所以事務中命令set username xujian沒有執行,exec命令返回空結果。

   注意:執行exec命令后立即取消對所有鍵的監控,如果不想執行事務中的命令也可以使用unwatch命令來保證下一個事務的執行不會受到影響。

二、生存時間

  在Redis中,可以使用expire命令設置一個鍵的生存時間,到時間后Redis會自動刪除它。

  expire命令的使用方法為expire key seconds,其中seconds參數表示鍵的生存時間,單位是秒。如果想知道一個鍵還有多久的時間會被刪除,可以使用TTL命令,返回值是鍵的剩余時間(單位是秒)。如果想取消鍵的生存時間設置,即將恢復永久的,可以使用persist命令。

  

三、排序

  1、有序集合

  有序集合常見的使用場景是大數據排序,如游戲的玩家排行榜。

  2、sort命令

  sort命令可以對列表類型、集合類型和有序集合類型鍵進行排序,並且可以完成與關系數據庫中的連接查詢相類似的任務。

  對List進行排序:

  

  對有序集合類型排序時會忽略元素的分數,只針對元素自身的值進行排序:

  

3、by參數

  by參數的語法為“by 參考鍵”,其中參考鍵可以是字符串類型鍵或者是散列類型鍵的某個字段。如果提供了by參數,sort命令將不再依據元素自身的值進行排序,而是對每個元素使用元素的值替換參考鍵中的第一個“*”並獲取其值,然后依據該值對元素排序:

  

4、store參數

  默認情況下,sort會直接返回排序結果,如果希望保存排序結果,可以使用store參數。

  注意:sort命令的時間復雜度是O(n+mlogm),其中n表示要排序的列表(集合或有序集合)中的元素個數,m表示要返回的元素的個數。當n較大的時候sort命令的性能相對較低,並且redis在排序前會建立一個長度為n的容器來存儲待排序的元素。在開發中使用sort要注意以下幾點:

  1、盡可能減少待排序鍵中元素的數量(使n盡可能小)

  2、使用limit參數只獲取需要的數據(使m盡可能小)

  3、如果要排序的數據量較大,盡可能使用store參數將結果緩存

四、消息通知

  一般來說,消息隊列有兩種場景,一種是生產者消費者模式,一種是發布者訂閱者模式。利用redis這兩種場景的消息隊列都能實現。

  1、生產者消費者模式

  生產者生產消息放到隊列中,多個消費者同時監聽隊列,誰先搶到消息誰就會從隊列中取走消息,即對於每個消息最多只能被一個消費者擁有。

  具體的方法就是創建一個任務隊列,生產者主動lpush消息,而消費者去rpop數據。但是這樣存在一個問題,就是消費者需要主動去請求數據,周期性的請求會造成資源的浪費。如果可以實現一旦有新消息加入隊列就通知消費者就好了,這時借助brpop命令就可以實現這樣的需求。brpop和rpop命令相似,唯一區別就是當列表中沒有元素時,brpop命令會一直阻塞住連接,直到有新元素加入。

BRPOP key timeout

  brpop命令接收兩個參數,第一個參數key為鍵值,第二個參數timeout為超時時間。BRPOP命令取數據時候,如果暫時不存在數據,該命令會一直阻塞直到達到超時時間。如果timeout設置為0,那么就會無限等待下去。

  

  2、發布者訂閱者模式

  發布者生產消息放到隊列里,多個監聽隊列的訂閱者都會受到同一份消息。

  生產者使用下面命令來發布消息:

PUBLISH CHANNEL MESSAGE

  訂閱者通過下面的命令來訂閱消息,執行subscribe命令后,客戶端進入訂閱狀態,處於此狀態的客戶端不能使用4個屬於“發布/訂閱”模型的命令之外的命令。另外,可以使用subscribe channel1.1 channel1.2 ... 同時訂閱多個頻道。

SUBSCRIBE CHANNEL

  

  3、Java實現的redis的消息隊列

  在jedis中,有對應的方法進行訂閱和發布,為了傳輸對象,需要將對象進行序列化,並封裝成字符串進行處理。

  下面我們要實現三個類,一個對應publish,一個對應subscribe,一個對應要傳遞的對象實體類:

  實體類:

import java.io.Serializable;
/**
 * 實體類
 * 封裝消息
 * @author Administrator
 *
 */
public class Message implements Serializable
{
    private static final long serialVersionUID = 1L;
    private String title;
    private String content;
    public String getTitle()
    {
        return title;
    }
    public void setTitle(String title)
    {
        this.title = title;
    }
    public String getContent()
    {
        return content;
    }
    public void setContent(String content)
    {
        this.content = content;
    }
}

  Publish類:

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import redis.clients.jedis.Jedis;
/**
 * 發布者,用於發布消息
 * @author Administrator
 *
 */
public class TestPub
{
    public static void main(String[] args)
    {
        Jedis jedis = new Jedis("127.0.0.1");
        try
        {
            Message message = new Message();
            message.setTitle("體育新聞");
            message.setContent("著名NBA球星科比退役了!");
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(message);
            String msg1 = baos.toString("ISO-8859-1");

            jedis.publish("foo", msg1);
        } catch (Exception e)
        {
            e.printStackTrace();
        }
        jedis.close();
    }
}

  Subscribe類:

import java.io.ByteArrayInputStream;
import java.io.ObjectInputStream;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPubSub;
/**
 * 訂閱者,用於接收消息
 * @author Administrator
 *
 */
public class TestSub
{
    public static void main(String[] args)
    {
        Jedis jedis = new Jedis("127.0.0.1");
        JedisPubSub jedisPubSub = new JedisPubSub() 
        {
            @Override
            public void onUnsubscribe(String channel, int subscribedChannels)
            {
            }

            @Override
            public void onSubscribe(String channel, int subscribedChannels)
            {
            }

            @Override
            public void onPUnsubscribe(String pattern, int subscribedChannels)
            {
            }

            @Override
            public void onPSubscribe(String pattern, int subscribedChannels)
            {
            }

            @Override
            public void onPMessage(String pattern, String channel, String message)
            {
            }

            @Override
            public void onMessage(String channel, String message)
            {
                try
                {
                    ByteArrayInputStream bis = new ByteArrayInputStream(message.getBytes("ISO-8859-1"));
            // 此處指定字符集將字符串編碼成字節數組,此處的字符集需要與發布時的字符集保持一致 ObjectInputStream ois = new ObjectInputStream(bis); Message message2= (Message) ois.readObject(); System.out.println(message2.getTitle()+"\n"+message2.getContent()); } catch (Exception e) { e.printStackTrace(); } finally { } } }; jedis.subscribe(jedisPubSub, "foo"); jedis.close(); } }

  先執行訂閱操作,然后發布者發布消息,執行結果為:

  

五、管道

  客戶端和Redis使用TCP協議連接,不管是客戶端向Redis發送命令還是Redis向客戶端返回命令的執行結果,都需要經過網絡傳輸。在執行過個命令時,每條命令都需要等待上一條命令執行完才能執行,即使命令不需要上一條命令的執行結果。

  Redis的底層通信協議對管道提供了支持,通過管道可以一次性發送多條命令並在執行完后一次性將結果返回,當一組命令中每條命令都不依賴之前命令的執行結果時就可以將這組命令一起通過管道發出。管道通過減少客戶端與Redis的通信次數來實現降低往返時延累計值的目的。

六、存儲優化

  1、精簡鍵名和鍵值

  2、內部編碼優化

七、參考資料

  1、Redis入門指南


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM