1. 程式人生 > >Redis及Spring-Data-Redis入門學習

Redis及Spring-Data-Redis入門學習

繼上一篇Solr和Spring Data Solr學習,我們思考一個問題,使用Solr的目的是什麼?肯定是為了加快伺服器的相應速度。因為即使不適用Solr,通過請求資料庫我們一樣能完成搜尋功能,但是這樣會給伺服器造成很大的壓力。

而Solr僅僅是在搜尋功能中用到了,但是大量請求的資料不僅僅出現在搜尋中,比如使用者的登入資訊,雖然資料量很小,但是整個專案每重新整理一次頁面都要請求一次使用者登入的Token資訊,也會拖慢伺服器的響應速度。我們通常有兩中解決方式:1.資料快取;2.網頁靜態化。

其實我們在Shiro實現使用者-角色-許可權管理系統中已經用到了快取技術,今天我們瞭解一下Redis快取技術。

專案開源地址: Github

安裝Redis

Redis是一款開源的Key-Value資料庫。首先我們要去 官網 下載Redis,由於筆者使用的是MacOS系統,和Windows系統有所不同。

安裝過程不再敘述,這裡提供兩個教程:

<br/>

啟動Redis

redis-server 
redis-server &

建議使用第二個命令,用第二個命令啟動了redis server後能繼續輸入命令,使用第一個命令則不行。

如果終端中顯示如下logo表示redis啟動成功:

<br/>

操縱Redis

上面僅僅是啟動了Redis Server,但Redis是一種Key-Value型資料庫,也包含了一些查詢資料庫的命令,操作redis命令的入口就是: redis/bin/redis-cli

./bin/redis-cli

redis-cli

  1. 檢視當前(db0)資料庫中所有的key值: keys *
  2. 清空當前資料庫中所有的資料: flushall

更多的Redis命令可以參看:redis中文文件

<br/>

Spring Data Redis

之前學習Solr的時候用到了Spring Data Solr,現在學習Redis,Spring提供了Spring Data Redis用來實現通過配置檔案的方式訪問redis服務。Spring Data Redis對Redis底層開發包(Jedis, JRedis, and RJC)進行了高度封裝,RedisTemplate

提供了redis各種操作、異常處理及序列化。

Jedis

Jedis是Redis官方推出的一款面向Java的客戶端,提供了很多借口供Java語言呼叫。

Spring Data Redis針對Jedis提供瞭如下功能:

  • 1.連線池自動管理,提供了一個高度封住的RedisTemplate類。
  • 2.針對jedis客戶端中大量api進行歸類封裝,將同一型別操作封裝為operation介面: ValueOperations: 簡單的K-V操作 SetOperations: set型別資料操作 ZSetOperations: zset型別資料操作 HashOperations: 針對Map型別的資料操作 ListOperations: 針對List型別的資料操作

準備

匯入依賴

<dependency> 
		  <groupId>redis.clients</groupId> 
		  <artifactId>jedis</artifactId> 
		  <version>2.8.1</version> 
</dependency> 
<dependency> 
		  <groupId>org.springframework.data</groupId> 
		  <artifactId>spring-data-redis</artifactId> 
		  <version>1.7.2.RELEASE</version> 
</dependency>	

建立redis-config.properties

redis.host=127.0.0.1 
redis.port=6379 
redis.pass= 
redis.database=0 
redis.maxIdle=300 
redis.maxWait=3000 
redis.testOnBorrow=true

解釋

  1. redis.host是安裝redis server的客戶端IP地址,如果安裝在本機上就是127.0.0.1,如果安裝在伺服器上請修改為伺服器的IP地址。
  2. redis.port是redis server的預設埠,你安裝了redis,就預設使用這個埠號。
  3. redis.pass是訪問redis server的密碼,一般我們不設定。
  4. redis.database=0代表使用的是redis預設提供的db0這個資料庫。
  5. redis-maxIdle是redis server的最大空閒數。
  6. redis-maxWait是連線redis時的最大等待毫秒數。
  7. redis-testOnBorrow在提取一個redis例項時,是否提前進行驗證操作;如果為true,則得到的jedis例項均是可用的。

建立spring-redis.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
		http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context
		http://www.springframework.org/schema/context/spring-context.xsd">

    <context:property-placeholder location="classpath:other/*.properties"/>
    <!-- redis 相關配置 -->
    <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <!-- 最大空閒數 -->
        <property name="maxIdle" value="${redis.maxIdle}"/>
        <!-- 連線時最大的等待時間(毫秒) -->
        <property name="maxWaitMillis" value="${redis.maxWait}"/>
        <!-- 在提取一個jedis例項時,是否提前進行驗證操作;如果為true,則得到的jedis例項均是可用的 -->
        <property name="testOnBorrow" value="${redis.testOnBorrow}"/>
    </bean>
    <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <property name="hostName" value="${redis.host}"/>
        <property name="port" value="${redis.port}"/>
        <property name="password" value="${redis.pass}"/>
        <property name="poolConfig" ref="poolConfig"/>
    </bean>

    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
        <property name="connectionFactory" ref="jedisConnectionFactory"/>
    </bean>
</bean>

例項

本例項原始碼:Github

首先載入配置檔案spring-redis.xml,注入RedisTemplate模板類:

@Autowired
private RedisTemplate redisTemplate;

值型別

RedisTemplate提供的很多操作redis資料庫的方法都是boundxxOps這種。

新增

@Test
public void setValue(){
    redisTemplate.boundValueOps("name").set("tycoding");
}

如果配置都正常的情況下,執行此方法就能向db0資料庫中新增一條key為name的記錄;那麼我們在redis命令列中檢視所有的key:

奇怪,我新增的key明明是name,為什麼查出來的確實一堆亂碼值呢?我們再使用redis命令列單獨新增一條記錄:

set testK testV

此時我們又發現,使用redis原生命令新增的資料是不會亂碼的;那麼就肯定是Spring Data Redis的原因了。經查詢是因為redisTemplate模板類在操作redis序列化的原因,我們要手動配置序列化方式為:StringRedisSerializer

修改之前建立的spring-redis.xml配置檔案:

<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
    <property name="connectionFactory" ref="jedisConnectionFactory"/>

    <!-- 序列化策略 推薦使用StringRedisSerializer -->
    <property name="keySerializer">
        <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
    </property>
    <property name="valueSerializer">
        <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
    </property>
    <property name="hashKeySerializer">
        <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/>
    </property>
    <property name="hashValueSerializer">
        <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/>
    </property>
</bean>

再次新增資料

查詢

@Test
public void getValue(){
    Object name = redisTemplate.boundValueOps("name").get();
    System.out.println(name);
}

刪除

@Test
public void deleteValue(){
    redisTemplate.delete("name");
}

Set型別

新增

@Test
public void setValueBySet(){
    redisTemplate.boundSetOps("nameset").add("tycoding");
}

查詢

@Test
public void getValueBySet(){
    Set nameset = redisTemplate.boundSetOps("nameset").members();
    System.out.println(nameset);
}

刪除Set中某一個值

@Test
public void deleteValueBySet(){
    redisTemplate.boundSetOps("nameset").remove("塗陌");
}

刪除整個Set

@Test
public void deleteAllValueByset(){
    redisTemplate.delete("nameset");
}

List型別

右壓棧

右壓棧,後新增的物件排在後邊

@Test
public void setRightValueByList(){
  redisTemplate.boundListOps("namelist").rightPush("tycoding");
  redisTemplate.boundListOps("namelist").rightPush("塗陌");
}

顯示右壓棧集合

@Test
public void getRightValueByListI(){
    List namelist = redisTemplate.boundListOps("namelist").range(0, 10);
    System.out.println(namelist);
}

左壓棧

左壓棧,後新增的物件排在前面

    @Test
    public void setLeftValueByList(){
        redisTemplate.boundListOps("namelist2").leftPush("tycoding");
        redisTemplate.boundListOps("namelist2").leftPush("塗陌");
    }

顯示左壓棧的集合:

    @Test
    public void getLeftValueByList(){
        List name2 = redisTemplate.boundListOps("namelist2").range(0, 10);
        System.out.println(name2);
    }

根據索引查詢集合中的元素

    @Test
    public void searchByIndex(){
        Object namelist = redisTemplate.boundListOps("namelist").index(1);
        System.out.println(namelist);
    }

Hash型別

新增

    @Test
    public void setValueByHash(){
        redisTemplate.boundHashOps("namehash").put("a","tycoding");
    }

提取所有的KEY

    @Test
    public void getKeysByHash(){
        Set namehash = redisTemplate.boundHashOps("namehash").keys();
        System.out.println(namehash);
    }

提取所有的VALUE

    @Test
    public void getValuesByHash(){
        List namehash = redisTemplate.boundHashOps("namehash").values();
        System.out.println(namehash);
    }

根據KEY取值

    @Test
    public void getValueByHash(){
        Object o = redisTemplate.boundHashOps("namehash").get("a");
        System.out.println(o);
    }

根據KEY移除值

    @Test
    public void deleteValueByHash(){
        redisTemplate.boundHashOps("namehash").delete("a");
    }

<br/>

測試

上面說了一大堆,沒有實際的測試,著實不清楚Redis究竟效果如何,是不是真的提高了訪問速度?

下面我們以查詢資料庫所有值的功能來看一下使用Redis快取和未使用快取直接查詢資料庫所用時間。

本例原始碼地址:Github

未使用Redis快取,直接請求資料庫

public List<Goods> findAll() {
        return goodsMapper.findAll();
}

使用了Redis快取

首先通過boundHashOps獲取Redis資料庫中是否存在KEY為all的資料,有的話就返回;沒有的話就查詢資料庫並將查詢到的資料新增到Redis資料庫中,且KEY為all

public List<Goods> findAll() {
    List<Goods> contentList = (List<Goods>) redisTemplate.boundHashOps("goods").get("all");
    if (contentList == null) {
        //說明快取中沒有資料
        System.out.println("從資料庫中讀取資料放入redis...");
        contentList = goodsMapper.findAll();
        redisTemplate.boundHashOps("goods").put("all", contentList); //存入redis中
    } else {
        System.out.println("從快取中讀取資料...");
    }

//        return goodsMapper.findAll();
    return contentList;
}

TestTime.java

@Test
public void run1() {
    Long startTime = System.currentTimeMillis(); //開始時間
    goodsMapper.findAll();
    Long endTime = System.currentTimeMillis(); //結束時間
    System.out.println("查詢資料庫--共耗時:" + (endTime - startTime) + "毫秒"); //1007毫秒
}

@Test
public void run2() {
    Long startTime = System.currentTimeMillis(); //開始時間
    goodsService.findAll();
    Long endTime = System.currentTimeMillis(); //結束時間
    System.out.println("從redis中讀取所有資料,共耗時:" + (endTime - startTime) + "毫秒");
}

在測試類中呼叫Service層的這兩個方法,得到的結果如下:

查詢資料庫--共耗時:1047毫秒

從redis中讀取所有資料,共耗時:197毫秒

<br/>

交流

如果大家有興趣,歡迎大家加入我的Java交流技術群:671017003 ,一起交流學習Java技術。博主目前一直在自學JAVA中,技術有限,如果可以,會盡力給大家提供一些幫助,或是一些學習方法,當然群裡的大佬都會積極給新手答疑的。所以,別猶豫,快來加入我們吧!

<br/>

聯絡

If you have some questions after you see this article, you can contact me or you can find some info by clicking these links.