1. 程式人生 > >redis和redis在java中的使用

redis和redis在java中的使用

Redis 簡介

REmote DIctionary Server(Redis) 是一個由Salvatore Sanfilippo寫的key-value儲存系統。Redis是一個開源的使用ANSI C語言編寫、遵守BSD協議、支援網路、可基於記憶體亦可持久化的日誌型、Key-Value資料庫,並提供多種語言的API。Redis 是完全開源免費的,遵守BSD協議,是一個高效能的key-value資料庫。
Redis 與其他 key - value 快取產品有以下三個特點:

  • Redis支援資料的持久化,可以將記憶體中的資料保持在磁碟中,重啟的時候可以再次載入進行使用。
  • Redis不僅僅支援簡單的key-value型別的資料,同時還提供list,set,zset,hash等資料結構的儲存。
  • Redis支援資料的備份,即master-slave模式的資料備份。

Redis 優勢

  • 效能極高–-> Redis能讀的速度是110000次/s,寫的速度是81000次/s 。
  • 豐富的資料型別–-> Redis支援二進位制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 資料型別操作。
  • 原子 –-> Redis的所有操作都是原子性的,同時Redis還支援對幾個操作全並後的原子性執行。
  • 豐富的特性–-> Redis還支援 publish/subscribe, 通知, key 過期等等特性。

Redis與其他key-value儲存有什麼不同

  • Redis有著更為複雜的資料結構並且提供對他們的原子性操作,這是一個不同於其他資料庫的進化路徑。Redis的資料型別都是基於基本資料結構的同時對程式設計師透明,無需進行額外的抽象。
  • Redis執行在記憶體中但是可以持久化到磁碟,所以在對不同資料集進行高速讀寫時需要權衡記憶體,應為資料量不能大於硬體記憶體。在記憶體資料庫方面的另一個優點是, 相比在磁碟上相同的複雜的資料結構,在記憶體中操作起來非常簡單,這樣Redis可以做很多內部複雜性很強的事情。 同時,在磁碟格式方面他們是緊湊的以追加的方式產生的,因為他們並不需要進行隨機訪問。

Redis下載

Redis版本號採用標準慣例:主版本號.副版本號.補丁級別,一個副版本號就標記為一個標準發行版本,例如1.2,2.0,2.2,2.4,2.6,2.8,奇數的副版本號用來表示非標準版本,例如2.9.x發行版本是Redis 3.0標準版本的非標準發行版本。

Redis 安裝(Linux)

下載編譯

$ wget http://download.redis.io/releases/redis-3.2.6.tar.gz
$ tar xzf redis-3.2.6.tar.gz
$ cd redis-3.2.6
$ make

這裡有的坑

  • 你的linux要裝gcc編譯器,不然會編譯失敗,centos下安裝命令為:yum install gcc-c++
  • 安裝後還編譯失敗,提示No such file or directory之類的,解決辦法,替換make命令如下:make MALLOC=libc

    官方解釋如下:

    Selecting a non-default memory allocator when building Redis is done by setting the MALLOC environment variable. Redis is compiled and linked against libc malloc by default, with the exception of jemalloc being the default on Linux systems. This default was picked because jemalloc has proven to have fewer fragmentation problems than libc malloc.
    To force compiling against libc malloc, use:
    % make MALLOC=libc

    To compile against jemalloc on Mac OS X systems, use:
    % make MALLOC=jemalloc

啟動會遇到的一些問題

編譯後生成src目錄.進入後執行$ ./redist-server

或者指定配置檔案$ ./redis-server redis.conf

則可以看到啟動日誌,這裡會有三個WARN:

$ 11980:M 08 Dec 11:30:51.347 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.

$ 11980:M 08 Dec 11:30:51.347 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add ‘vm.overcommit_memory = 1’ to /etc/sysctl.conf and then reboot or run the command ‘sysctl vm.overcommit_memory=1’ for this to take effect.

$ 11980:M 08 Dec 11:30:51.347 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command ‘echo never > /sys/kernel/mm/transparent_hugepage/enabled’ as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.

1、第一個提示somaxconn這個值為128太小了,這個值是系統的網路連線佇列大小,而redis的TCP backlog設定的值為511,因此受限,所以修改下系統的值

echo net.core.somaxconn = 20480 > /etc/sysctl.conf  
#最大佇列長度,應付突發的大併發連線請求,預設為128;
echo net.ipv4.tcp_max_syn_backlog = 20480 > /etc/sysctl.conf  
#半連線佇列長度,此值受限於記憶體大小,預設為1024;
sysctl -p  #使引數生效;

2、overcommit_memory設定為0,在低記憶體條件下可能會儲存失敗,修復方法如下

echo vm.overcommit_memory = 1 > /etc/sysctl.conf
sysctl -p  #使引數生效

vm.overcommit_memory不同的值說明:
- 0 表示檢查是否有足夠的記憶體可用,如果是,允許分配;如果記憶體不夠,拒絕該請求,並返回一個錯誤給應用程式。
- 1 允許分配超出實體記憶體加上交換記憶體的請求
- 2 核心總是返回true

redis的資料回寫機制分為兩種:
- 同步回寫即SAVE命令。redis主程序直接寫資料到磁碟。當資料量大時,這個命令將阻塞,響應時間長
- 非同步回寫即BGSAVE命令。redis 主程序fork一個子程序,複製主程序的記憶體並通過子程序回寫資料到磁碟。

由於RDB檔案寫的時候fork一個子程序。相當於複製了一個記憶體映象。當時系統的記憶體是4G,而redis佔用了近3G的記憶體,因此肯定會報記憶體無法分配。如果 「vm.overcommit_memory」設定為0,在可用記憶體不足的情況下,就無法分配新的記憶體。如果 「vm.overcommit_memory」設定為1。 那麼redis將使用交換記憶體。

3、關閉THP透明記憶體

Transparent Huge Pages (THP)`告警,這是一個關於透明記憶體巨頁的話題。簡單來說記憶體可管理的最小單位是page,一個page通常是4kb,那1M記憶體就會有256個page,CPU通過內建的記憶體管理單元管理page表記錄。Huge Pages就是表示page的大小已超過4kb了,一般是2M到1G,它的出現主要是為了管理超大記憶體。個人理解上TB的記憶體。而THP就是管理Huge Pages的一個抽象層次,根據一些資料顯示THP會導致記憶體鎖影響效能,所以一般建議關閉此功能。

/sys/kernel/mm/transparent_hugepage/enabled有三個值,如下:

$ cat /sys/kernel/mm/transparent_hugepage/enabled
always [madvise] never
####
# always 儘量使用透明記憶體,掃描記憶體,有512個 4k頁面可以整合,就整合成一個2M的頁面   
# never 關閉,不使用透明記憶體    
# madvise 避免改變記憶體佔用

修改方法:

$ vim /etc/rc.local  
$ echo never > /sys/kernel/mm/transparent_hugepage/enabled 
#在開機腳本里追加此命令,關閉,不使用透明記憶體

再啟動,則不報錯了,否則容易在使用ping命令時出現以下錯誤:

MISCONF Redis is configured to save RDB snapshots, but it is currently not able to persist on disk. Commands that may modify the data set are disabled, because this instance is configured to report errors during writes if RDB snapshotting fails (stop-writes-on-bgsave-error option). Please check the Redis logs for details about the RDB error.

這個錯誤資訊是Redis客戶端工具在儲存資料時候丟擲的異常資訊。網上很多建議config set stop-writes-on-bgsave-error no。這樣做其實不好,這僅僅讓程式忽略了這個異常,實際上資料還是會儲存到硬碟失敗由於Redis是daemon模式執行的,沒法看到詳細的日誌。修改配置檔案設定logfile引數為檔案(預設是stdout,建議以後安裝完畢就修改這個引數為檔案,不然會丟掉很多重要資訊),重啟Redis,檢視日誌,看到程式啟動時就有一行警告提示:

“WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add ‘vm.overcommit_memory = 1’ to /etc/sysctl.conf and then reboot or run the command ‘sysctl vm.overcommit_memory=1’ for this to take effect.”(警告:過量使用記憶體設定為0!在低記憶體環境下,後臺儲存可能失敗。為了修正這個問題,請在/etc/sysctl.conf 新增一項 ‘vm.overcommit_memory = 1’ ,然後重啟(或者執行命令’sysctl vm.overcommit_memory=1’ )使其生效。)

當時沒明白意思,就忽略了。再啟動Redis客戶端,程式儲存資料時繼續報MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on disk異常,再檢視Redis日誌,看到有這樣的錯誤提示Can’t save in background: fork: Cannot allocate memory,這個提示很明顯”Fork程序時記憶體不夠用了!”(還是記憶體的問題)。修改vm.overcommit_memory=1後問題解決。

  • 為什麼系統明明還剩2GB的記憶體,Redis會說記憶體不夠呢?

    簡單地說:Redis在儲存資料到硬碟時為了避免主程序假死,需要Fork一份主程序,然後在Fork程序內完成資料儲存到硬碟的操作,如果主程序使用了4GB的記憶體,Fork子程序的時候需要額外的4GB,此時記憶體就不夠了,Fork失敗,進而資料儲存硬碟也失敗了。

如上,安裝結束。使用redis-cli進行測試,然後執行如下命令,其中redis-cli是客戶端程式,命令是利用該程式與伺服器互動.具體指令形式:

$ ./redist-cli
#或者redis-cli -h host -p port -a password
#不加引數,表示預設連線本地.
#127.0.0.1 是本機 IP ,6379 是 redis 服務埠。現在我們輸入 PING 命令。
redis 127.0.0.1:6379> ping
PONG
#或者通過客戶端來關閉redis服務端
127.0.0.1:6379> shutdown

以上說明我們已經成功安裝了redis。

Redis 配置

Redis 的配置檔案位於 Redis 安裝目錄下,檔名為 redis.conf。你可以通過CONFIG命令檢視或設定配置項,

檢視配置

你可以直接開啟 redis.conf 檔案或使用CONFIG GET命令來檢視配置。

## CONFIG GET 命令基本語法:
redis 127.0.0.1:6379> CONFIG GET CONFIG_SETTING_NAME
## 示例
redis 127.0.0.1:6379> CONFIG GET loglevel
1) "loglevel"
2) "notice"
## 使用*號獲取所有配置項:
redis 127.0.0.1:6379> CONFIG GET *
  1) "dbfilename"
  2) "dump.rdb"
  3) "requirepass"
     ……(省略)
115) "bind"
116) ""

編輯配置

你可以直接修改 redis.conf 檔案或使用CONFIG set命令來修改配置。

## CONFIG SET 命令基本語法:
redis 127.0.0.1:6379> CONFIG SET CONFIG_SETTING_NAME NEW_CONFIG_VALUE
## 例項
redis 127.0.0.1:6379> CONFIG SET loglevel "notice"
OK
redis 127.0.0.1:6379> CONFIG GET loglevel
1) "loglevel"
2) "notice"

引數說明

redis.conf 配置項說明如下:

  • daemonize no:Redis預設不是以守護程序的方式執行,可以通過該配置項修改,使用yes啟用守護程序
  • pidfile /var/run/redis.pid:當Redis以守護程序方式執行時,Redis預設會把pid寫入/var/run/redis.pid檔案,可以通過pidfile指定
  • port 6379:指定Redis監聽埠,預設埠為6379,作者在自己的一篇博文中解釋了為什麼選用6379作為預設埠,因為6379在手機按鍵上MERZ對應的號碼,而MERZ取自義大利歌女Alessia Merz的名字
  • bind 127.0.0.1:繫結的主機地址
  • timeout 300:當客戶端閒置多長時間後關閉連線,如果指定為0,表示關閉該功能
  • loglevel verbose:指定日誌記錄級別,Redis總共支援四個級別:debug、verbose、notice、warning,預設為verbose
  • logfile stdout:日誌記錄方式,預設為標準輸出,如果配置Redis為守護程序方式執行,而這裡又配置為日誌記錄方式為標準輸出,則日誌將會發送給/dev/null
  • databases 16:設定資料庫的數量,預設資料庫為0,可以使用SELECT 命令在連線上指定資料庫id
  • save <seconds> <changes>:指定在多長時間內,有多少次更新操作,就將資料同步到資料檔案,可以多個條件配合

    save <seconds> <changes>
        Redis預設配置檔案中提供了三個條件:
        save 900 1
        save 300 10
        save 60 10000
        分別表示900秒(15分鐘)內有1個更改,300秒(5分鐘)內有10個更改以及60秒內有10000個更改。
    
  • rdbcompression yes:指定儲存至本地資料庫時是否壓縮資料,預設為yes,Redis採用LZF壓縮,如果為了節省CPU時間,可以關閉該選項,但會導致資料庫檔案變的巨大

  • dbfilename dump.rdb:指定本地資料庫檔名,預設值為dump.rdb
  • dir ./:指定本地資料庫存放目錄
  • slaveof <masterip> <masterport>:設定當本機為slav服務時,設定master服務的IP地址及埠,在Redis啟動時,它會自動從master進行資料同步
  • masterauth <master-password>:當master服務設定了密碼保護時,slav服務連線master的密碼
  • requirepass foobared:設定Redis連線密碼,如果配置了連線密碼,客戶端在連線Redis時需要通過AUTH 命令提供密碼,預設關閉
  • maxclients 128:設定同一時間最大客戶端連線數,預設無限制,Redis可以同時開啟的客戶端連線數為Redis程序可以開啟的最大檔案描述符數,如果設定 maxclients 0,表示不作限制。當客戶端連線數到達限制時,Redis會關閉新的連線並向客戶端返回max number of clients reached錯誤資訊
  • maxmemory <bytes>:指定Redis最大記憶體限制,Redis在啟動時會把資料載入到記憶體中,達到最大記憶體後,Redis會先嚐試清除已到期或即將到期的Key,當此方法處理 後,仍然到達最大記憶體設定,將無法再進行寫入操作,但仍然可以進行讀取操作。Redis新的vm機制,會把Key存放記憶體,Value會存放在swap區
  • appendonly no:指定是否在每次更新操作後進行日誌記錄,Redis在預設情況下是非同步的把資料寫入磁碟,如果不開啟,可能會在斷電時導致一段時間內的資料丟失。因為 redis本身同步資料檔案是按上面save條件來同步的,所以有的資料會在一段時間內只存在於記憶體中。預設為no
  • appendfilename appendonly.aof:指定更新日誌檔名,預設為appendonly.aof
  • appendfsync everysec:指定更新日誌條件,共有3個可選值:

    no:表示等作業系統進行資料快取同步到磁碟(快)
    always:表示每次更新操作後手動呼叫fsync()將資料寫到磁碟(慢,安全)
    everysec:表示每秒同步一次(折衷,預設值)
    appendfsync everysec
    
  • vm-enabled no:指定是否啟用虛擬記憶體機制,預設值為no,簡單的介紹一下,VM機制將資料分頁存放,由Redis將訪問量較少的頁即冷資料swap到磁碟上,訪問多的頁面由磁碟自動換出到記憶體中(在後面的文章我會仔細分析Redis的VM機制)

  • vm-swap-file /tmp/redis.swap:虛擬記憶體檔案路徑,預設值為/tmp/redis.swap,不可多個Redis例項共享
  • vm-max-memory 0:將所有大於vm-max-memory的資料存入虛擬記憶體,無論vm-max-memory設定多小,所有索引資料都是記憶體儲存的(Redis的索引資料 就是keys),也就是說,當vm-max-memory設定為0的時候,其實是所有value都存在於磁碟。預設值為0
  • vm-page-size 32:Redis swap檔案分成了很多的page,一個物件可以儲存在多個page上面,但一個page上不能被多個物件共享,vm-page-size是要根據儲存的 資料大小來設定的,作者建議如果儲存很多小物件,page大小最好設定為32或者64bytes;如果儲存很大大物件,則可以使用更大的page,如果不 確定,就使用預設值
  • vm-pages 134217728:設定swap檔案中的page數量,由於頁表(一種表示頁面空閒或使用的bitmap)是在放在記憶體中的,,在磁碟上每8個pages將消耗1byte的記憶體。
  • vm-max-threads 4:設定訪問swap檔案的執行緒數,最好不要超過機器的核數,如果設定為0,那麼所有對swap檔案的操作都是序列的,可能會造成比較長時間的延遲。預設值為4
  • glueoutputbuf yes:設定在向客戶端應答時,是否把較小的包合併為一個包傳送,預設為開啟
  • hash-max-zipmap-entries 64 hash-max-zipmap-value 512:指定在超過一定的數量或者最大的元素超過某一臨界值時,採用一種特殊的雜湊演算法
  • activerehashing yes:指定是否啟用重置雜湊,預設為開啟(後面在介紹Redis的雜湊演算法時具體介紹)
  • include /path/to/local.conf:指定包含其它的配置檔案,可以在同一主機上多個Redis例項之間使用同一份配置檔案,而同時各個例項又擁有自己的特定配置檔案

使用

Redis支援五種資料型別:string(字串),hash(雜湊),list(列表),set(集合)及zset(sorted set:有序集合)。

命令

String(字串)

string是redis最基本的型別,你可以理解成與Memcached一模一樣的型別,一個key對應一個value。string型別是二進位制安全的。意思是redis的string可以包含任何資料。比如jpg圖片或者序列化的物件 。string型別是Redis最基本的資料型別,一個鍵最大能儲存512MB。舉個栗子:

redis 127.0.0.1:6379> SET name "test"
OK
redis 127.0.0.1:6379> GET name
"test"

在以上例項中我們使用了 Redis 的 SETGET 命令。鍵為 name,對應的值為test。注意:一個鍵最大能儲存512MB。

Hash(雜湊表)

Redis hash 是一個鍵值對集合。Redis hash是一個string型別的field和value的對映表,hash特別適合用於儲存物件。舉個栗子:

redis 127.0.0.1:6379> HMSET user:1 username test password test points 200
OK
redis 127.0.0.1:6379> HGETALL user:1
1) "username"
2) "test"
3) "password"
4) "test"
5) "points"
6) "200"

以上例項中 hash 資料型別儲存了包含使用者指令碼資訊的使用者物件。例項中我們使用了 Redis HMSET, HGETALL命令,user:1為鍵值。每個 hash 可以儲存 232 - 1鍵值對(40多億)。

List(列表)

Redis 列表是簡單的字串列表,按照插入順序排序。你可以新增一個元素到列表的頭部(左邊)或者尾部(右邊)。

redis 127.0.0.1:6379> lpush test redis
(integer) 1
redis 127.0.0.1:6379> lpush test mongodb
(integer) 2
redis 127.0.0.1:6379> lpush test rabitmq
(integer) 3
redis 127.0.0.1:6379> lrange test 0 10
1) "rabitmq"
2) "mongodb"
3) "redis"

列表最多可儲存 元素 2^32 -1 (4294967295, 每個列表可儲存40多億)。

Set(集合)

Redis的Set是string型別的無序集合。集合是通過雜湊表實現的,所以新增,刪除,查詢的複雜度都是O(1)。

sadd 命令: 新增一個string元素到,key對應的set集合中,成功返回1,如果元素以及在集合中返回0,key對應的set不存在返回錯誤。例項如下:

redis 127.0.0.1:6379> sadd test redis
(integer) 1
redis 127.0.0.1:6379> sadd test mongodb
(integer) 1
redis 127.0.0.1:6379> sadd test rabitmq
(integer) 1
redis 127.0.0.1:6379> sadd test rabitmq
(integer) 0
redis 127.0.0.1:6379> smembers  test
1) "rabitmq"
2) "mongodb"
3) "redis"

注意:以上例項中 rabitmq 添加了兩次,但根據集合內元素的唯一性,第二次插入的元素將被忽略。集合中最大的成員數為 2^32 - 1(4294967295, 每個集合可儲存40多億個成員)。

zset(sorted set:有序集合)

Redis zset 和 set 一樣也是string型別元素的集合,且不允許重複的成員。不同的是每個元素都會關聯一個double型別的分數。redis正是通過分數來為集合中的成員進行從小到大的排序。zset的成員是唯一的,但分數(score)卻可以重複。

zadd 命令: 新增元素到集合,元素在集合中存在則更新對應score。例項如下:

redis 127.0.0.1:6379> zadd test 0 redis
(integer) 1
redis 127.0.0.1:6379> zadd test 0 mongodb
(integer) 1
redis 127.0.0.1:6379> zadd test 0 rabitmq
(integer) 1
redis 127.0.0.1:6379> zadd test 0 rabitmq
(integer) 0
redis 127.0.0.1:6379> ZRANGEBYSCORE test 0 1000
1) "redis"
2) "mongodb"
3) "rabitmq"

Java 使用 Redis

匯入jar包

  • 下載jar包手動匯入:去官網下載驅動包 jedis.jar,在程式中匯入使用,不懂請自行百度。
  • Maven:
    xml
    <!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
    <dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
    </dependency>

Java使用Jedis訪問Redis示例

在載入Jedis JAR包之後,我們可以直接使用新建一個Jedis例項的方法,來建立一個到Redis的連線,並進行操作。不過跟Mysql一樣,每次操作的時候,都建立連線,很耗費效能。解決方法就是從一個連線池中取出連線物件,用完還回去。使用連線池的方案還能解決很多同步性問題。

在Jedis中,管理Redis連線的類是JedisPool,以下是具體的樣例程式碼:

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class Test {
    private Jedis jedis;
    private JedisPool jedisPool;//連線池
    // 初始化
    Test(){
        // 建構函式中初始化jedisPool
        initialPool();
        try {
            if (jedisPool != null) {
                jedis = jedisPool.getResource();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
        if (jedis != null)
            jedis.close();
    }
        jedisPool.destroy();
    }
    /**
     * 初始化連線池
     */
    private void initialPool(){
        // 池基本配置
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(20); //可用連線例項的最大數目,預設值為8;
        config.setMaxIdle(5);  //控制一個pool最多有多少個狀態為idle(空閒的)的jedis例項,預設值也是8。
        config.setMaxWaitMillis(10000);  //等待可用連線的最大時間
        config.setTestOnBorrow(false); // 定時對執行緒池中空閒的連結進行validateObject校驗
        jedisPool = new JedisPool(config,"ip",6379); // redis預設 6379
    }
    public void testKey()
    {
        System.out.println("連線成功");
        //檢視服務是否執行
        System.out.println("伺服器正在執行: "+jedis.ping());
        System.out.println("清空資料:"+jedis.flushDB());
        System.out.println("判斷某個鍵是否存在:"+jedis.exists("username"));
        System.out.println("新增<'username','zzh'>的鍵值對:"+jedis.set("username", "zzh"));
        System.out.println("再次判斷是否存在:"+jedis.exists("username"));
        System.out.println("新增<'password','password'>的鍵值對:"+jedis.set("password", "password"));
        System.out.print("系統中所有的鍵如下:");
        Set<String> keys = jedis.keys("*");
        System.out.println(keys);
        System.out.println("刪除鍵password:"+jedis.del("password"));
        System.out.println("判斷鍵password是否存在:"+jedis.exists("password"));
        System.out.println("檢視鍵username所儲存的值的型別:"+jedis.type("username"));
    }
    /**
     *
     * @Title:        testString
     * @Description:  測試java 操作redis中的String
     * @author        henry
     * @Date          2016-7-16 上午11:08:38
     */
    public void testString(){
        System.out.println("======================start String==========================");
        // 清空資料
        System.out.println("清空庫中所有資料:"+jedis.flushDB());
        jedis.set("name", "henry"); //向key-->name中放入了value-->henry
        System.out.println("獲取name鍵值對的值:"+jedis.get("name"));
        System.out.println("執行對name鍵值對的值的拼接:"+jedis.append("name", " is my lover"));
        System.out.println("獲取拼接後的name值:"+jedis.get("name"));
        System.out.println("獲取name鍵值對的值:"+jedis.get("name"));
        System.out.println("刪除name鍵值對:"+jedis.del("name"));
        System.out.println("獲取name鍵值對的值:"+jedis.get("name"));
        //設定多個鍵值對
        System.out.println("增加多個鍵值對:"+jedis.mset("name","henry","age","22","sex","male"));
        System.out.println("獲取多個鍵值對:"+jedis.mget("name","age","sex"));
        System.out.println("修改age鍵值對的值:"+jedis.set("sex", "female"));
        System.out.println("age的值+1:"+jedis.incr("age"));
        System.out.println("age的值+5:"+jedis.incrBy("age",5));
        System.out.println("age的值減1:"+jedis.decr("age"));
        System.out.println("age的值減5:"+jedis.decrBy("age",5));
        System.out.println("獲取鍵值對的對應值:"+jedis.get("name") + "-" + jedis.get("age")+ "-" + jedis.get("sex"));
        System.out.println("刪除多個鍵值對:"+jedis.del(new String[]{"age","sex"}));
        System.out.println("返回 key 所儲存的字串值的長度:"+jedis.strlen("name"));
        // 清空資料
        System.out.println("清空庫中所有資料:"+jedis.flushDB());
        System.out.println("=============新增鍵值對時防止覆蓋原先值=============");
        System.out.println("原name值不存在,新增name:"+jedis.setnx("name", "henry"));
        System.out.println("當name值存在時,測試新增name:"+jedis.setnx("name", "henry"));
        System.out.println("=============超過有效期鍵值對被刪除=============");
        System.out.println("新增age,並指定過期時間為2秒"+jedis.setex("age", 2, "30"));
        //等待3秒
        try {
            Thread.sleep(3000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("3秒之後,獲取age對應的值:"+jedis.get("age"));
        System.out.println("===========獲取原值,更新為新值==========");
        System.out.println("獲取name舊值,並更新name新值:"+jedis.getSet("name", "hi henry"));
        System.out.println("獲取name新值:"+jedis.get("name"));
        System.out.println("======================end String==========================");
        System.out.println();
    }
    /**
     *
     * @Title:        testHash
     * @Description:  測試java 操作redis中的Hash
     * @author        henry
     * @Date          2016-7-16 下午2:31:00
     */
    public void testHash(){
        System.out.println("======================start Hash==========================");
        //清空資料
        System.out.println("清空庫中所有資料:"+jedis.flushDB());
        Map<String,String> map = new HashMap<>();
        map.put("name","henry");
        map.put("age","22");
        System.out.println("user中新增多個鍵值對:"+jedis.hmset("user",map));
        System.out.println("user中新增sex鍵值對:"+jedis.hset("user", "sex", "male"));
        System.out.println("user中的所有值:"+jedis.hgetAll("user"));
        System.out.println("user中的所有value值:"+jedis.hvals("user"));
        System.out.println("user中刪除sex鍵值對:"+jedis.hdel("user", "sex"));
        System.out.println("雜湊hash中鍵值對的個數:"+jedis.hlen("user"));
        System.out.println("判斷hash中是否存在sex:"+jedis.hexists("user","sex"));
        System.out.println("user中的所有值:"+jedis.hgetAll("user"));
        System.out.println("獲取hash中的值:"+jedis.hmget("user","name","age"));
        System.out.println("user整型鍵值的值增加10:"+jedis.hincrBy("user", "age", 10));
        System.out.println("user中的所有值:"+jedis.hgetAll("user"));
        System.out.println("user中的所有key:"+jedis.hkeys("user"));
        System.out.println("======================end Hash==========================");
        System.out.println();
    }
    /**
     * @Title:        testList
     * @Description:  測試java 操作redis中的List
     * @author        henry
     * @Date          2016-7-16 下午2:31:30
     */
    public void testList(){
        System.out.println("======================start List==========================");
        System.out.println("清空庫中所有資料:"+jedis.flushDB());
        System.out.println("list中新增鍵值對:"+jedis.lpush("list", "key0","key1","key2","key3","key4","key5"));
        System.out.println("從列表右端新增鍵值對:"+jedis.rpush("list", "key6"));
        System.out.println("查詢list中所有鍵值對:"+jedis.lrange("list", 0, -1));
        System.out.println("刪除list指定元素:"+jedis.lrem("list", 2, "key2"));
        System.out.println("查詢list中刪除後的所有鍵值對:"+jedis.lrange("list", 0, -1));
        System.out.println("刪除下標0-3區間之外的元素:"+jedis.ltrim("list", 0, 3));
        System.out.println("刪除指定區間之外元素後的list:"+jedis.lrange("list", 0, -1));
        System.out.println("出棧元素(左端):"+jedis.lpop("list"));
        System.out.println("出棧元素(右端):"+jedis.rpop("list"));
        System.out.println("元素出棧後-list:"+jedis.lrange("list", 0, -1));
        System.out.println("長度-list:"+jedis.llen("list"));
        System.out.println("獲取下標為2的元素:"+jedis.lindex("list", 2));
        System.out.println("修改下標2的內容:"+jedis.lset("list", 2, "changedKey"));
        System.out.println("===============================");
        jedis.lpush("sortedList", "3","6","2","0","7","4");
        System.out.println("sortedList排序前:"+jedis.lrange("sortedList", 0, -1));
        System.out.println(jedis.sort("sortedList"));
        System.out.println("sortedList排序後:"+jedis.lrange("sortedList", 0, -1));
        System.out.println("======================end List==========================");
        System.out.println();
    }
    /**
     * @Title:        testSet
     * @Description:  測試java 操作redis中的Set
     * @author        henry
     * @Date          2016-7-16 下午2:31:50
     */
    public void testSet(){
        System.out.println("======================start set==========================");
        System.out.println("清空庫中所有資料:"+jedis.flushDB());
        System.out.println("向sets中加入元素:"+jedis.sadd("sets", "key7"));
        System.out.println("向sets中批量加入元素:"+jedis.sadd("sets", "key1","key2","key3","key4","key5","key6"));
        System.out.println("查詢sets中的所有資料:"+jedis.smembers("sets"));
        System.out.println("刪除sets中指定的資料:"+jedis.srem("sets", "key7"));
        System.out.println("查詢sets中的所有資料:"+jedis.smembers("sets"));
        System.out.println("批量刪除sets中指定的資料:"+jedis.srem("sets", "key5","key6"));
        System.out.println("隨機的移除集合中的一個元素:"+jedis.spop("sets"));
        System.out.println("查詢sets中的所有資料:"+jedis.smembers("sets"));
        System.out.println("包含元素的個數:"+jedis.scard("sets"));
        System.out.println("key1是否在sets中:"+jedis.sismember("sets", "key1"));
        System.out.println("======================set計算==========================");
        System.out.println("向sets1中批量加入元素:"+jedis.sadd("sets1", "key4","key5","key6","key7","key8","key9"));
        System.out.println("查詢sets1中的所有資料:"+jedis.smembers("sets1"));
        System.out.println("sets和sets1交集:"+jedis.sinter("sets", "sets1"));
        System.out.println("sets和sets1並集:"+jedis.sunion("sets", "sets1"));
        System.out.println("sets和sets1差集:"+jedis.sdiff("sets", "sets1"));
        System.out.println("======================end set==========================");
        System.out.println();
    }
    /**
     * @Title:        testZset
     * @Description:  測試java 操作redis中的Zset
     * @author        henry
     * @Date          2016-7-16 下午2:32:21
     */
    public void testZset(){
        System.out.println("======================start zset==========================");
        System.out.println("清空庫中所有資料:"+jedis.flushDB());
        Map<String, Double> map = new HashMap<>();
        map.put("key2", 1.2);
        map.put("key3", 4.0);
        map.put("key4", 5.0);
        map.put("key5", 0.2);
        System.out.println(jedis.zadd("zset", 3,"key1"));
        System.out.println(jedis.zadd("zset",map));
        System.out.println("查詢zset中所有值:"+jedis.zrange("zset", 0, -1));
        System.out.println("zset中的所有元素:"+jedis.zrangeWithScores("zset", 0, -1));
        System.out.println("zset中key2的分值:"+jedis.zscore("zset", "key2"));
        System.out.println("zset中key2的排名:"+jedis.zrank("zset", "key2"));
        System.out.println("刪除zsets中的指定值:"+jedis.zrem("zset", "key2"));
        System.out.println("查詢zsets刪除元素後的所有值:"+jedis.zrange("zset", 0, -1));
        System.out.println("長度-zset:"+jedis.zcard("zset"));
        System.out.println("zset中分值在1-4之間的元素的個數:"+jedis.zcount("zset", 1, 4));
        System.out.println("key2的分值加上5:"+jedis.zincrby("zset", 5, "key2"));
        System.out.println("key3的分值加上4:"+jedis.zincrby("zset", 4, "key3"));
        System.out.println("zset中的所有元素:"+jedis.zrange("zset", 0, -1));
        System.out.println("======================end zset==========================");
        System.out.println();
    }
    public static void main(String[]args){
        Test test = new Test();
        test.testKey();
        test.testString();
        test.testHash();
        test.testList();
        test.testSet();
        test.testZset();
    }
}