Redis入门和Java利用jedis操作redis Redis入门和Java利用jedis操作redis

Redis介绍

Redis 是完全开源的,遵守 BSD 协议,是一个高性能的 key-value 数据库。

Redis 与其他 key - value 缓存产品有以下三个特点:

  • Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
  • Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
  • Redis支持数据的备份,即master-slave模式的数据备份。

优势:

  • 性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。
  • 丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
  • 原子 – Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
  • 丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性。

本文旨在介绍Redis数据库的使用,具体底层的实现逻辑还需要漫长的学习,推荐《Redis设计与实现》这本书,继续学习Redis的具体实现。

Redis的常用命令

首先搭建好Redis的环境后,在控制台输入redis-cli,便进入redis的操作会话中

PS C:Users12392> redis-cli
127.0.0.1:6379>

通过select关键字选择数据库

127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]>

这里选择到第一个数据库

redis常用键的命令

127.0.0.1:6379[1]> flushdb
OK
127.0.0.1:6379[1]> set "user" "hello"
OK
127.0.0.1:6379[1]> keys *
1) "user"
127.0.0.1:6379[1]> type "user"
string
127.0.0.1:6379[1]> set "user" "hello1" # 更改user的值为hello1
OK
127.0.0.1:6379[1]> get "user" # 获取user的值
"hello1"
127.0.0.1:6379[1]> del "user" #删除user
(integer) 1
127.0.0.1:6379[1]> keys * 
(empty list or set)
127.0.0.1:6379[1]> mset "user1" "hello1" "user2" "hello2" # 批量设置键值对
OK
127.0.0.1:6379[1]> keys * 
1) "user2"
2) "user1"
127.0.0.1:6379[1]> mget "user1" "user2" # 批量获取键值对
1) "hello1"
2) "hello2"
127.0.0.1:6379[1]> exists "user1" # 查看是否存在键
(integer) 1
127.0.0.1:6379[1]> expire "user1" 10 # 设置user1的过期时间为10秒
(integer) 1
127.0.0.1:6379[1]> keys *
1) "user2"
127.0.0.1:6379[1]> pexpire "user2" 1000 # 设置user2的过期时间为1000毫秒
(integer) 1
127.0.0.1:6379[1]> keys *
(empty list or set)
127.0.0.1:6379[1]> set "user" "hello"
OK
127.0.0.1:6379[1]> expire "user" 20
(integer) 1
127.0.0.1:6379[1]> persist "user" # 取消user的过期时间
(integer) 1
127.0.0.1:6379[1]> keys *
1) "user"

string类型

127.0.0.1:6379[1]> flushdb
OK
127.0.0.1:6379[1]> set "string" "0123456789"
OK
127.0.0.1:6379[1]> get "string"
"0123456789"
127.0.0.1:6379[1]> getrange "string" 0 5 # 取字符串从0开始到5的位置
"012345"
127.0.0.1:6379[1]> getrange "string" 0 -1 # 取整个字符串
"0123456789"
127.0.0.1:6379[1]> getset "string" "hello" # 获取键并设置值
"0123456789"
127.0.0.1:6379[1]> get "string"
"hello"
127.0.0.1:6379[1]> setnx "testkey" "testvalue" # 如果不存在当前的键,那么就插入,返回1成果,0失败
(integer) 1
127.0.0.1:6379[1]> setnx "string" "123" 
(integer) 0
127.0.0.1:6379[1]> setex "user" 100 "123" # 设置user的过期时间为100秒
OK
127.0.0.1:6379[1]> incr "user" # user增加1
(integer) 124
127.0.0.1:6379[1]> decr "user" #user减少1
(integer) 123
127.0.0.1:6379[1]> incrby "user" 20 # user增加20 
(integer) 143
127.0.0.1:6379[1]> decrby "user" 20 # user减少20
(integer) 123
127.0.0.1:6379[1]> append "string" "world" # 字符串尾部加上值
(integer) 10
127.0.0.1:6379[1]> get "string" 
"helloworld"
127.0.0.1:6379[1]> strlen "string" # 获取字符串长度
(integer) 10

hash类型

127.0.0.1:6379[1]> flushdb
OK
127.0.0.1:6379[1]> hset "hash" 1 1 # 设置hash键,对应的map中存入键值对1 1
(integer) 1
127.0.0.1:6379[1]> hget "hash" 1 # 获取hash中键1的值
"1"
127.0.0.1:6379[1]> hmset "hash" 2 2 3 3 # 批量设置hash中的键值对
OK
127.0.0.1:6379[1]> hmget "hash" 2 3 # 批量获取hash中的键值对
1) "2"
2) "3"
127.0.0.1:6379[1]> hgetall "hash" # 获取hash中全部的键值对
1) "1"
2) "1"
3) "2"
4) "2"
5) "3"
6) "3"
127.0.0.1:6379[1]> hexists "hash" 1 # hash中是否存在键1
(integer) 1
127.0.0.1:6379[1]> hexists "hash" 4
(integer) 0
127.0.0.1:6379[1]> hsetnx "hash" 1 1 # 如果hash中没有键1,就设置键值对1 1,返回1,否则返回0
(integer) 0
127.0.0.1:6379[1]> hsetnx "hash" 4 4
(integer) 1
127.0.0.1:6379[1]> hincrby "hash" 1 20 # 设置hash中键1的值增加20
(integer) 21
127.0.0.1:6379[1]> hdel "hash" 1
(integer) 1
127.0.0.1:6379[1]> hkeys "hash" # 仅获取全部的键
1) "2"
2) "3"
3) "4"
127.0.0.1:6379[1]> hvals "hash" #仅获取全部的值
1) "2"
2) "3"
3) "4"
127.0.0.1:6379[1]> hlen "hash" #获取hash的大小
(integer) 3

list类型

127.0.0.1:6379[1]> lpush "list" a b c # 左插入a b c
(integer) 3
127.0.0.1:6379[1]> rpush "list" x y z # 右插入x y z
(integer) 6
127.0.0.1:6379[1]> lrange "list" 0 -1 # 遍历列表
1) "c"
2) "b"
3) "a"
4) "x"
5) "y"
6) "z"
127.0.0.1:6379[1]> lpop "list" # 左弹出一个元素,并返回该元素
"c"
127.0.0.1:6379[1]> rpop "list" # 右弹出一个元素,并返回该元素
"z"
127.0.0.1:6379[1]> lrange "list" 0 -1
1) "b"
2) "a"
3) "x"
4) "y"
127.0.0.1:6379[1]> llen "list" # 列表长度
(integer) 4
127.0.0.1:6379[1]> lpush "list" b b b
(integer) 7
127.0.0.1:6379[1]> lrem "list" 2 b # 从左开始删除两个值为b的元素
(integer) 0
# Redis Lrem 根据参数 COUNT 的值,移除列表中与参数 VALUE 相等的元素。
# lrem key count value
# count 的值可以是以下几种:
# count > 0 : 从表头开始向表尾搜索,移除与 VALUE 相等的元素,数量为 COUNT 。
# count < 0 : 从表尾开始向表头搜索,移除与 VALUE 相等的元素,数量为 COUNT 的绝对值。
# count = 0 : 移除表中所有与 VALUE 相等的值。
127.0.0.1:6379[1]> lrange "list" 0 -1
1) "b"
2) "b"
3) "a"
4) "x"
5) "y"
127.0.0.1:6379[1]> lindex "list" 2 # 获取list中下标为2的值
"a"
127.0.0.1:6379[1]> lset "list" 2 n # 设置list中下标为2的元素值为n
OK
127.0.0.1:6379[1]> lrange "list" 0 -1
1) "b"
2) "b"
3) "n"
4) "x"
5) "y"
127.0.0.1:6379[1]> ltrim "list" 1 3 # 保留list中下标1到3的元素,其余删除
OK
127.0.0.1:6379[1]> lrange "list" 0 -1
1) "b"
2) "n"
3) "x"
127.0.0.1:6379[1]> linsert "list" before b n # 在list中的n前面加上b
(integer) 4
127.0.0.1:6379[1]> linsert "list" after x n # 在list中的n后面加上x
(integer) 5
127.0.0.1:6379[1]> lrange "list" 0 -1
1) "n"
2) "b"
3) "n"
4) "x"
5) "n"
127.0.0.1:6379[1]> rpoplpush "list" "newlist" # 源list右端弹出一个元素并插入newlist左端,并返回该元素
"n"
127.0.0.1:6379[1]> lrange "newlist" 0 -1
1) "n"

# 利用好list数据结构,可以构造出栈和队列等数据结构

set类型

127.0.0.1:6379[1]> sadd "set" 1 # 在set中添加一个元素1
(integer) 1
127.0.0.1:6379[1]> sadd "set" 2 3 4 5 #在set中批量添加元素
(integer) 4
127.0.0.1:6379[1]> smembers "set" # 获取set中所有元素
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
127.0.0.1:6379[1]> srem "set" 5 # 删除set中值为5的元素
(integer) 1
127.0.0.1:6379[1]> smembers "set" 
1) "1"
2) "2"
3) "3"
4) "4"
127.0.0.1:6379[1]> scard "set" # 获取set中元素个数
(integer) 4
127.0.0.1:6379[1]> sadd "set2" 3 4 5 6
(integer) 4
127.0.0.1:6379[1]> sdiff "set" "set2" # set和set2的差集
1) "1"
2) "2"
127.0.0.1:6379[1]> sinter "set" "set2" # set和set2的交集
1) "3"
2) "4"
127.0.0.1:6379[1]> sunion "set" "set2" # set和set2的并集
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
127.0.0.1:6379[1]> srandmember "set" # 随机获取一个set中的元素
"3"
127.0.0.1:6379[1]> srandmember "set" 2 # 随机获取多个set中的元素
1) "3"
2) "1"
127.0.0.1:6379[1]> spop "set" # 随机删除一个set中元素
"2"
127.0.0.1:6379[1]> spop "set" 2 # 随机删除两个set中元素
1) "3"
2) "4"
127.0.0.1:6379[1]> smembers "set"
1) "1"

zset(有序集合)

127.0.0.1:6379[1]> zadd "zset" 1 one # 添加一个权值为1的元素one
(integer) 1
127.0.0.1:6379[1]> zadd "zset" 2 two 3 three # 批量添加
(integer) 2
127.0.0.1:6379[1]> zrange "zset" 0 -1 withscores # 返回集合元素按照分数排序
1) "one"
2) "1"
3) "two"
4) "2"
5) "three"
6) "3"
127.0.0.1:6379[1]> zcard "zset" # 获取集合元素
(integer) 3
127.0.0.1:6379[1]> zrangebyscore "zset" 2 3 withscores # 按分数获取集合中元素
1) "two"
2) "2"
3) "three"
4) "3"
127.0.0.1:6379[1]> zrangebyscore "zset" 1 3 withscores limit 1 2 # 获取元素后分页返回
1) "two"
2) "2"
3) "three"
4) "3"
127.0.0.1:6379[1]>  zrevrangebyscore "zset" 3 1 withscores # 按分数获取元素从大到小输出
1) "three"
2) "3"
3) "two"
4) "2"
5) "one"
6) "1"
127.0.0.1:6379[1]> zcount "zset" 2 3 # 权值范围在2~3之间的元素个数
(integer) 2
127.0.0.1:6379[1]> zadd "zset" 4 four
(integer) 1
127.0.0.1:6379[1]> zrem "zset" one four # 删除元素one和four
(integer) 2
127.0.0.1:6379[1]> zadd "zset" 1 one 4 four
(integer) 1
127.0.0.1:6379[1]> zremrangebyrank "zset" 2 3 # 删除排名2~3的元素
(integer) 1
127.0.0.1:6379[1]> zadd "zset" 2 two 3 three
(integer) 1
127.0.0.1:6379[1]> zremrangebyscore "zset" 2 3 # 删除分数在2~3之间的元素
(integer) 2

Redis 事务

Redis 事务可以一次执行多个命令, 并且带有以下三个重要的保证:

  • 批量操作在发送 EXEC 命令前被放入队列缓存。
  • 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
  • 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。
127.0.0.1:6379[1]> multi # 开启事务OK127.0.0.1:6379[1]> set a aQUEUED127.0.0.1:6379[1]> set b bQUEUED127.0.0.1:6379[1]> exec # 执行1) OK2) OK

Jedis连接Redis

准备

<dependency>    <groupId>redis.clients</groupId>    <artifactId>jedis</artifactId>    <version>3.5.1</version></dependency>

Jedis对象不是线程安全的,在多线程下使用同一个Jedis对象会出现并发问题,为了避免每次使用Jedis对象时都需要重新创建,Jedis提供了JedisPool。Jedis是线程安全的连接池。下面是jedispool类的代码

import redis.clients.jedis.Jedis;import redis.clients.jedis.JedisPool;import redis.clients.jedis.JedisPoolConfig;public class jedisPool {    private jedisPool() {}    private static JedisPool jedisPool;    private static int maxtotal = 100; // 最大连接数    private static int maxwaitmillis = 3000; // 获取连接时的最大等待毫秒数(如果设置为阻塞时BlockWhenExhausted),如果超时就抛异常, 小于零:阻塞不确定的时间,  默认-1    private static String host = "127.0.0.1";    private static int port = 6379;    /*创建连接池*/    static{        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();        jedisPoolConfig.setMaxTotal(maxtotal);        jedisPoolConfig.setMaxWaitMillis(maxwaitmillis);        jedisPool = new JedisPool(jedisPoolConfig, host, port);    }    /*获取jedis*/    public static Jedis getJedis(){        return jedisPool.getResource();    }    /*关闭Jedis*/    public static void close(Jedis jedis){        if(jedis!=null){            jedis.close();        }    }}

使用

在需要使用redis时,从连接池中获取jedis实例,通过idea的智能提示可以获取到jedis的全部方法,由于过多所以这里不做介绍,其中的用发和上文命令行才做jedis几乎一样。下面仅仅是一个小demo

@Testpublic void Test() {    Jedis jedis = jedisPool.getJedis();    jedis.set("1", "1");    jedis.mset("2", "2", "3", "3");    System.out.println(jedis.get("1"));}

下面是一些特殊用法:

jedis的事务执行:

@Testpublic void Test() {    Jedis jedis = jedisPool.getJedis();    Transaction t = jedis.multi();    t.set("fool", "bar");    Response<String> result1 = t.get("fool");    t.zadd("foo", 1, "barowitch");    t.zadd("foo", 0, "barinsky");    t.zadd("foo", 0, "barikoviev");    Response<Set<String>> sose = t.zrange("foo", 0, -1);    t.exec();    System.out.println(result1.get());    System.out.println(sose.get());    // 另一种方式    // List<Object> allResults = t.exec();    // System.out.println(allResults);}

管道操作:

如果遇到一次性存储大量数据,又不需要返回结果的时,可以采用管道操作来节约时间,下面时测试代码来测试管道插入和查询的性能:

public class test1 {    static Long start = 0L;    static Long time = 0L;    @Test    public void Test() {        Jedis jedis = jedisPool.getJedis();        jedis.flushAll();        start();        for(int i = 0; i<10000; i++){            String content = i + "";            jedis.set(content, content);        }        end();        System.out.println("未使用管道插入数据,耗时:" + time + "ms");        jedis.flushAll();        Pipeline p = jedis.pipelined();        start();        for(int i = 0; i<10000; i++){            String content = i + "";            p.set(content, content);        }        p.sync();        end();        System.out.println("已使用管道插入数据,耗时:" + time + "ms");        Map result = new HashMap<String,String>(10000);        start();        for(int i = 0; i<10000; i++){            String content = i + "";            String value = jedis.get(content);            result.put(content,value);        }        end();        System.out.println("未使用管道查询数据,耗时:" + time + "ms");        result.clear();        start();        Map<String,Response> responses = new HashMap<String, Response>(10000);        for(int i = 0;i<10000;i++){            String content = i + "";            Response<String> response = p.get(content);            responses.put(content, response);        }        for(String key:responses.keySet()){            result.put(key,responses.get(key));        }        end();        System.out.println("已使用管道查询数据,耗时:" + time + "ms");        p.close();    }    public static void start(){        start = System.currentTimeMillis();    }    public static void end(){        time = System.currentTimeMillis() - start;    }}

输出:

未使用管道插入数据,耗时:550ms已使用管道插入数据,耗时:22ms未使用管道查询数据,耗时:409ms已使用管道查询数据,耗时:5ms