Redis 客户端应用
Java 客户端:Jedis
Jedis 是 Redis 官网首选的 Java 客户端开发包。集成了 redis 的一些命令操作,封装了 redis 的 java 客户端。提供了连接池治理。
Jedis Maven 依赖包
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
<type>jar</type>
<scope>compile</scope>
</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 直连
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();}
}
}
}
Redis 其余性能
慢查问
生命周期
生命周期两点阐明:
- 慢查问只产生在第 3 阶段。
- 客户端超时不肯定是慢查问,但慢查问是客户端超时的一个可能因素。
两个配置
slowlog-max-len
- 此参数示意慢查问最大保留个数,慢查问日志都是保留在队列中。
- 先进先出。
- 固定长度。
- 保留在内存中(服务器断电会导致慢查问数据失落)。
slowlog-log-slower-than
- 此参数示意慢查问阈值(单位:奥妙),默认配置 10000 奥妙。
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 左右。- 了解命令生命周期。
- 定期长久化慢查问。
Pipeline
什么是流水线
1 次网络命令通信模型
批量网络命令通信模型
什么是流水线
流水线的作用
命令 | N 个命令操作 | 1 次 pipeline(N 个命令) |
---|---|---|
工夫 | N 次网络 +N 次命令 | 1 次网络 +N 次命令 |
数据量 | 1 条命令 | N 条命令 |
留神
- Redis 命令执行工夫是奥妙级别的。
- pipeline 每次批量命令条数须要管制(留神网络传输)。
Pipeline-Jedis 客户端实现
没有应用 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
}
}
与原生 M 操作比照
mset、mget、hmset、hmget 等都是原子操作。
pipeline 命令是非原子操作,然而命令返回程序可能保障。
应用倡议
- 留神每次 pipeline 携带的数据量
- pipeline 每次只能作用在一个 Redis 节点上
-
M 命令操作与 pipeline 的区别
- mset、mget、hmget、hmset 等命令是:n 次网络工夫 +n 次命令工夫。
- pipeline 操作命令是:1 次网络工夫 +n 次命令工夫。
公布订阅
- 发布者(publisher)
- 订阅者(subscriber)
- 频道(channel)
模型
多个订阅者订阅一个频道
发布者 publisher 只有公布了音讯,所有订阅了这个频道 channel 的订阅者都能收到音讯。
一个订阅者能够订阅多个频道
一个订阅者能够订阅多个频道,当发布者公布不同音讯到多个频道,订阅者能够承受多个频道音讯。
公布订阅与音讯队列
Redis 还能够用作音讯队列,所有音讯订阅者是去抢队列外面的音讯。
相干 API
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
其它 API
psubscribe [pattern...]
:订阅指定规定的频道。punsubscribe [pattern...]
:退订指定的模式。pubsub channels
:列出至多有一个订阅者的频道。pubsub numsub [channel...]
:列出给定频道的订阅者数量。
Bitmap
位图
位图并不是一种数据结构,其实就是一种一般的字符串,也能够说是 byte 数组。
- b 的 ASCII=98 对应的二进制为:01100010
- i 的 ASCII=105 对应的二进制为:01101001
- g 的 ASCII=103 对应的二进制为:01100111
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
参数指定要检测的范畴。
独立用户统计
- 应用 set 和 bitmap
- 1 亿用户,5 千万独立
数据类型 | 每个 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 |
应用教训
- type=string 类型,最大 512MB。
- 留神 setbit 时的偏移量,可能有较大耗时。
- 位图不是相对好,正当的场景应用正当的技术。
HyperLogLog
Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的长处是,在输出元素的数量或者体积十分十分大时,计算基数所需的空间总是固定的、并且是很小的。
在 Redis 外面,每个 HyperLogLog 键只须要破费 12 KB 内存,就能够计算靠近 2^64 个不同元素的基 数。这和计算基数时,元素越多消耗内存就越多的汇合造成鲜明对比。
然而,因为 HyperLogLog 只会依据输出元素来计算基数,而不会贮存输出元素自身,所以 HyperLogLog 不能像汇合那样,返回输出的各个元素。
PFADD命令
将任意数量的元素增加到指定的 HyperLogLog 外面。
作为这个命令的副作用,HyperLogLog 外部可能会被更新,以便反映一个不同的惟一元素预计数量(也即是汇合的基数)。
如果 HyperLogLog 预计的近似基数(approximated cardinality)在命令执行之后呈现了变动,那么命令返回 1
,否则返回 0
。如果命令执行时给定的键不存在,那么程序将先创立一个空的 HyperLogLog 构造,而后再执行命令。
- 如果给定键曾经是一个 HyperLogLog,那么这种调用不会产生任何成果。
- 但如果给定的键不存在,那么命令会创立一个空的 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
应用教训
- 是否能容忍谬误:HyperLogLog 错误率为:0.81%
- 是否须要单条数据:如果须要单条数据,就不适宜应用 HyperLogLog。
GEO
GEO 次要用于存储地理位置信息,并对存储的信息进行操作。
GEOADD命令
将给定的空间元素(纬度、经度、名字)增加到指定的键外面。这些数据会以有序汇合的模式被贮存在键外面。
- 无效的经度介于 -180 度至 180 度之间。
- 无效的纬度介于 -85.05112878 度至 85.05112878 度之间。
当用户尝试输出一个超出范围的经度或者纬度时,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 <count>
选项去获取前 N 个匹配元素,然而因为命令在外部可能会须要对所有被匹配的元素进行解决,所以在对一个十分大的区域进行搜寻时,即便只应用 COUNT
选项去获取大量元素,命令的执行速度也可能会十分慢。然而从另一方面来说,应用 COUNT
选项去缩小须要返回的元素数量,对于缩小带宽来说依然是十分有用的。
返回值
GEORADIUS
命令返回一个数组,具体来说:
- 在没有给定任何
WITH
选项的状况下,命令只会返回一个像["New York","Milan","Paris"]
这样的线性(linear)列表。 - 在指定了
WITHCOORD
、WITHDIST
、WITHHASH
等选项的状况下,命令返回一个二层嵌套数组,内层的每个子数组就示意一个元素。
在返回嵌套数组时,子数组的第一个元素总是地位元素的名字。至于额定的信息,则会作为子数组的后续元素,依照以下程序被返回:
- 以浮点数格局返回的核心与地位元素之间的间隔,单位与用户指定范畴时的单位统一。
- geohash 整数。
- 由两个元素组成的坐标,别离为经度和纬度。