跟我一起數據挖掘(21)——redis

什麼是Redis

Redis是一個開源的使用ANSI C語言編寫、支持網絡、可基於內存亦可持久化的日誌型、Key-Value數據庫,並提供多種語言的API。從2010年3月15日起,Redis的開發工作由VMware主持。從2013年5月開始,Redis的開發由Pivotal贊助。

Memcached和Redis

Memcached的基本應用模型如下圖所示:

image

redis使用與其相同,將memcached改爲redis即可。

Redis支持的數據類型

字符串(Strings)

字符串是Redis值的最基礎的類型。Redis字符串是二進制安全的,這意味着一個Redis字符串可以包含任何種類的數據,例如一個JPEG圖像或者一個序列化的Ruby對象。 一個字符串值最多可以保存512M字節的內容。 你可以使用Redis的字符串做一些有趣的事情,例如你可以:

  • 在使用命令INCR系列( INCR, DECR, INCRBY)命令時將字符串作爲的原子計數器。
  • 使用APPEND命令追加字符串。
  • 將字符串作爲GETRANGE 和 SETRANGE的隨機訪問向量。
  • 在小空間裏編碼大量數據, 或者使用 GETBIT 和 SETBIT創建一個Redis支持的Bloom過濾器。

列表(Lists)

Redis列表是簡單的字符串列表,按照插入順序排序。你可以添加一個元素導列表的頭部(左邊)或者尾部(右邊) LPUSH 命令插入一個新的元素導頭部, 而 RPUSH插入一個新元素導尾部.當一個這兩個操作在一個空的Key上被執行的時候一個新的列表被創建。相似的,如果一個列表操作清空一個列表那麼對應的key將被從key空間刪除。這是非常方便的語義,因爲他們被調用使用一個空列表完全就像他們被調用時使用一個不存在的鍵值(可以)做爲參數。 一些類表操作和結果的例子:

LPUSH mylist a   # 現在類表中是 "a"

LPUSH mylist b   # 現在列表中是 "b","a"

RPUSH mylist c   # 現在類表中是 "b","a","c" (這次RPUSH被使用)

一個列表最多可以包含 232 - 1 個元素 (4294967295, 每個列表超過40億個元素)。 從時間複雜度的角度來看Redis列表的主要特徵是在頭和尾的元素插入和刪除是固定時間,即便是數以百萬計的插入。. 在列表的兩端訪問元素是非常快的但是如果你試着訪問一個非常大的列表的中間的元素是很慢的,因爲那是一個O(N)操作。 你可以用Redis列表做很多有趣的事情,比如你可以:

  • 在一個社交網絡中建立一個時間線模型,使用LPUSH 去添加新的元素到用戶的時間線, 使用LRANGE去接收一些最近插入的元素。
  • 你可以將 LPUSH 和 LTRIM 一起用去創建一個永遠也不會超過指定元素數目的列表,但是記住是最後的N個元素。
  • 列表能夠被用來作爲消息傳遞primitive[譯註:不清楚表達的意思], 例如衆所周知的用來創建後臺工作的Resque Ruby庫.
  • 你可以使用列表做更多的事,這個數據類型支持許多命令,包括像BLPOP這樣的阻塞命令。

集合(Sets)

Redis 集合(Set)是一個無序的字符串集合. 你可以以O(1)的時間複雜度 (無論集合中有多少元素時間複雜度都是常量)完成添加,刪除,以及測試元素是否存在。 Redis 集合擁有令人滿意的不允許包含相同成員的屬性。多次添加相同的元素,最終在集合裏只會有一個元素。 實際上說這些就是意味着在添加元素的時候無須檢測元素是否存在。 一個Redis集合的非常有趣的事情是他支持一些服務端的命令從現有的集合出發去進行集合運算,因此你可以在非常短的時間內進行合併(unions), 求交集(intersections),找出不同的元素(differences of sets)。 一個集合最多可以包含 232 - 1 個元素(4294967295, 每個集合超過40一個元素). 你可以使用集合多很多有趣的事情,比如你能夠:

  • 你可以使用集合追蹤一件(獨一無二的)事情,想要知道所有訪問一個博客文章的獨立IP? 每次當你處理一個頁面訪問的事簡單的使用SADD。你可以肯定重複的IP是不會被插入的。
  • Redis 集合是很擅長表現關係的。你可以使用Redis集合創建一個tagging系統去表現每一個tag。接下來你能夠使用SADD命令將有一個給定tag的所有對象的所有ID添加到一個用來展現這個特定tag的集合裏。你想要同時有三個不同tag的所有對象的ID嗎?使用SINTER就好了。
  • 使用 SPOP 或者 SRANDMEMBER 命令你可以使用集合去隨意的抽取元素。

哈希(Hashes)

Redis Hashes是字符串字段和字符串值之間的映射,因此他們是展現對象的完美數據類型。 (例如:一個有名,姓,年齡等等屬性的用戶):

@cli

HMSET user:1000 username antirez password P1pp0 age 34

HGETALL user:1000

HSET user:1000 password 12345

HGETALL user:1000

一個帶有一些字段(這裏的一些意味着高達一百左右)的hash僅僅需要一塊很小的空間存儲,因此你可以存儲數以百萬計的對象在一個小的Redis實例中。 哈希主要用來表現對象,他們有能力存儲很多對象,因此你可以將哈希用於許多其他的任務。 每一個哈希可以存儲超過232 - 1 字段-值 對 (超過40億).

有序集合(Sorted Sets)

Redis有序集合與普通集合非常相似,是一個沒有重複元素的字符串集合。不同之處是有序集合的沒有成員都關聯了一個評分,這個評分被用來按照從最低分到最高分的方式排序集合中的成員。集合的成員是唯一的,但是評分可以是重複了。 使用有序集合你可以以非常快的速度(O(log(N)))添加,刪除和更新元素。因爲元素是有序的, 所以你也可以很快的根據評分(score)或者次序(position)來獲取一個範圍的元素。訪問有序集合的中間元素也是非常快的,因此你能夠使用有序集合作爲一個沒有重複成員的智能列表。在有序集合中,你可以很快捷的訪問一切你需要的東西:有序的元素,快速的存在性測試,快速訪問集合的中間元素! 簡而言之使用有序集合你可以做完成許多對性能有極端要求的任務,而那些任務使用其他類型的數據庫真的是很難完成的。 使用有序集合你可以:

  • 在一個大型的在線遊戲中展示一個排行榜,在那裏一旦一個新的分數被提交,你可以使用ZADD命令去更新它.你也可用使用 ZRANGE命令來得到頂級的用戶,你還可以使用ZRANK命令根據用戶名返回該用戶在排行榜中的位次。同時使用ZRANK 和 ZRANGE 你可以顯示和給定用戶分數相同的所有用戶。所有這些操作都非常的快速。
  • 有序集合常常被用來索引存儲在Redis中的數據。舉個例子,如果你有許多的哈希(Hashes)來代表用戶,你可以使用一個有序集合,這個集合中的元素的年齡字段被用來當做評分,而ID作爲值。因此,使用 ZRANGEBYSCORE 命令,那是微不足道的並且能夠很快的接收到給定年齡段的所有用戶。

centos安裝redis

wget http://download.redis.io/redis-stable.tar.gz

tar xvzf redis-stable.tar.gz

cd redis-stable

make

前面3步應該沒有問題,主要的問題是執行make的時候,出現了異常。

異常一:

make[2]: cc: Command not found

異常原因:沒有安裝gcc

解決方案:yum install gcc-c++

異常二:

zmalloc.h:51:31: error: jemalloc/jemalloc.h: No such file or directory

異常原因:一些編譯依賴或原來編譯遺留出現的問題

解決方案:make distclean。清理一下,然後再make。

在make成功以後,需要make test。在make test出現異常。

異常一:

couldn't execute "tclsh8.5": no such file or directory

異常原因:沒有安裝tcl

解決方案:yum install -y tcl。

在make成功以後,會在src目錄下多出一些可執行文件:redis-server,redis-cli等等。

方便期間用cp命令複製到usr目錄下運行。

cp redis-server /usr/local/bin/

cp redis-cli /usr/local/bin/

然後新建目錄,存放配置文件

mkdir /etc/redis

mkdir /var/redis

mkdir /var/redis/log

mkdir /var/redis/run

mkdir /var/redis/6379

在redis解壓根目錄中找到配置文件模板,複製到如下位置。

cp redis.conf /etc/redis/6379.conf

通過vim命令修改

daemonize yes

pidfile /var/redis/run/redis_6379.pid

logfile /var/redis/log/redis_6379.log

dir /var/redis/6379

最後運行redis:

$ redis-server /etc/redis/6379.conf

使用Jedis操作Redis

使用Java操作Redis需要jedis-2.1.0.jar,下載地址:http://files.cnblogs.com/liuling/jedis-2.1.0.jar.zip

如果需要使用Redis連接池的話,還需commons-pool-1.5.4.jar,下載地址:http://files.cnblogs.com/liuling/commons-pool-1.5.4.jar.zip

也可以使用maven引入:

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.0.0</version>
    <type>jar</type>
    <scope>compile</scope>
</dependency>
package com.test;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.junit.Before;
import org.junit.Test;

import redis.clients.jedis.Jedis;

public class TestRedis {
    private Jedis jedis; 
    
    @Before
    public void setup() {
        //連接redis服務器,192.168.0.100:6379
        jedis = new Jedis("192.168.0.100", 6379);
        //權限認證
        jedis.auth("admin");  
    }
    
    /**
     * redis存儲字符串
     */
    @Test
    public void testString() {
        //-----添加數據----------  
        jedis.set("name","xinxin");//向key-->name中放入了value-->xinxin  
        System.out.println(jedis.get("name"));//執行結果:xinxin  
        
        jedis.append("name", " is my lover"); //拼接
        System.out.println(jedis.get("name")); 
        
        jedis.del("name");  //刪除某個鍵
        System.out.println(jedis.get("name"));
        //設置多個鍵值對
        jedis.mset("name","liuling","age","23","qq","476777389");
        jedis.incr("age"); //進行加1操作
        System.out.println(jedis.get("name") + "-" + jedis.get("age") + "-" + jedis.get("qq"));
        
    }
    
    /**
     * redis操作Map
     */
    @Test
    public void testMap() {
        //-----添加數據----------  
        Map<String, String> map = new HashMap<String, String>();
        map.put("name", "xinxin");
        map.put("age", "22");
        map.put("qq", "123456");
        jedis.hmset("user",map);
        //取出user中的name,執行結果:[minxr]-->注意結果是一個泛型的List  
        //第一個參數是存入redis中map對象的key,後面跟的是放入map中的對象的key,後面的key可以跟多個,是可變參數  
        List<String> rsmap = jedis.hmget("user", "name", "age", "qq");
        System.out.println(rsmap);  
  
        //刪除map中的某個鍵值  
        jedis.hdel("user","age");
        System.out.println(jedis.hmget("user", "age")); //因爲刪除了,所以返回的是null  
        System.out.println(jedis.hlen("user")); //返回key爲user的鍵中存放的值的個數2 
        System.out.println(jedis.exists("user"));//是否存在key爲user的記錄 返回true  
        System.out.println(jedis.hkeys("user"));//返回map對象中的所有key  
        System.out.println(jedis.hvals("user"));//返回map對象中的所有value 
  
        Iterator<String> iter=jedis.hkeys("user").iterator();  
        while (iter.hasNext()){  
            String key = iter.next();  
            System.out.println(key+":"+jedis.hmget("user",key));  
        }  
    }
    
    /** 
     * jedis操作List 
     */  
    @Test  
    public void testList(){  
        //開始前,先移除所有的內容  
        jedis.del("java framework");  
        System.out.println(jedis.lrange("java framework",0,-1));  
        //先向key java framework中存放三條數據  
        jedis.lpush("java framework","spring");  
        jedis.lpush("java framework","struts");  
        jedis.lpush("java framework","hibernate");  
        //再取出所有數據jedis.lrange是按範圍取出,  
        // 第一個是key,第二個是起始位置,第三個是結束位置,jedis.llen獲取長度 -1表示取得所有  
        System.out.println(jedis.lrange("java framework",0,-1));  
        
        jedis.del("java framework");
        jedis.rpush("java framework","spring");  
        jedis.rpush("java framework","struts");  
        jedis.rpush("java framework","hibernate"); 
        System.out.println(jedis.lrange("java framework",0,-1));
    }  
    
    /** 
     * jedis操作Set 
     */  
    @Test  
    public void testSet(){  
        //添加  
        jedis.sadd("user","liuling");  
        jedis.sadd("user","xinxin");  
        jedis.sadd("user","ling");  
        jedis.sadd("user","zhangxinxin");
        jedis.sadd("user","who");  
        //移除noname  
        jedis.srem("user","who");  
        System.out.println(jedis.smembers("user"));//獲取所有加入的value  
        System.out.println(jedis.sismember("user", "who"));//判斷 who 是否是user集合的元素  
        System.out.println(jedis.srandmember("user"));  
        System.out.println(jedis.scard("user"));//返回集合的元素個數  
    }  
  
    @Test  
    public void test() throws InterruptedException {  
        //jedis 排序  
        //注意,此處的rpush和lpush是List的操作。是一個雙向鏈表(但從表現來看的)  
        jedis.del("a");//先清除數據,再加入數據進行測試  
        jedis.rpush("a", "1");  
        jedis.lpush("a","6");  
        jedis.lpush("a","3");  
        jedis.lpush("a","9");  
        System.out.println(jedis.lrange("a",0,-1));// [9, 3, 6, 1]  
        System.out.println(jedis.sort("a")); //[1, 3, 6, 9]  //輸入排序後結果  
        System.out.println(jedis.lrange("a",0,-1));  
    }  
    
    @Test
    public void testRedisPool() {
        RedisUtil.getJedis().set("newname", "中文測試");
        System.out.println(RedisUtil.getJedis().get("newname"));
    }
}

Redis連接池:

package com.test;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

public final class RedisUtil {
    
    //Redis服務器IP
    private static String ADDR = "192.168.0.100";
    
    //Redis的端口號
    private static int PORT = 6379;
    
    //訪問密碼
    private static String AUTH = "admin";
    
    //可用連接實例的最大數目,默認值爲8;
    //如果賦值爲-1,則表示不限制;如果pool已經分配了maxActive個jedis實例,則此時pool的狀態爲exhausted(耗盡)。
    private static int MAX_ACTIVE = 1024;
    
    //控制一個pool最多有多少個狀態爲idle(空閒的)的jedis實例,默認值也是8。
    private static int MAX_IDLE = 200;
    
    //等待可用連接的最大時間,單位毫秒,默認值爲-1,表示永不超時。如果超過等待時間,則直接拋出JedisConnectionException;
    private static int MAX_WAIT = 10000;
    
    private static int TIMEOUT = 10000;
    
    //在borrow一個jedis實例時,是否提前進行validate操作;如果爲true,則得到的jedis實例均是可用的;
    private static boolean TEST_ON_BORROW = true;
    
    private static JedisPool jedisPool = null;
    
    /**
     * 初始化Redis連接池
     */
    static {
        try {
            JedisPoolConfig config = new JedisPoolConfig();
            config.setMaxActive(MAX_ACTIVE);
            config.setMaxIdle(MAX_IDLE);
            config.setMaxWait(MAX_WAIT);
            config.setTestOnBorrow(TEST_ON_BORROW);
            jedisPool = new JedisPool(config, ADDR, PORT, TIMEOUT, AUTH);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    /**
     * 獲取Jedis實例
     * @return
     */
    public synchronized static Jedis getJedis() {
        try {
            if (jedisPool != null) {
                Jedis resource = jedisPool.getResource();
                return resource;
            } else {
                return null;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    
    /**
     * 釋放jedis資源
     * @param jedis
     */
    public static void returnResource(final Jedis jedis) {
        if (jedis != null) {
            jedisPool.returnResource(jedis);
        }
    }
}

總結

在使用緩存的時候,redis比memcached具有更多的優勢,並且支持更多的數據類型。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章