当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客户端开发包.

《当Java遇到Redis:Jedis实战入门》 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方法,还有其他命令如下表:

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

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

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

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

《当Java遇到Redis:Jedis实战入门》 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的命令如下表:

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

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

《当Java遇到Redis:Jedis实战入门》 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 ./

《当Java遇到Redis:Jedis实战入门》 Redis数据文件,只有一个数据文件

AOF

优势:

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

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

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

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

劣势:

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

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

《当Java遇到Redis:Jedis实战入门》 关于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
    原文作者:登高且赋
    原文地址: https://www.jianshu.com/p/7b3cb7b1eac7
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞