当Java遇到Redis:Jedis实战入门

Redis是一个开源,高级的键值存储和一个适用的解决方案,用于构建高性能,可扩展的Web应用程序。本文将概要介绍Redis的特性和语法,并以实例代码的形式介绍如何通过Jedis在java语言环境下控制Redis,帮助各位读者快速入门。

NoSQL概述

Redis是NoSQL数据库的代表之一,那什么是NoSQL?

NoSQL = not only SQL,即非关系型数据库。

Why NoSQL?

和传统的关系型数据库相比,NoSQL具有以下的优势:

  • High Performance:高并发读写;
  • Huge Storage: 海量数据的高效存储和访问;
  • High Scalability & High Availability:高可扩展和高可用性

NoSQL的分类:

  • Key-Value,如Redis,快速查询,但是数据存储缺少结构化;
  • 列存储,如HBase, 扩展性强,查找速度快,但是功能局限;
  • 文档数据库,如MongoDB,数据格式灵活,查询性能不高,缺少统一的查询语法;
  • 图形数据库,如InfoGrid,基于图的算法,但是不容易做分布式的查询;

Redis概述

Redis是一个开源,高性能的键值对数据库, 其优点包括:

  • 异常快 :Redis非常快,每秒可执行大约110000次的设置(SET)操作,每秒大约可执行81000次的读取/获取(GET)操作。
  • 支持丰富的数据类型 :Redis支持开发人员常用的大多数数据类型,例如列表集合排序集散列等等。这使得Redis很容易被用来解决各种问题,因为我们知道哪些问题可以更好使用地哪些数据类型来处理解决。
  • 操作具有原子性 : 所有Redis操作都是原子操作,这确保如果两个客户端并发访问,Redis服务器能接收更新的值。
  • 多实用工具 : Redis是一个多实用工具,可用于多种用例,如:
    1. 缓存
    2. 任务队列
    3. 网站统计;
    4. 数据过期处理
    5. 应用排行榜;
    6. 分布式集群的Session分离

Redis与其他键值存储系统

  • Redis是键值数据库系统的不同进化路线,它的值可以包含更复杂的数据类型,可在这些数据类型上定义原子操作。

  • Redis是一个内存数据库,但在磁盘数据库上是持久的,因此它代表了一个不同的权衡,在这种情况下,在不能大于存储器(内存)的数据集的限制下实现非常高的写和读速度。

  • 内存数据库的另一个优点是,它与磁盘上的相同数据结构相比,复杂数据结构在内存中存储表示更容易操作。 因此,Redis可以做很少的内部复杂性。

Redis的安装和使用

Redis的安装是否简单,在Ubuntu上安装Redis,打开终端并键入以下命令即可

sudo apt-get update 
sudo apt-get install redis-server

启动服务器:redis-server
启动客户端:redis-cli
如果是Windows环境下,则参照以下文章:
http://download.csdn.net/download/fengxinyixiao/9860813
http://blog.csdn.net/joyhen/article/details/47358999

Jedis入门

Jedis是Redis官网首选的Java客户端开发包.


Jedis是Redis官网首选的Java客户端开发包

其GItHub地址为:
https://github.com/xetorthio/jedis

在Maven中,添加如下依赖即可使用:

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
    <type>jar</type>
    <scope>compile</scope>
</dependency>

以下是官网是上给出的示例代码,连接本地Redis,进行操作.

    @Test
    public void ConnectionTest(){
        //1. Connecting to Redis server on localhost
        Jedis jedis = new Jedis("localhost");
        System.out.println("Connection to server sucessfully");
        //2. set the data in redis string
        jedis.set("username", "Roxin");
        //3. Get the stored data and print it
        System.out.println("Stored string in redis:: "+ jedis.get("username"));
        //4. Close the Redis connection;
        jedis.close();
    }

其中通过调用set方法来设置键值对,通过get方法获取键值对。

除此之外,Jedis还提供连接池的方式控制连接

    @Test
    public void ConnectionPoolTest(){
        //连接池设定
        JedisPoolConfig config = new JedisPoolConfig();
        //设定最大连接数
        config.setMaxTotal(30);
        //设置最大空闲连接数
        config.setMaxIdle(10);
        //创建连接池
        JedisPool jedisPool = new JedisPool(config, "127.0.0.1");
        //获得服务资源
        Jedis jedis = jedisPool.getResource();
        jedis.select(1);
        jedis.set("username", "Roxin By Jedis Pool");
        System.out.println(jedis.get("username"));
        jedis.close();
        jedisPool.close();
    }

Redis的数据类型

Redis中的数据类型有:

  • 字符串(String)
  • 列表(list)
  • 有序集合(sorted set)
  • 散列(hash)
  • 集合(set)

无论哪种数据类型都需要为其设定键值Key,设置Key的注意点:

  1. 不要太长(<1024字节);
  2. 不要太短,有可读性;
  3. 统一命名规范;

String

Redis中的字符串是一个字节序列。Redis中的字符串是二进制安全的,这意味着它们的长度不由任何特殊的终止字符决定。因此,可以在一个字符串中存储高达512兆字节的任何内容。

除了上面提到的set/get方法,还有其他命令如下表:

序号 命令 说明
1 SET key value 此命令设置指定键的值。
2 GET key 获取指定键的值。
3 GETRANGE key start end 获取存储在键上的字符串的子字符串。
4 GETSET key value 设置键的字符串值并返回其旧值。
5 GETBIT key offset 返回在键处存储的字符串值中偏移处的位值
6 MGET key1 [key2..] 获取所有给定键的值
7 SETBIT key offset value 存储在键上的字符串值中设置或清除偏移处的位
8 SETEX key seconds value 使用键和到期时间来设置值
9 SETNX key value 设置键的值,仅当键不存在时
10 SETRANGE key offset value 在指定偏移处开始的键处覆盖字符串的一部分
11 STRLEN key 获取存储在键中的值的长度
12 MSET key value [key value …] 为多个键分别设置它们的值
13 MSETNX key value [key value …] 为多个键分别设置它们的值,仅当键不存在时
14 PSETEX key milliseconds value 设置键的值和到期时间(以毫秒为单位)
15 INCR key 将键的整数值增加1
16 INCRBY key increment 将键的整数值按给定的数值增加
17 INCRBYFLOAT key increment 将键的浮点值按给定的数值增加
18 DECR key 将键的整数值减1
19 DECRBY key decrement 按给定数值减少键的整数值
20 APPEND key value 将指定值附加到键

Hash

相当于Map,在Redis中,每个哈希(散列)可以存储多达4亿个键-值对。

Jedis示例代码如下

    @Test
    public void HashTest(){
        Jedis jedis = jedisPool.getResource();

        String hashKey = "hashKey";
        //hset设置一个键值对
        jedis.hset(hashKey,"user","Roxin");
        HashMap<String, String> map = new HashMap<>();
        for (int i = 0; i < 10; i++) {
            map.put("field"+i,"value"+i);
        }
        //设置多个键值对
        jedis.hmset(hashKey,map);
        //获得键值对的个数
        Long hlen = jedis.hlen(hashKey);
        System.out.println("Hash Size in redis:: "+hlen);
        assert hlen==11;
        //得到全部键值对
        List<String> user = jedis.hmget(hashKey, "user");
        System.out.println("Stored string in redis:: "+ user);
        assert user.get(0).equals("Roxin");

        //删除键值
        jedis.del(hashKey);
        jedis.close();
    }

Redis关于Hash类型的常见命令如下表:

序号 命令 说明
1 HDEL key field2 [field2] 删除一个或多个哈希字段。
2 HEXISTS key field 判断是否存在散列字段。
3 HGET key field 获取存储在指定键的哈希字段的值。
4 HGETALL key 获取存储在指定键的哈希中的所有字段和值
5 HINCRBY key field increment 将哈希字段的整数值按给定数字增加
6 HINCRBYFLOAT key field increment 将哈希字段的浮点值按给定数值增加
7 HKEYS key 获取哈希中的所有字段
8 HLEN key 获取散列中的字段数量
9 HMGET key field1 [field2] 获取所有给定哈希字段的值
10 HMSET key field1 value1 [field2 value2 ] 为多个哈希字段分别设置它们的值
11 HSET key field value 设置散列字段的字符串值
12 HSETNX key field value 仅当字段不存在时,才设置散列字段的值
13 HVALS key 获取哈希中的所有值

列表list

Redis列表只是字符串列表,按插入顺序排序。可以在列表的头部或尾部添加Redis列表中的元素。

列表的最大长度为2^32 - 1个元素(即4294967295,每个列表可存储超过40亿个元素)。

Jedis中示例代码如下

   @Test
    public void ListTest(){
        Jedis jedis = jedisPool.getResource();
        String listKey = "LISTKEY";
        for (int i = 0; i < 10; i++) {
            //从头插入一个元素
            jedis.lpush(listKey,"L-value");
        }

        List<String> list = jedis.lrange(listKey, 0, -1);//从第一个到最后一个,负数代表倒数第几个
        assert list.size() == 10;

        assert "L-value".equals(jedis.rpop(listKey));//从尾部取出一个元素
        assert 9==jedis.llen(listKey);

        jedis.rpush(listKey,"R-valure");//从尾部加入一个元素
        jedis.lrem(listKey,2,"L-value");//删除从左数2两个"L-value"元素
        jedis.lrem(listKey,0,"L-value"); //0表示删除全部"L-value"元素
        assert "R-valure".equals(jedis.lpop(listKey));//从头部加入一个元素

        jedis.del(listKey);
        jedis.close();

    }

Redis中关于list的命令如下表:

序号 命令 说明
1 BLPOP key1 [key2 ] timeout 删除并获取列表中的第一个元素,或阻塞,直到有一个元素可用
2 BRPOP key1 [key2 ] timeout 删除并获取列表中的最后一个元素,或阻塞,直到有一个元素可用
3 BRPOPLPUSH source destination timeout 从列表中弹出值,将其推送到另一个列表并返回它; 或阻塞,直到一个可用
4 LINDEX key index 通过其索引从列表获取元素
5 LINSERT key BEFORE/AFTER pivot value 在列表中的另一个元素之前或之后插入元素
6 LLEN key 获取列表的长度
7 LPOP key 删除并获取列表中的第一个元素
8 LPUSH key value1 [value2] 将一个或多个值添加到列表
9 LPUSHX key value 仅当列表存在时,才向列表添加值
10 LRANGE key start stop 从列表中获取一系列元素
11 LREM key count value 从列表中删除元素
12 LSET key index value 通过索引在列表中设置元素的值
13 LTRIM key start stop 修剪列表的指定范围
14 RPOP key 删除并获取列表中的最后一个元素
15 RPOPLPUSH source destination 删除列表中的最后一个元素,将其附加到另一个列表并返回
16 RPUSH key value1 [value2] 将一个或多个值附加到列表
17 RPUSHX key value 仅当列表存在时才将值附加到列表

需要特别说明下:
rpoplpush source destination 删除列表中的最后一个元素,将其附加到另一个列表并返回,在消息队列中,可以用于消息备份:当消息被发布后,一个消息从主消息队列中被取出,被放入到缓存队列中,当确认发生成功之后,再将其彻底删除,如果发送不成功,就恢复该消息。

rpoplpush

Set

Redis集合是唯一字符串的无序集合。 唯一值表示集合中不允许键中有重复的数据。

在Redis中设置添加,删除和测试成员的存在(恒定时间O(1),而不考虑集合中包含的元素数量)。列表的最大长度为2^32 - 1个元素(即4294967295,每组集合超过40亿个元素)。

存储Set的使用场景:

  • 跟踪唯一性数据
  • 用于维护数据对象之间的关联关系;

Jedis的代码示例:

   @Test
    public void SetTest(){
        Jedis jedis = jedisPool.getResource();
        String setKey1 = "SETKEY-1";
        for (int i = 0; i < 10; i++) {
            //添加一个元素
            jedis.sadd(setKey1,"value-"+i);
        }

        assert 10 == jedis.scard(setKey1); //获得元素个数
        jedis.sadd(setKey1,"value-1");//添加重复的元素将失效
        assert 10 == jedis.scard(setKey1);
        
        
        String s= jedis.srandmember(setKey1);//随机获取一个元素
        assert jedis.sismember(setKey1,s);//是否为集合成员

        String setKey2 = "SETKEY-2";
        for (int i = 1; i < 11; i++) {
            jedis.sadd(setKey2,"value-"+i);
        }

        assert jedis.sdiff(setKey1,setKey2).size() == 1;//补集
        assert jedis.sinter(setKey1,setKey2).size() == 9;//交集
        assert jedis.sunion(setKey1,setKey2).size() == 11;//并集

        jedis.del(setKey1,setKey2);
        jedis.close();
    }

Redis中关于Set的命令如下表:

序号 命令 说明
1 SADD key member1 [member2] 将一个或多个成员添加到集合
2 SCARD key 获取集合中的成员数
3 SDIFF key1 [key2] 减去多个集合
4 SDIFFSTORE destination key1 [key2] 减去多个集并将结果集存储在键中
5 SINTER key1 [key2] 相交多个集合
6 SINTERSTORE destination key1 [key2] 交叉多个集合并将结果集存储在键中
7 SISMEMBER key member 判断确定给定值是否是集合的成员
8 SMOVE source destination member 将成员从一个集合移动到另一个集合
9 SPOP key 从集合中删除并返回随机成员
10 SRANDMEMBER key [count] 从集合中获取一个或多个随机成员
11 SREM key member1 [member2] 从集合中删除一个或多个成员
12 SUNION key1 [key2] 添加多个集合
13 SUNIONSTORE destination key1 [key2] 添加多个集并将结果集存储在键中
14 SSCAN key cursor [MATCH pattern] [COUNT count] 递增地迭代集合中的元素

Sorted-Set

Redis可排序集合类似于Redis集合,是不重复的字符集合。 不同之处在于,排序集合的每个成员都与分数相关联,这个分数用于按最小分数到最大分数来排序的排序集合。虽然成员是唯一的,但分数值可以重复。

Sorted-Set的使用场景:

  • 大型在线游戏的积分排名;
  • 构建索引数据;

Jedis中的示例:

   @Test
    public void SortedSetTest(){
        Jedis jedis = jedisPool.getResource();
        String sortedSetKey = "SORTEDSETKEY";

        for (int i = 0; i < 10; i++) {
            //添加一个元素
            jedis.zadd(sortedSetKey,i*10,"v-"+i);
        }
        assert 10 == jedis.zcard(sortedSetKey);//获得集合中元素个数

        assert 20 == (jedis.zscore(sortedSetKey,"v-2"));//获得集合中元素对应的分数

        Set<String> set = jedis.zrange(sortedSetKey, 0, -2);//从第一个到倒数第二个
        assert 9 == set.size() ;
        assert !set.contains("v-9");

        jedis.zincrby(sortedSetKey,20,"v-1");//让元素的分数增长20

        assert 30 == jedis.zscore(sortedSetKey,"v-1");
        assert 3 == jedis.zcount(sortedSetKey,20,30);//获得分数段中元素个数

        jedis.del(sortedSetKey);
        jedis.close();
    }

Keys的通用操作

Jedis中关于键值操作的实例:

    @Test
    public void KeyTest(){
        Jedis jedis = jedisPool.getResource();
        String key = "TESTKEY-1";
        String key2 = "TESTKEY-2";

        jedis.set(key2,"");//设置键值
        jedis.rename(key2,key);//键值重命名

        System.out.println("Key Type:"+jedis.type(key));//键值的类型

        assert jedis.exists(key);//键值是否存在
        jedis.expire(key,1);//设置键值过期时间
        assert 1 == jedis.ttl(key);//查看键值过期时间

        try {
            Thread.sleep(2000);//睡眠2s
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        assert !jedis.exists(key);//键值已过期,不存在
    }

Redis中关于键值的其他命令:

编号 命令 描述
1 DEL key 此命令删除一个指定键(如果存在)
2 DUMP key 此命令返回存储在指定键的值的序列化版本
3 EXISTS key 此命令检查键是否存在
4 EXPIRE key seconds 设置键在指定时间秒数之后到期/过期
5 EXPIREAT key timestamp 设置在指定时间戳之后键到期/过期。这里的时间是Unix时间戳格式
6 PEXPIRE key milliseconds 设置键的到期时间(以毫秒为单位)
7 PEXPIREAT key milliseconds-timestamp 以Unix时间戳形式来设置键的到期时间(以毫秒为单位)
8 KEYS pattern 查找与指定模式匹配的所有键
9 MOVE key db 将键移动到另一个数据库
10 PERSIST key 删除指定键的过期时间,得永生
11 PTTL key 获取键的剩余到期时间
12 RANDOMKEY 从Redis返回一个随机的键
13 RENAME key newkey 更改键的名称
14 PTTL key 获取键到期的剩余时间(以毫秒为单位)
15 RENAMENX key newkey 如果新键不存在,重命名键
16 TYPE key 返回存储在键中的值的数据类型

KEYS pattern 查找与指定模式匹配的所有键:

  • keys * 查看所有的key;
  • keys xx? 模糊匹配key;

Redis的特性

多数据

提供16个数据库(0-15),默认为0号数据库,可是通过select index选择。

事务

和关系型数据库一样,Redis也提供事务性操作:

  1. DISCARD  丢弃在MULTI之后发出的所有命令(放弃事务,回滚)
    
  2. EXEC 执行MULTI后发出的所有命令(提交事务)
  3. MULTI 标记事务块的开始(开始事务)
  4. UNWATCH 取消 WATCH 命令对所有 key 的监视。
  5. WATCH key [key …] 监视给定的键以确定MULTI / EXEC块的执行

Redis中示例代码如下:

   @Test
    public void TransactionTest(){
        //获得服务资源
        Jedis jedis = jedisPool.getResource();

        jedis.select(1);
        Transaction transaction = jedis.multi();//开启事务
        transaction.set("username", "Roxin in transaction1");
        System.out.println(transaction.get("username"));
        transaction.exec();//提交事务
        System.out.println(jedis.get("username"));

        transaction = jedis.multi();//开启事务
        transaction.set("username", "Roxin in transaction2");
        System.out.println(transaction.get("username"));
        transaction.discard();//撤销事务
        
        System.out.println(jedis.get("username"));
        jedis.close();
    }

Redis持久化

Redis是一个内存数据库,但在磁盘数据库上是持久化的,持久化的方式分为两种:

  • RDB:默认方式,定时将内存数据集快照写入磁盘;
  • AOF:以日志的方式,记录所有操作;

RDB

优势

  • 数据只存在一个文件中,便于数据归档和整理;
  • 多线程启动,性能好;

劣势

  • 如果在两次快照写操作之间出现问题,将无法回复期间的数据;
  • 多线程启动的时候,可能会有停顿;

配置文件为Redis按照目录下的 redis.conf:


redis.conf 保存频率的设施

这是配置文件设置内存快照写入磁盘的条件

#   In the example below the behaviour will be to save:
#   after 900 sec (15 min) if at least 1 key changed
#   after 300 sec (5 min) if at least 10 keys changed
#   after 60 sec if at least 10000 keys changed
save 900 1 
save 300 10
save 60 10000

数据文件被定义为dump.rdb,保存路径为Redis的按照路径。

# The filename where to dump the DB
dbfilename dump.rdb

# The working directory.
#
# The DB will be written inside this directory, with the filename specified
# above using the 'dbfilename' configuration directive.
#
# The Append Only File will also be created inside this directory.
#
# Note that you must specify a directory here, not a file name.
dir ./
Redis数据文件,只有一个数据文件

AOF

优势:

  1. 更高的数据安全性,有三种数据同步策略:

    • 每秒同步,效率高,但是在一秒间隔内断电,未同步的数据会丢失;
    • 每次修改同步,性能低,但是安全;
    • 不同步;
  2. 日志是追加(append)模式,即使有宕机,也不会用问题。 如果是数据写入一半出现问题,可以使用redis-check-aof,恢复数据一致;

  3. 日志自动重写,以防止日志过大;

  4. 日志格式清楚明了,可用于数据重建;

劣势:

  • 文件要更大;
  • 运行效率更低;

配置文件中关于AOF的配置:


关于AOF的配置

默认不使用;

日志文件为 appendonly.aof

# The name of the append only file (default: "appendonly.aof")
appendfilename "appendonly.aof"

上面提高的三种同步策略;

# no: don't fsync, just let the OS flush the data when it wants. Faster.
# always: fsync after every write to the append only log. Slow, Safest.
# everysec: fsync only one time every second. Compromise.
#
# The default is "everysec",If unsure, use "everysec".

# appendfsync always
appendfsync everysec
# appendfsync no
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349