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:\Users\12392> 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