1. 程式人生 > >Redis的常用命令與Java整合及高階應用篇

Redis的常用命令與Java整合及高階應用篇

一,redis是什麼?

​ 首先資料庫分為關係型資料庫和非關係型資料庫,關係型資料庫是採用關係模型來組織資料的資料庫,簡單來說就是二維表格模型,同時保證事務的一致性。

​ 相反非關係型資料庫採用key-value形式進行儲存,是一種資料結構化儲存方法的集合,具有分散式性質。

​ Redis是當前比較熱門的NOSQL系統之一,它是一個開源的使用ANSI c語言編寫的key-value儲存系統(區別於MySQL的二維表格的形式儲存。)遵守BSD協議、支援網路、可基於記憶體亦可持久化的日誌型、Key-Value資料庫,並提供多種語言的API。它通常被稱為資料結構伺服器,因為值(value)可以是 字串(String), 雜湊(Map), 列表(list), 集合(sets) 和 有序集合(sorted sets)等型別。

二,redis的優勢

​ 1,效能快:redis讀取的速度是110000次/s,寫的速度是81000次/s。

​ 2,豐富的資料型別:string(字串);list(列表);hash(雜湊),set(集合);zset(有序集合)等。

​ 3,原子性:Redis的所有操作都是原子性的,且多個客戶端同時訪問redis客戶端可獲得更新後的值。

​ 4,持久化:叢集(主從複製,分散式)。

三,redis與Memcached區別(經典面試題)

1 、redis不僅僅支援簡單的k/v型別的資料,同時還提供list,set,zset,hash等資料結構的儲存型別。memcache支援簡單的資料型別,String,同時還可以快取圖片,視訊。

2 、Redis支援資料的備份,即master-slave模式的資料備份(主從複製)。

3 、Redis支援資料的持久化,可以將記憶體中的資料保持在磁碟中,重啟的時候可以再次載入進行使用。

4、 redis的速度比memcached快很多

5、Memcached是多執行緒,非阻塞IO複用的網路模型;Redis使用單執行緒的IO複用模型。

6,資料安全性:memcache掛掉後,資料便消失;redis可以定期儲存到磁碟(持久化)。

四,redis常用命令及springboot操作redis

4.1,引入依賴包。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

4.2,常用命令與程式碼整合操作。

​ 注意:redis預設使用JDK序列化方式

​ 更多詳細命令,請參考Redis中文網:https://www.redis.net.cn/

4.3,字串操作型別。

/**
 * String - 字串型別的操作方式
 * redisTemplate.opsForValue()
*/
    @Test
    public void stringType(){
        //  改為String序列化方式
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());

        //  redis命令:set key value
        redisTemplate.opsForValue().set("age", "19");

        // redis命令:get key
        String age = (String) redisTemplate.opsForValue().get("age");
        System.out.println("-->" + age);

        // redis命令:mset key value key value ...
        Map<String, Object> map = new HashMap<>();
        map.put("key1", "value1");
        map.put("key2", "value2");
        map.put("key3", "value3");
        redisTemplate.opsForValue().multiSet(map);

        // redis命令:mget key key key...
        List<String> keys = new ArrayList<>();
        keys.add("key1");
        keys.add("key2");
        keys.add("key3");
        List values = redisTemplate.opsForValue().multiGet(keys);
        System.out.println("mget -->" + values);

        // redis命令:del key
        Boolean boo = redisTemplate.delete("key1");

        // redis命令:strlen key - 可能會因為序列化的原因造成長度不準
        Long resultLong = redisTemplate.opsForValue().size("age");
        System.out.println("strlen --> " + resultLong);

        // redis命令:getset key value
        String oldValue = (String) redisTemplate.opsForValue().getAndSet("age", "25");
        System.out.println("getset --> " + oldValue);

        // redis命令:getrange key start end - 可能會因為序列化的原因造成長度不準
        String age1 = redisTemplate.opsForValue().get("age", 0, 1);
        System.out.println("getrange --> " + age1);

        // redis命令:append - 可能會因為序列化的原因造成長度不準
        Integer age2 = redisTemplate.opsForValue().append("age", "26");
        System.out.println("append --> " + age2);

        // redis命令:incr key - 自增 - 可能會因為序列化的原因造成長度不準
        Long age3 = redisTemplate.opsForValue().increment("age", 10);
        System.out.println("incr -->" + age3);

        // redis命令:decr key - 自減
        redisTemplate.opsForValue().increment("age", -10);
        Long decr = redisTemplate.getConnectionFactory().getConnection().decr("age".getBytes());
        System.out.println("decr --> " + decr);

    }

4.4,Hash操作型別。

/**
 * Hash資料型別的操作
 */
    @Test
    public void hashType(){
        
        // redis命令:mset key field value
        redisTemplate.opsForHash().put("person", "name", "張三");
        redisTemplate.opsForHash().put("person", "age", 19);

        // redis命令:mget key field
        String value = (String) redisTemplate.opsForHash().get("person", "name");
        System.out.println("mget-->" + value);

        // redis命令:hmset key field1 value1 field2 value2 ...
        Map<String, String> map = new HashMap<>();
        map.put("bookname", "Java精通之路");
        map.put("price", "100.99");
        redisTemplate.opsForHash().putAll("book", map);

        // redis命令:hmget key field1 field2 ...
        List<String> list = new ArrayList<>();
        list.add("bookname");
        list.add("price");
        List books = redisTemplate.opsForHash().multiGet("book", list);
        System.out.println("hmget-->" + books);

        // redis命令:del key
        redisTemplate.delete("book");

        // redis命令:hdel key field1 field2...
        redisTemplate.opsForHash().delete("book", "bookname", "price");

        // redis命令:hexists key field
        Boolean bool = redisTemplate.opsForHash().hasKey("book", "bookname");
        System.out.println("hexists-->" + bool);

        // redis命令:hlen key
        Long length = redisTemplate.opsForHash().size("book");
        System.out.println("hlen-->" + length);

        // redis命令:hkeys key - 展示key對應的所有欄位名稱
        Set set = redisTemplate.opsForHash().keys("book");
        System.out.println("hkeys-->" + set);

        // redis命令:hvals key - 展示key對應的所有欄位的值
        List values = redisTemplate.opsForHash().values("book");
        System.out.println("hvals-->" + values);

        // redis命令:hgetall key - field and value
        Map bookmap = redisTemplate.opsForHash().entries("book");
        System.out.println("hgetall-->" + bookmap);
    }

4.5,連結串列資料結構。

/**
 * 連結串列資料結構
 */
    @Test
    public void linkedType(){
        // redis命令:lpush key value1 value2...
        redisTemplate.opsForList().leftPush("book", "c++");
        redisTemplate.opsForList().leftPushAll("book", "c", "java");

        // redis命令:rpush key value1 value2
        redisTemplate.opsForList().rightPush("book", "mysql");
        redisTemplate.opsForList().rightPushAll("book", "oracle", "sqlserver");

        // redis命令:lindex key index
        String book0 = (String) redisTemplate.opsForList().index("book", 0);
        System.out.println("lindex-->" + book0);

        // redis命令:llen key
        Long bookLen = redisTemplate.opsForList().size("book");
        System.out.println("llen-->" + bookLen);

        // redis命令:lpop key
        String leftBook = (String) redisTemplate.opsForList().leftPop("book");
        System.out.println("lpop-->" + leftBook);

        // redis命令:rpop key
        String rightBook = (String) redisTemplate.opsForList().rightPop("book");
        System.out.println("rpop-->" + rightBook);

        // redis命令:linsert key before|after oldnode newnode
        redisTemplate.opsForList().leftPush("book", "java", "pythod");
        redisTemplate.opsForList().rightPush("book", "java", "jquery");

        // redis命令:lrange key start end
        List rangeList = redisTemplate.opsForList().range("book", 0, redisTemplate.opsForList().size("book") - 1);
        System.out.println("lrange-->" + rangeList);

        // redis命令:lset key index value
        redisTemplate.opsForList().set("book", 0, "db");

        // redis命令:ltrim key start end
        redisTemplate.opsForList().trim("book", 1, 3);

        // redis命令:lrange key start end
        List rangeList2 = redisTemplate.opsForList().range("book", 0, redisTemplate.opsForList().size("book") - 1);
        System.out.println("lrange-->" + rangeList2);

    }

4.6,集合操作型別。

/**
* 集合操作
 */
    @Test
    public void setType(){
        // redis命令:sadd
        redisTemplate.opsForSet().add("person", "小明","小紅","小剛");

        // redis命令:scard
        Long person = redisTemplate.opsForSet().size("person");
        System.out.println("scard-->" + person);

        // redis命令:smembers
        Set set = redisTemplate.opsForSet().members("person");
        System.out.println("smembers-->" + set);
    }

4.7,有序集合操作型別

/**
* 有序集合
*/
    @Test
    public void zsetType(){
        redisTemplate.opsForZSet().add("book", "mysql", 1.5);
        redisTemplate.opsForZSet().add("book", "java", 8.5);
        redisTemplate.opsForZSet().add("book", "html", 10.5);

        Set set = redisTemplate.opsForZSet().range("book", 0, redisTemplate.opsForZSet().size("book") - 1);
        System.out.println(set);
    }

五,redis高階應用

5.1,事務

​ 與其他NoSQL不同,Redis是存在事務的,儘管沒有資料庫那麼強大,但是還是非常有用,尤其是在高併發的情況中,使用redis的事務可以保證資料一致性的同時,大幅度提高資料讀寫的響應速度。

​ redis的事務是使用multi-exec的命令組合,使用它可以提供兩個重要保證:

​ 1、事務是一個被隔離的操作,事務中的方法都會被redis進行序列化並按順序執行,事務在執行的過程中不會被其他客戶端的發出的命令所打斷。

​ 2、事務是一個原子性操作,它要麼全部執行、要麼全部不執行。

​ ==事務的常用命令:==

​ multi:開啟事務,之後的命令就會進入佇列,而不是馬上執行。

​ watch key1 [key2]...:監聽某些鍵,當被監聽的鍵在提交事務前被修改,則事務會回滾 (基於樂觀鎖機制)。

​ unwatch key1 [key2]...:取消監聽。

​ exec:執行事務,如果被監聽的鍵沒有被修改,則採用提交命令,否則就執行回滾命令。

​ discard:回滾事務。

​ 事務的開啟及提交如下圖:

​ 從上圖中看出,當開始事務時進行操作,命令並不會馬上執行,而是放在佇列中,只有在事務提交後才會執行。

​ ==但是,要注意注意:redis中,如果遇到格式正確而資料型別不符合的情況時,不會進行事務回滾。這是什麼意思,如下圖操作所示:==

​ 問題描述:比如我要儲存1000金額到記憶體中,但是我不小心將金額輸入成1000a,後面多了一個a。但是同樣儲存到了佇列中。最後當提交事務的時候便會報錯,可是1000a還是儲存到了記憶體,證明事務並沒有回滾。

​ redis中存在監聽機制,可以監聽某一個key。

/**
* redis的事務管理,要保證事務的開啟和提交是同一條連線。
*/
    @Test
    public void transcation(){
        List results = (List) redisTemplate.execute(new SessionCallback() {
            @Override
            public Object execute(RedisOperations redisOperations) throws DataAccessException {
        //開啟事務
        redisOperations.multi();
        //進行操作
        redisOperations.opsForValue().set("name", "張三");
        redisOperations.opsForValue().get("name");
        //提交事務
        List result = redisOperations.exec();
        return result;
    }
});
    System.out.println("-->" + results);
}

5.2,流水線

​ 在現實情況中,redis的讀寫速度十分快,而系統的瓶頸往往是在網路通訊中的延遲。redis可能會再很多時候處於空閒狀態而等待命令的到達。為了解決這個問題,可以使用redis的流水線,流水線是一種通訊協議,類似一個佇列批量執行一組命令。

​ 由於這種情況在實際工作中較少使用,所以就簡短介紹一下。

/**
* 流水線
*/
    @Test
    public void pipelined(){
        //流水線 
        long begin = System.currentTimeMillis();
        redisTemplate.executePipelined(new SessionCallback() {
            @Override
            public Object execute(RedisOperations redisOperations) throws DataAccessException {
                for (int i = 0; i < 100000; i++) {
                    redisOperations.opsForValue().set("key" + i, "value" + i);
                    redisOperations.opsForValue().get("key" + i);
                }
                return null;
            }
        });
        long end = System.currentTimeMillis();
        System.out.println("耗時:" + (end - begin));
    }

5.3,釋出訂閱

​ 說到釋出訂閱是否會想到RabbitMQ等訊息中介軟體?

​ 但是redis的釋出訂閱具有實時性,當釋出者改變資料時,訂閱者便會接收到更改後的訊息。

​ ==使用命令:==

​ subscribe chat:訂閱chat渠道。

​ publish chat "message:釋出訊息到chat渠道。

5.3.1,定義監聽類:

/**
 * Redis訊息監聽器
 */
public class RedisMessageListener implements MessageListener{
    private RedisTemplate template;
    @Override
    public void onMessage(Message message, byte[] pattern) {
        //獲取渠道名稱
        System.out.println("渠道名稱:" + new String(pattern));
        //獲得訊息
        byte[] body = message.getBody();
        //獲得值序列化轉換器
        String msg = (String) template.getValueSerializer().deserialize(body);
        System.out.println("訊息為:" + msg);
    }
    public RedisTemplate getTemplate() {
        return template;
    }
    public void setTemplate(RedisTemplate template) {
        this.template = template;
    }
}

5.3.2,配置檔案配置監聽

<!-- 配置監聽器 -->
    <bean id="redisMsgListener" class="com.yx.redis.RedisMessageListener">
        <!-- 注入redis模板 -->
        <property name="template" ref="template"></property>
    </bean>
    <!-- 配置監聽容器 -->
    <bean id="topicContainer" class="org.springframework.data.redis.listener.RedisMessageListenerContainer" destroy-method="destroy">
        <!-- redis連線工廠 -->
        <property name="connectionFactory" ref="connectionFactory"/>
        <!-- 配置連線池,連線池生存才能持續監聽 -->
        <property name="taskExecutor">
            <bean class="org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler">
                <property name="poolSize" value="3"/>
            </bean>
        </property>     
        <!-- 配置訊息監聽 -->
        <property name="messageListeners">
            <map>
                <!-- key-ref和監聽器的id保持一致 -->
                <entry key-ref="redisMsgListener">
                    <bean class="org.springframework.data.redis.listener.ChannelTopic">
                        <!-- 定義渠道 -->
                        <constructor-arg value="yx"/>
                    </bean>
                </entry>
            </map>
        </property>
    </bean>

5.3.3,釋出訊息

ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext-*.xml");
RedisTemplate redistemp = context.getBean(RedisTemplate.class); 
redistemp.convertAndSend("yx", "Hello");

5.4,超時時間

​ 在redis中的超時時間時非常重要的,因為我們的記憶體時有限的,在一段時間內如果沒有對一些資料進行處理。那便會產生很多的垃圾資料,因此對資料進行時間 上的設定是一種較好的習慣。

​ 這裡先暫時不講述過期時間的原理,後面會與大家分享,還請關注哦~~~

​ ==超時時間的命令:==

​ persist key:持久化key,即得永生(移除key的超時時間)。

​ expire key seconds:設定超時時間,單位為秒。

​ ttl key:檢視key的超時時間,單位為秒,返回-1表示沒有超時時間,如果key不存在或者已經超時,則返回-2。

​ pttl key:檢視key的超時時間,單位為毫秒。

​ pexpire key milliseconds:設定key的超時時間,以毫秒為單位。

​ ==關於超時時間需要有幾點注意:當一個key過了超時時間以後,並不會立刻從記憶體中移除。在以下情況下資料會被清除。==

​ 1、當要獲得key的值的時候,比如執行了get key命令。

​ 2、系統自己會有一個定時器,每隔1秒,掃描一次記憶體。清除超時的key(不會完全掃描所有的key,不會完全的移除所有超時的key)。

​ 3、記憶體已滿,就會根據配置檔案進行記憶體資料的清理。

@Test
    public void expire() throws ParseException {
        redisTemplate.opsForValue().set("name", "小明");
        //設定超時時間 - 5 ~ 10分鐘
        redisTemplate.expire("name", 10, TimeUnit.SECONDS);
        //設定超時時間到指定的時間
        String time = "2019-08-23 12:00:00";
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date date = sdf.parse(time);
        redisTemplate.expireAt("name", date);
        //移除超時時間
        redisTemplate.persist("name");
        //獲得還能活多久
        redisTemplate.getExpire("name");
        String name = (String) redisTemplate.opsForValue().get("name");
        System.out.println(name);
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(name);
    }

六,總結

​ 寫到這裡redis的基本使用也差不多了,但是仍有很多技術點沒有記錄到。比如與Lua語言的結合使用,redis的持久化,記憶體的淘汰策略,讀寫分離(哨兵模式),叢集等。

​ 如果你看到這篇部落格,以上沒有分享的內容會在後續釋出的,還請關注~

​ 最後,以上內容均是自主學習的總結,如有錯誤或者不合適的地方歡迎留言(或者郵箱)指教。

​ 感謝觀看!