Redis的基本命令
127.0.0.1:6379> set key aaa //设置键为key,值为aaa
OK
127.0.0.1:6379> get key //获取key值为key的值
"aaa"
127.0.0.1:6379> exists key //判断key键是否存在
(integer) 1
127.0.0.1:6379> expire key 1000 设置key键的过期时间(单位/秒)
(integer) 1
127.0.0.1:6379> ttl key //查看key的剩余过期时间(单位/秒)
(integer) 954
127.0.0.1:6379> type key //获取key的类型
string
127.0.0.1:6379> expire key 1000 //重新设置key的过期时间
(integer) 1
127.0.0.1:6379> ttl key
(integer) 997
127.0.0.1:6379> persist key //移除key的过期时间
(integer) 1
127.0.0.1:6379> ttl key //-1表示永不过期
(integer) -1
127.0.0.1:6379> expire key 1000
(integer) 1
127.0.0.1:6379> ttl key
(integer) 996
127.0.0.1:6379> set key1 world
OK
127.0.0.1:6379> get key1
"world"
127.0.0.1:6379> randomkey //随机返回一个key值
"key1"
127.0.0.1:6379> pttl key1
(integer) -1
127.0.0.1:6379> pttl key //返回key的过期时间(单位/毫秒)
(integer) 899033
127.0.0.1:6379> rename key key1 //将key更名为key1(会覆盖掉原先的key1)
OK
127.0.0.1:6379> get key1
"aaa"
127.0.0.1:6379> get key
(nil)
127.0.0.1:6379> renamenx key1 key //仅当key不存在时,才会将key1更名为key(防止覆盖掉原来的key),返回1表示成功,0表示失败
(integer) 1
127.0.0.1:6379> get key1
(nil)
127.0.0.1:6379> get key
"aaa"
127.0.0.1:6379> set key1 abc
OK
127.0.0.1:6379> get key1
"abc"
127.0.0.1:6379> renamenx key1 key
(integer) 0
1. 字符串string
1.1 命令行操作
//getrange 截取字符串
127.0.0.1:6379> set mykey "hello world"
OK
127.0.0.1:6379> get mykey
"hello world"
127.0.0.1:6379> getrange mykey 0 4
"hello"
127.0.0.1:6379> getrange mykey 0 -2 //从0截到倒数第二个字符
"hello worl"
//getset 将mykey设为新值,并返回旧值
127.0.0.1:6379> getset mykey "pangting"
"hello world"
127.0.0.1:6379> get mykey
"pangting"
127.0.0.1:6379>
//mget 获取key和mykey的值(获取多个key的值)
127.0.0.1:6379> mget key mykey
1) "xiaoming"
2) "pangting"
//setex 将key的值设为qinqiang 并设置100秒的过期时间
127.0.0.1:6379> setex key 100 qinqiang
OK
127.0.0.1:6379> get key
"qinqiang"
127.0.0.1:6379> pttl key //返回key的过期时间(单位/毫秒)
(integer) 88048
127.0.0.1:6379>
//setnx 当key不存在时才会设置,存在时不做操作
127.0.0.1:6379> get key
"qq"
127.0.0.1:6379> setnx key aa
(integer) 0
127.0.0.1:6379> get key
"qq"
127.0.0.1:6379> strlen key //strlen 返回key的值的字符串长度
(integer) 2
127.0.0.1:6379> mset key1 aa key2 bb key3 cc //mset 同时设置多个键值对
OK
127.0.0.1:6379> mget key1 key2 key3 ad //mget 获取多个key的值
1) "aa"
2) "bb"
3) "cc"
4) (nil)
127.0.0.1:6379> set key 1
OK
127.0.0.1:6379> get key
"1"
127.0.0.1:6379> incr key //incr 自增1
(integer) 2
127.0.0.1:6379> incrby key 100 // incrby 将key值+100
(integer) 102
127.0.0.1:6379> decr key //decr 自减1
(integer) 101
127.0.0.1:6379> decrby key 100 //decrby key值减100
(integer) 1
//append 连接字符串,不存在时新增
127.0.0.1:6379> get key
(nil)
127.0.0.1:6379> append key aa
(integer) 2
127.0.0.1:6379> get key
"aa"
127.0.0.1:6379> append key bb
(integer) 4
127.0.0.1:6379> get key
"aabb"
1.2 java使用
package redis;
import org.junit.Test;
import redis.clients.jedis.*;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class RedisTest {
private static Jedis jedis = new Jedis("localhost");
//set方法nxxx参数,NX代表不存在时才设置,XX表示只有在key存在时才设置
private final static String NOT_EXISTS = "NX";
private final static String IS_EXISTS = "XX";
//set方法expx参数,ex代表单位为秒,px代表单位为毫秒
private final static String SECONDS = "EX";
private final static String MILLI_SECONDS = "PX";
@Test
public void testKey() {
//jedis.set("name1", "pangting");
//jedis.set("age", "18", IS_EXISTS, SECONDS, 10);
//当age不存在时,设置age的值并设置过期时间为10秒
//System.out.println(jedis.set("age", "18", NOT_EXISTS, SECONDS, 10));
System.out.println(jedis.type("name"));//获取key:name的类型
//jedis.del("name");//删除key:name
System.out.println(jedis.get("name"));//获取"name"对应的值
System.out.println(jedis.randomKey());//返回一个随机的key
Set<String> keys = jedis.keys("*");//返回所有的key
System.out.println(keys.toString());
System.out.println(jedis.ttl("name"));//返回key:name的过期时间(单位/秒);-1代表永不过期
jedis.expireAt("name", 1588174437);//设置key:name在某个时间过期
System.out.println(jedis.pttl("name"));//返回key:name的过期时间(单位/豪秒)
//jedis.rename("name", "newName");//修改key:name为newName(会覆盖掉原来的newName)
System.out.println(jedis.exists("name"));//判断key:name是否存在
jedis.setex("name", 10, "pangting");//设置key的值和10秒的过期时间
jedis.psetex("name", 1000L, "pangting");//设置key的值和1000毫秒的过期时间
jedis.setnx("name", "pangting");//设置key的值,若key已存在,则设置失败并返回0,不存在则设置成功并返回1
}
@Test
public void testString() {
//jedis.append("name", "dashadiao");
//批量设置key-value的值,当有一个key已存在时,设置失败并返回0
//System.out.println(jedis.msetnx("name3","pangting","name4","dapangting"));
System.out.println(jedis.incr("age"));//自增
jedis.incrBy("age", 100);//增加100
System.out.println(jedis.get("age"));
jedis.decr("age");//自减
jedis.decrBy("age", 50);//减50
System.out.println(jedis.get("age"));
//批量获取key的值
List<String> mget = jedis.mget("name", "name3", "name4");
System.out.println(mget.toString());
System.out.println(jedis.strlen("name"));//返回value的长度
System.out.println(jedis.getrange("name",0,-1));//根据索引截取value的值
}
}
1.3 应用场景
1.3.1 实现分布式锁
//设置锁字符串键,若存在则设置失败
SETNX("couponcode","lock") == 1 //成功获取锁
SETNX(lockKey,'lock')==0//有人占用资源获取锁失败
业务处理完毕释放分布式锁
DEL(lockKey);
//设置锁字符串键的失效时间,防止宕机,系统运行意外,导致无法释放锁
PEXPIRE(lockKey, lockMilliSeconds)
1.3.2 实现库存扣减或者计数器
// 计数器
INCR key //INCR readcount::{帖子ID} 每阅读一次
GET key //GET readcount::{帖子ID} 获取阅读量
//库存操作
incr key //incr 自增1
incrby key 100 // incrby 将key值+100
decr key //decr 自减1
decrby key 100 //decrby key值减100
2. 散列表Hash
2.1 命令行操作
127.0.0.1:6379> hmset user name1 xiaohong name2 xiaoqiang name3 xiaoting //hmset 设置哈希表user的键值对
OK
127.0.0.1:6379> hmget user name1 name2 name3 //hmget 获取user中 key值为name1,name2,name3的值
1) "xiaohong"
2) "xiaoqiang"
3) "xiaoting"
127.0.0.1:6379> hsetnx user name1 xiaoxiao //hsetnx 只有当user中的name1不存在时才设置值
(integer) 0
127.0.0.1:6379> hmget user name1 name2 name3
1) "xiaohong"
2) "xiaoqiang"
3) "xiaoting"
127.0.0.1:6379> hsetnx user name4 xiaoxiao
(integer) 1
127.0.0.1:6379> hmget user name1 name2 name3 name4
1) "xiaohong"
2) "xiaoqiang"
3) "xiaoting"
4) "xiaoxiao"
127.0.0.1:6379> hkeys user //hkeys 返回user中的所有key
1) "name1"
2) "name2"
3) "name3"
4) "name4"
127.0.0.1:6379> hlen user //返回key的数量
(integer) 4
127.0.0.1:6379> hvals user //返回user中所有的值
1) "xiaohong"
2) "xiaoqiang"
3) "xiaoting"
4) "xiaoxiao"
127.0.0.1:6379> hset user age 20
(integer) 1
127.0.0.1:6379> hget user age
"20"
127.0.0.1:6379> hdel user name4
(integer) 1
127.0.0.1:6379> hexists user name4
(integer) 0
127.0.0.1:6379> hexists user name3
(integer) 1
127.0.0.1:6379> hgetall user //hgetall 获取user中所有的键值对
1) "name1"
2) "xiaohong"
3) "name2"
4) "xiaoqiang"
5) "name3"
6) "xiaoting"
7) "age"
8) "20"
127.0.0.1:6379> hincrby user age 100 //hincrby 将user中key为age的值增加100
(integer) 120
2.2 java基本操作
@Test
public void testHash() {
jedis.hset("myHash", "name", "xiaoming");
jedis.hset("myHash", "age", "22");
jedis.hset("myHash", "height", "178");
System.out.println(jedis.hget("myHash", "name"));
System.out.println(jedis.hget("myHash", "age"));
System.out.println(jedis.hget("myHash", "height"));
List<String> hmget = jedis.hmget("myHash", "name", "age", "height");//根据属性获取value
System.out.println(hmget.toString());
Map<String, String> map = jedis.hgetAll("myHash");//返回所有键值对
System.out.println(map.toString());
List<String> myHash = jedis.hvals("myHash");//返回所有value
System.out.println(myHash.toString());
System.out.println(jedis.hexists("myHash", "age"));
}
3. 列表List
3.1 命令行操作
127.0.0.1:6379> lpush list eee //在列表list首部添加元素eee
(integer) 5
127.0.0.1:6379> lrange list 0 -1
1) "eee"
2) "aaa"
3) "bbb"
4) "ccc"
5) "ddd"
127.0.0.1:6379> rpush list aaa bbb //rpush 向list列表尾部添加元素aaa和bbb
(integer) 2
127.0.0.1:6379> llen list //获取列表长度
(integer) 2
127.0.0.1:6379> lrange list 0 -1 //根据索引范围获取列表元素
1) "aaa"
2) "bbb"
127.0.0.1:6379> rpush list ccc ddd eee
(integer) 5
127.0.0.1:6379> lrange list 0 -1
1) "aaa"
2) "bbb"
3) "ccc"
4) "ddd"
5) "eee"
127.0.0.1:6379> rpushx list fff // 只有列表list存在才会添加值fff
(integer) 6
127.0.0.1:6379> rpushx list1 fff //列表list1不存在,添加失败
(integer) 0
127.0.0.1:6379> lrange list 0 -1
1) "aaa"
2) "bbb"
3) "ccc"
4) "ddd"
5) "eee"
6) "fff"
127.0.0.1:6379> rpop list //rpop 移除并返回列表的最后一个元素
"fff"
127.0.0.1:6379> lrange list 0 -1
1) "aaa"
2) "bbb"
3) "ccc"
4) "ddd"
5) "eee"
127.0.0.1:6379> lpop list //lpop 移除并返回列表的第一个元素
"aaa"
127.0.0.1:6379> lrange list 0 -1
1) "bbb"
2) "ccc"
3) "ddd"
4) "eee"
127.0.0.1:6379> lindex list 1 // 根据索引获取列表的值
"ccc"
127.0.0.1:6379> blpop list 10 // 阻塞方式移除并获取列表的第一个元素,10秒内获取失败(即列表为空)则返回nil和时间
1) "list"
2) "bbb"
127.0.0.1:6379> blpop list1 10
(nil)
(10.03s)
127.0.0.1:6379> brpop list 10// 阻塞方式移除并获取列表的最后一个元素,10秒内获取失败(即列表为空)则返回nil和时间
1) "list"
2) "eee"
127.0.0.1:6379> brpop list1 10
(nil)
(10.04s)
127.0.0.1:6379> lrange list 0 -1
1) "ccc"
2) "ddd"
127.0.0.1:6379> linsert list before ccc aaa // 在ccc元素之前插入aaa
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "aaa"
2) "ccc"
3) "ddd"
127.0.0.1:6379> linsert list after aaa bbb //在aaa元素之后插入元素bbb
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "aaa"
2) "bbb"
3) "ccc"
4) "ddd"
3.2 java基本操作
@Test
public void testList() {
jedis.lpush("myList", "aa", "bb", "cc");//left-push : [cc, bb, aa]
System.out.println(jedis.lrange("myList",0,-1).toString());//根据索引范围获取List的全部元素
jedis.rpush("myList", "dd", "ee");//right-push [cc, bb, aa, dd, ee]
jedis.lpop("myList");//向左移出队列一个元素 [bb, aa, dd, ee]
jedis.rpop("myList");//向右移出队列一个元素 [bb, aa, dd]
jedis.blpop(10, "myList");//block-left-pop 阻塞方式向左出队,10秒后超时 [aa, dd]
jedis.brpop(10, "myList");//block-right-pop 阻塞方式向右出队,10秒后超时 [aa]
jedis.linsert("myList", Client.LIST_POSITION.BEFORE, "aa", "abc");//在aa元素之前插入abc
System.out.println(jedis.lrange("myList",0,-1).toString()); //[abc, aa]
Long length = jedis.llen("myList");//获取集合长度
String value = jedis.lindex("myList", 1);//根据索引获取元素
jedis.flushAll();
}
3.3 应用场景
3.3.1 实现消息队列
List列表,先进先出,从队首取值,从队尾存值;反过来也可以
3.3.2 实现栈的数据结构
栈(lpush-->lpop或者rpush-->rpop)的数据结构
4. 无序集合Set:
4.1 命令行操作
127.0.0.1:6379> sadd myset aa bb cc aa dd bb //sadd 添加set元素(元素不能重复)
(integer) 4
127.0.0.1:6379> scard myset // 返回set元素的数量
(integer) 4
127.0.0.1:6379> smembers myset // 返回set中的元素
1) "dd"
2) "cc"
3) "bb"
4) "aa"
127.0.0.1:6379> sismember myset aa // 判断元素aa是否在set中
(integer) 1
127.0.0.1:6379> sismember myset ab
(integer) 0
127.0.0.1:6379> spop myset // 随机删除一个元素并返回
"dd"
127.0.0.1:6379> srem myset aa bb dd //删除元素aa bb dd(已被删除,所以删除成功的数量是2)
(integer) 2
127.0.0.1:6379>
4.2 java基本操作
/**
* 无序集合
*/
@Test
public void testSet() {
jedis.sadd("mySet", "aa", "bb", "cc", "aa", "dd");//[dd, bb, cc, aa]
Long setLong = jedis.scard("mySet");//获取set的长度
Set<String> mySet1 = jedis.smembers("mySet");//获取所有set的元素
System.out.println(mySet1.toString());
jedis.sadd("mySet1", "aa");
jedis.sadd("mySet2", "dd");
Set<String> sdiff = jedis.sdiff("mySet", "mySet1", "mySet2");//取差集 mySet - mySet1 - mySet2
System.out.println(sdiff.toString());//[bb, cc]
Set<String> sinter = jedis.sinter("mySet", "mySet1");//取交集
System.out.println(sinter.toString());//[aa]
Set<String> sunion = jedis.sunion("mySet", "mySet1");//取并集
System.out.println(sunion.toString());
jedis.sunionstore("newSet", "mySet", "mySet1");//将mySet和mySet1的并集保存入新的newSet中
String mySet = jedis.spop("mySet");//随机移除一个元素并返回
jedis.srem("mySet", "aa", "bb"); //删除mySet中的aa和bb元素
jedis.flushAll();
}
4.3 应用场景
4.3.1 实现抽奖
- 像转发微博,直播刷礼物时都会有一个抽奖的操作, 将这些满足条件的用户添加入set中
SADD key user1 user2 user3 ... //将用户ID添加入set中
- 随机弹出count名用户并将其从set中删除, 实现了抽奖的操作
SPOP key [count] // 抽取count名用户
4.3.2 获取共同好友
获取共同好友就是取交集的操作了
jedis.sinter("mySet", "mySet1");//取交集
像其他的取并集取差集等的,也可以通过set来实现
5. 有序集合SortSet
5.1 java基本操作
/**
* 有序集合
*/
@Test
public void testSortSet() {
Map<String, Double> map = new HashMap<>();
map.put("aaa", 1.0);
map.put("bbb", 1.2);
map.put("ccc", -1.2);
map.put("ddd", 0.0);
jedis.zadd("mySortSet", map);
Set<String> mySortSet = jedis.zrange("mySortSet", 0, -1);
System.out.println(mySortSet);//[ccc, ddd, aaa, bbb] 从小到大排序
jedis.flushAll();
}
5.2 应用场景
5.2.1 实现延时队列
延时队列:使用有序集合,score参数保存时间戳,会按照时间戳排序,
开一个线程不断地取第一个数据,当前时间大于时间戳时则拿出第一个key来运行
5.2.2 带有权重的场景:排行榜等
6. HyperLogLog
6.1 介绍
HyperLogLog 可以接受多个元素作为输入,并给出输入元素的基数估算值:
• 基数:集合中不同元素的数量。比如 {‘apple’, ‘banana’, ‘cherry’, ‘banana’, ‘apple’} 的基数就是 3 。
• 估算值:算法给出的基数并不是精确的,可能会比实际稍微多一些或者稍微少一些,但会控制在合
理的范围之内。
HyperLogLog 的优点是,即使输入元素的数量或者体积非常非常大,计算基数所需的空间总是固定
的、并且是很小的。
在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基
数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以
HyperLogLog 不能像集合那样,返回输入的各个元素。
6.2 命令行使用
127.0.0.1:6379> pfadd myHyperLogLog a a a b b b c c c d e // 添加元素
(integer) 1
127.0.0.1:6379> PFCOUNT myHyperLogLog // 获取基数
(integer) 5
127.0.0.1:6379> pfadd myHyperLogLog2 a b e f g
(integer) 1
127.0.0.1:6379> PFCOUNT myHyperLogLog2
(integer) 5
//将myHyperLogLog 和myHyperLogLog2合并成为一个新的myHyperLogLog3
127.0.0.1:6379> PFMERGE myHyperLogLog3 myHyperLogLog myHyperLogLog2
OK
127.0.0.1:6379> PFCOUNT myHyperLogLog3
(integer) 7
6.3 java使用
@Test
public void testHyperLogLog() {
jedis.flushAll();
jedis.pfadd("myHyperLogLog", "a", "b", "c");//添加元素
jedis.pfadd("myHyperLogLog2", "c", "d", "e");
System.out.println(jedis.pfcount("myHyperLogLog"));//获取基数 3
System.out.println(jedis.pfcount("myHyperLogLog2")); //3
System.out.println(jedis.pfcount("myHyperLogLog2","myHyperLogLog"));//获取两个集合合并之后的基数 5
//将myHyperLogLog 和myHyperLogLog2合并成为一个新的myHyperLogLog3
jedis.pfmerge("myHyperLogLog3","myHyperLogLog2", "myHyperLogLog");//5
System.out.println(jedis.pfcount("myHyperLogLog3"));
}
6.4 应用场景
适用的场景都是一个大集合中,找出不重复的基数数量, 而且对结果的准确度没有很大要求的情况
6.4.1 记录网站每天访问的独立IP数量
pfadd ips ip1 ip2 ip3 ... // 添加ip
pfcount ips // 获取ip数量
6.4.2 记录网站每天访问的会员数量(同理)
6.5 HyperLogLog原理?
待补充
7. bitmap
Bitmap是一串连续的2进制数字(0或1),每一位所在的位置为偏移(offset)
7.1 命令行基本操作
127.0.0.1:6379> SETBIT myBitMap 100 1 //将offset=100设为1
(integer) 0
127.0.0.1:6379> BITCOUNT myBitMap //获取位数为1的总数
(integer) 1
127.0.0.1:6379> GETBIT myBitMap 100 // 获取offset为100的值
(integer) 1
7.2 应用场景
7.2.1 用户签到
@Test
public void testBitMap() {
jedis.flushAll();
for (int userId = 0; userId < 10000; userId++) {
if (userId % 2 == 0) {
//模拟签到,将用户ID设为偏移位offset
jedis.setbit("sign_user_2020_05_01", userId, true);
}
}
Long sign_user_2020_05_01 = jedis.bitcount("sign_user_2020_05_01");//获取1的数量,即签到用户的数量
System.out.println(sign_user_2020_05_01);
System.out.println(jedis.getbit("sign_user_2020_05_01",112));//查看112用户是否签到
for (int userId = 0; userId < 10000; userId++) {
if (userId % 2 != 0) {
//模拟签到,将用户ID设为偏移位offset
jedis.setbit("sign_user_2020_05_02", userId, true);
}
}
//逻辑与运算:获取5.1和5.2都签到的用户
jedis.bitop(BitOP.AND, "sign_user_2020_05_02_and_2020_05_01", "sign_user_2020_05_02", "sign_user_2020_05_01");
//逻辑或:获取5.1和5.2签到的用户总数
jedis.bitop(BitOP.OR, "sign_user_2020_05_02_or_2020_05_01", "sign_user_2020_05_02", "sign_user_2020_05_01");
//逻辑非:获取5.1签到但是5.2不登录的用户数
jedis.bitop(BitOP.NOT, "sign_user_2020_05_01_not_2020_05_02", "sign_user_2020_05_01", "sign_user_2020_05_02");
System.out.println(jedis.bitcount("sign_user_2020_05_02_and_2020_05_01"));
}
bitmap优点
- 节约空间,统计一亿人每天的登录情况,用一亿bit,约1200WByte,约10M的字符就能表示(因为bitop命令的返回值是保存到time中的字符串的长度(以字节byte为单位),和输入 key 中最长的字符串长度相等。即1亿除以8bit=1250万Byte);
- 计算方便。
性能:
如果你的 bitmap 数据非常大,那么可以考虑使用以下两种方法:
● 将一个大的 bitmap 分散到不同的 key 中,作为小的 bitmap 来处理。使用 Lua 脚本可以很方便地完成这一工作。
● 使用 BITCOUNT 的 start 和 end 参数,每次只对所需的部分位进行计算,将位的累积工作(accumulating)放到客户端进行,并且对结果进行缓存 (caching)。
何时使用:
如果活跃用户在百万级别,使用Redis BitMap很划算。
如果活跃用户很少,而用户id都是10位以上的int(即offset最大值为100亿以上,大约就要消耗1G的内存)。那就很浪费内存了,还不如使用set集合,然后求交集就可以了。
8. Geo(地理空间)
待补充