Jedis 是 Redis 官方首选的 Java 客户端开发包。集成了 redis 的一些命令操作,封装了 redis 的 java 客户端。提供了连接池管理。
<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
<version>2.9.0version>
<type>jartype>
<scope>compilescope>
dependency>
/**
* @author 又坏又迷人
* 公众号: Java菜鸟程序员
* @date 2020/12/29
* @Description: Redis简单实用
*/
public class RedisTest {
public static void main(String[] args) {
// 1.生成一个Jedis对象,这个对象负责和指定Redis节点进行通信
Jedis jedis = new Jedis("127.0.0.1", 6379);
// 2.执行string操作
jedis.set("hello", "world");
String hello = jedis.get("hello");
System.out.println(hello); // world
jedis.set("count", "1");
// 自增
jedis.incr("count");
System.out.println(jedis.get("count")); // 2
//3.执行hash操作
jedis.hset("myHash", "f1", "v1");
jedis.hset("myHash", "f2", "v2");
System.out.println(jedis.hgetAll("myHash").toString()); // {f2=v2, f1=v1}
//4. list
jedis.rpush("myList", "1", "2", "3");
System.out.println(jedis.lrange("myList", 0, -1)); // [1, 2, 3]
//5. set
jedis.sadd("mySet", "a", "b", "c");
System.out.println(jedis.smembers("mySet")); // [a, c, b]
//6. zset
jedis.zadd("myzset", 10, "Jack");
jedis.zadd("myzset", 20, "Rose");
jedis.zadd("myzset", 30, "Michelle");
System.out.println(jedis.zrange("myzset", 0, -1)); //[Jack, Rose, Michelle]
}
}
Jedis 直连
Jedis 连接池
方案 | 优点 | 缺点 |
---|---|---|
直连 | 简单方便,适用于少量长期连接的场景。 | 存在每次新建/关闭 TCP 开销,资源无法控制,存在泄露的可能。Jedis 对象线程不安全。 |
连接池 | Jedis 预先生成,减低开销使用。连接池的形式保护和控制资源的使用 | 相对于直连,使用相对麻烦,尤其在资源的管理上需要很多参数来保证。一旦规划不合理就会出现问题。 |
/**
* @author 又坏又迷人
* 公众号: Java菜鸟程序员
* @date 2020/12/29
* @Description: Redis连接池使用
*/
public class RedisPoolTest {
// 初始化Jedis连接池,通常来讲JedisPool是单例的.
private final static GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
private final static JedisPool jedisPool = new JedisPool(poolConfig, "127.0.0.1", 6379);
public static void main(String[] args) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
jedis.set("hello", "world");
String hello = jedis.get("hello");
System.out.println(hello); // world
jedis.set("count", "1");
// 自增
jedis.incr("count");
System.out.println(jedis.get("count")); // 2
} catch (Exception e) {
e.printStackTrace();
} finally {
if(jedis != null){
//归还资源
jedis.close();
}
}
}
}
生命周期两点说明:
slowlog-max-len
slowlog-log-slower-than
slowlog-log-slower-than=0
, 表示记录所有命令。slowlog-log-slower-than<0
,表示不记录任何命令。动态配置:
127.0.0.1:6379> config get slowlog-max-len #查看默认配置
1) "slowlog-max-len"
2) "128"
127.0.0.1:6379> config set slowlog-max-len 1000 #动态修改配置
OK
127.0.0.1:6379> config get slowlog-max-len
1) "slowlog-max-len"
2) "1000"
127.0.0.1:6379> config get slowlog-log-slower-than #查看默认配置
1) "slowlog-log-slower-than"
2) "10000"
127.0.0.1:6379> config set slowlog-log-slower-than 1200 #动态修改配置
OK
127.0.0.1:6379> config get slowlog-log-slower-than
1) "slowlog-log-slower-than"
2) "1200"
slowlog get [n]
:获取慢查询队列slowlog len
:获取慢查询队列长度slowlog reset
: 清空慢查询队列127.0.0.1:6379> slowlog get 10
(empty list or set)
127.0.0.1:6379> slowlog len
(integer) 0
127.0.0.1:6379> slowlog reset
OK
slowlog-max-len
不要设置的过大,默认 10ms,通常设置 1ms。slowlog-log-slower-than
不要设置过小,通常设置 1000 左右。1 次网络命令通信模型
批量网络命令通信模型
什么是流水线
流水线的作用
命令 | N 个命令操作 | 1 次 pipeline(N 个命令) |
---|---|---|
时间 | N 次网络+N 次命令 | 1 次网络+N 次命令 |
数据量 | 1 条命令 | N 条命令 |
注意
没有使用 Pipeline用时:29707
/**
* @author 又坏又迷人
* 公众号: Java菜鸟程序员
* @date 2020/12/29
* @Description:
*/
public class PipelineRedisTest {
// 初始化Jedis连接池,通常来讲JedisPool是单例的.
private final static GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
private final static JedisPool jedisPool = new JedisPool(poolConfig, "47.110.41.15", 6379);
public static void main(String[] args) {
long start = System.currentTimeMillis();
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
for (int i = 0; i < 1000; i++) {
jedis.hset("hashkey", "field_" + i, "value_" + i);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (jedis != null) {
//归还资源
jedis.close();
}
}
long end = System.currentTimeMillis();
System.out.println(end - start); //29707
}
}
使用 Pipeline用时:3161
/**
* @author 又坏又迷人
* 公众号: Java菜鸟程序员
* @date 2020/12/29
* @Description:
*/
public class PipelineRedisTest {
// 初始化Jedis连接池,通常来讲JedisPool是单例的.
private final static GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
private final static JedisPool jedisPool = new JedisPool(poolConfig, "47.110.41.15", 6379);
public static void main(String[] args) {
long start = System.currentTimeMillis();
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
for (int i = 0; i < 100; i++) {
Pipeline pipeline = jedis.pipelined();
for (int j = i * 100; j < (i + 1) * 100; j++) {
pipeline.hset("pipelinekey", "field_" + j, "value_" + j);
}
pipeline.syncAndReturnAll();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (jedis != null) {
//归还资源
jedis.close();
}
}
long end = System.currentTimeMillis();
System.out.println(end - start); //3161
}
}
mset、mget、hmset、hmget 等都是原子操作。
pipeline 命令是非原子操作,但是命令返回顺序能够保证。
多个订阅者订阅一个频道
发布者 publisher 只要发布了消息,所有订阅了这个频道 channel 的订阅者都能收到消息。
一个订阅者可以订阅多个频道
一个订阅者可以订阅多个频道,当发布者发布不同消息到多个频道,订阅者可以接受多个频道消息。
发布订阅与消息队列
Redis 还可以用作消息队列,所有消息订阅者是去抢队列里面的消息。
subscribe
首先订阅频道。
127.0.0.1:6379> subscribe baidu
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "baidu"
3) (integer) 1
1) "message"
2) "baidu"
3) "hello"
1) "message"
2) "baidu"
3) "world"
1) "message"
2) "baidu"
3) "java"
1) "message"
2) "baidu"
3) "python"
1) "message"
2) "baidu"
3) "go"
publish
发送消息。
127.0.0.1:6379> publish baidu hello
(integer) 1
127.0.0.1:6379> publish baidu world
(integer) 1
127.0.0.1:6379> publish baidu java
(integer) 1
127.0.0.1:6379> publish baidu python
(integer) 1
127.0.0.1:6379> publish baidu go
(integer) 1
使用 unsubscribe 取消订阅频道
127.0.0.1:6379> unsubscribe baidu
1) "unsubscribe"
2) "baidu"
3) (integer) 0
psubscribe [pattern...]
:订阅指定规则的频道。punsubscribe [pattern...]
:退订指定的模式。pubsub channels
:列出至少有一个订阅者的频道。pubsub numsub [channel...]
:列出给定频道的订阅者数量。位图并不是一种数据结构,其实就是一种普通的字符串,也可以说是 byte 数组。
127.0.0.1:6379> set hello big
OK
127.0.0.1:6379> getbit hello 0
(integer) 0
127.0.0.1:6379> getbit hello 1
(integer) 1
setbit命令
对 key
所储存的字符串值,设置或清除指定偏移量上的位(bit)。
位的设置或清除取决于 value
参数,可以是 0
也可以是 1
。
当 key
不存在时,自动生成一个新的字符串值。
字符串会进行伸展(grown)以确保它可以将 value
保存在指定的偏移量上。当字符串值进行伸展时,空白位置以 0
填充。
offset
参数必须大于或等于 0
,小于 2^32 (bit 映射被限制在 512 MB 之内)。
返回指定偏移量原来储存的位。
getbit命令
对 key
所储存的字符串值,获取指定偏移量上的位(bit)。
当 offset
比字符串值的长度大,或者 key
不存在时,返回 0
。
bitcount命令
计算给定字符串中,被设置为 1
的比特位的数量。
一般情况下,给定的整个字符串都会被进行计数,通过指定额外的 start
或 end
参数,可以让计数只在特定的位上进行。
不存在的 key
被当成是空字符串来处理,因此对一个不存在的 key
进行 BITCOUNT
操作,结果为 0
。
bitop命令
对一个或多个保存二进制位的字符串 key
进行位元操作,并将结果保存到 destkey
上。
operation
可以是 AND
、 OR
、 NOT
、 XOR
这四种操作中的任意一种:
BITOP AND destkey key [key ...]
,对一个或多个 key
求逻辑并,并将结果保存到 destkey
。BITOP OR destkey key [key ...]
,对一个或多个 key
求逻辑或,并将结果保存到 destkey
。BITOP XOR destkey key [key ...]
,对一个或多个 key
求逻辑异或,并将结果保存到 destkey
。BITOP NOT destkey key
,对给定 key
求逻辑非,并将结果保存到 destkey
。除了 NOT
操作之外,其他操作都可以接受一个或多个 key
作为输入。
返回保存到 destkey
的字符串的长度,和输入 key
中最长的字符串长度相等。
bitpos命令
返回位图中第一个值为 bit
的二进制位的位置。
在默认情况下, 命令将检测整个位图, 但用户也可以通过可选的 start
参数和 end
参数指定要检测的范围。
数据类型 | 每个 UserId 占用空间 | 需要存储的用户量 | 全部内存量 |
---|---|---|---|
set | 32 位 | 50,000,000 | 32 位 * 50,000,000 = 200MB |
bitmap | 1 位 | 100,000,000 | 1 位 * 100,000,000 = 12.5MB |
但是如果只有 10 万独立用户的话,结果就不一样了。
数据类型 | 每个 UserId 占用空间 | 需要存储的用户量 | 全部内存量 |
---|---|---|---|
set | 32 位 | 100,000 | 32 位 * 100,000 = 4MB |
bitmap | 1 位 | 100,000,000 | 1 位 * 100,000,000 = 12.5MB |
Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。
在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。
PFADD命令
将任意数量的元素添加到指定的 HyperLogLog 里面。
作为这个命令的副作用, HyperLogLog 内部可能会被更新, 以便反映一个不同的唯一元素估计数量(也即是集合的基数)。
如果 HyperLogLog 估计的近似基数(approximated cardinality)在命令执行之后出现了变化, 那么命令返回 1
, 否则返回 0
。 如果命令执行时给定的键不存在, 那么程序将先创建一个空的 HyperLogLog 结构, 然后再执行命令。
1
。如果 HyperLogLog 的内部储存被修改了, 那么返回 1 , 否则返回 0 。
API:PFADD key element [element …]
127.0.0.1:6379> pfadd data 'java' 'python' 'go'
(integer) 1
127.0.0.1:6379> pfcount data
(integer) 3
PFCOUNT命令
当 PFCOUNT key [key …] 命令作用于单个键时, 返回储存在给定键的 HyperLogLog 的近似基数, 如果键不存在, 那么返回 0
。
当 PFCOUNT key [key …] 命令作用于多个键时, 返回所有给定 HyperLogLog 的并集的近似基数, 这个近似基数是通过将所有给定 HyperLogLog 合并至一个临时 HyperLogLog 来计算得出的。
命令返回的可见集合(observed set)基数并不是精确值, 而是一个带有 0.81% 标准错误(standard error)的近似值。
返回给定 HyperLogLog 包含的唯一元素的近似数量。
API:PFCOUNT key [key …]
127.0.0.1:6379> pfadd data 'java' 'python' 'go'
(integer) 1
127.0.0.1:6379> pfcount data
(integer) 3
PFCOUNT命令
将多个 HyperLogLog 合并(merge)为一个 HyperLogLog , 合并后的 HyperLogLog 的基数接近于所有输入 HyperLogLog 的可见集合(observed set)的并集。
合并得出的 HyperLogLog 会被储存在 destkey
键里面, 如果该键并不存在, 那么命令在执行之前, 会先为该键创建一个空的 HyperLogLog 。
字符串回复:返回 OK
。
API:PFMERGE destkey sourcekey [sourcekey …]
127.0.0.1:6379> PFADD nosql "Redis" "MongoDB" "Memcached"
(integer) 1
127.0.0.1:6379> PFADD RDBMS "MySQL" "MSSQL" "PostgreSQL"
(integer) 1
127.0.0.1:6379> PFMERGE databases nosql RDBMS
OK
127.0.0.1:6379> pfcount databases
(integer) 6
GEO 主要用于存储地理位置信息,并对存储的信息进行操作。
GEOADD命令
将给定的空间元素(纬度、经度、名字)添加到指定的键里面。 这些数据会以有序集合的形式被储存在键里面。
当用户尝试输入一个超出范围的经度或者纬度时, GEOADD
命令将返回一个错误。
返回新添加到键里面的空间元素数量, 不包括那些已经存在但是被更新的元素。
GEOPOS命令
从键里面返回所有给定位置元素的位置(经度和纬度)。
因为 GEOPOS
命令接受可变数量的位置元素作为输入, 所以即使用户只给定了一个位置元素, 命令也会返回数组回复。
GEOPOS
命令返回一个数组, 数组中的每个项都由两个元素组成: 第一个元素为给定位置元素的经度, 而第二个元素则为给定位置元素的纬度。 当给定的位置元素不存在时, 对应的数组项为空值。
GEODIST命令
返回两个给定位置之间的距离。
如果两个位置之间的其中一个不存在, 那么命令返回空值。
指定单位的参数 unit
必须是以下单位的其中一个:
m
表示单位为米。km
表示单位为千米。mi
表示单位为英里。ft
表示单位为英尺。如果用户没有显式地指定单位参数, 那么 GEODIST
默认使用米作为单位。
GEODIST
命令在计算距离时会假设地球为完美的球形, 在极限情况下, 这一假设最大会造成 0.5% 的误差。
计算出的距离会以双精度浮点数的形式被返回。 如果给定的位置元素不存在, 那么命令返回空值。
GEORADIUS命令
以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。
范围可以使用以下其中一个单位:
m
表示单位为米。km
表示单位为千米。mi
表示单位为英里。ft
表示单位为英尺。在给定以下可选项时, 命令会返回额外的信息:
WITHDIST
: 在返回位置元素的同时, 将位置元素与中心之间的距离也一并返回。 距离的单位和用户给定的范围单位保持一致。WITHCOORD
: 将位置元素的经度和维度也一并返回。WITHHASH
: 以 52 位有符号整数的形式, 返回位置元素经过原始 geohash 编码的有序集合分值。 这个选项主要用于底层应用或者调试, 实际中的作用并不大。命令默认返回未排序的位置元素。 通过以下两个参数, 用户可以指定被返回位置元素的排序方式:
ASC
: 根据中心的位置, 按照从近到远的方式返回位置元素。DESC
: 根据中心的位置, 按照从远到近的方式返回位置元素。在默认情况下, GEORADIUS
命令会返回所有匹配的位置元素。 虽然用户可以使用 COUNT
选项去获取前 N 个匹配元素, 但是因为命令在内部可能会需要对所有被匹配的元素进行处理, 所以在对一个非常大的区域进行搜索时, 即使只使用 COUNT
选项去获取少量元素, 命令的执行速度也可能会非常慢。 但是从另一方面来说, 使用 COUNT
选项去减少需要返回的元素数量, 对于减少带宽来说仍然是非常有用的。
返回值
GEORADIUS
命令返回一个数组, 具体来说:
WITH
选项的情况下, 命令只会返回一个像 ["New York","Milan","Paris"]
这样的线性(linear)列表。WITHCOORD
、 WITHDIST
、 WITHHASH
等选项的情况下, 命令返回一个二层嵌套数组, 内层的每个子数组就表示一个元素。在返回嵌套数组时, 子数组的第一个元素总是位置元素的名字。 至于额外的信息, 则会作为子数组的后续元素, 按照以下顺序被返回: