【Java】深入学习Redis(二)

Redis 客户端使用

Java 客户端:Jedis

Jedis 是 Redis 官方首选的 Java 客户端开发包。集成了 redis 的一些命令操作,封装了 redis 的 java 客户端。提供了连接池管理。

【Java】深入学习Redis(二)

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 直连

【Java】深入学习Redis(二)

Jedis 连接池

【Java】深入学习Redis(二)

方案优点缺点
直连简单方便,适用于少量长期连接的场景。存在每次新建/关闭 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 其他功能

慢查询

生命周期

【Java】深入学习Redis(二)

生命周期两点说明:

  • 慢查询只发生在第 3 阶段。
  • 客户端超时不一定是慢查询,但慢查询是客户端超时的一个可能因素。

两个配置

【Java】深入学习Redis(二)

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 次网络命令通信模型

【Java】深入学习Redis(二)

批量网络命令通信模型

【Java】深入学习Redis(二)

什么是流水线

【Java】深入学习Redis(二)

流水线的作用

命令N 个命令操作1 次 pipeline(N 个命令)
时间N 次网络+N 次命令1 次网络+N 次命令
数据量1 条命令N 条命令

注意

  1. Redis 命令执行时间是微妙级别的。
  2. 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 等都是原子操作。

【Java】深入学习Redis(二)

pipeline 命令是非原子操作,但是命令返回顺序能够保证。

【Java】深入学习Redis(二)

使用建议

  • 注意每次 pipeline 携带的数据量
  • pipeline 每次只能作用在一个 Redis 节点上
  • M 命令操作与 pipeline 的区别

    • mset、mget、hmget、hmset 等命令是:n 次网络时间+n 次命令时间。
    • pipeline 操作命令是:1 次网络时间+n 次命令时间。

发布订阅

  • 发布者(publisher)
  • 订阅者(subscriber)
  • 频道(channel)

模型

多个订阅者订阅一个频道

发布者 publisher 只要发布了消息,所有订阅了这个频道 channel 的订阅者都能收到消息。

【Java】深入学习Redis(二)

一个订阅者可以订阅多个频道

一个订阅者可以订阅多个频道,当发布者发布不同消息到多个频道,订阅者可以接受多个频道消息。

【Java】深入学习Redis(二)

发布订阅与消息队列

Redis 还可以用作消息队列,所有消息订阅者是去抢队列里面的消息。

【Java】深入学习Redis(二)

相关 API

subscribe首先订阅频道。

【Java】深入学习Redis(二)

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发送消息。

【Java】深入学习Redis(二)

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 数组。

【Java】深入学习Redis(二)

  • 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

【Java】深入学习Redis(二)

setbit命令

key 所储存的字符串值,设置或清除指定偏移量上的位(bit)。

位的设置或清除取决于 value 参数,可以是 0 也可以是 1

key 不存在时,自动生成一个新的字符串值。

字符串会进行伸展(grown)以确保它可以将 value 保存在指定的偏移量上。当字符串值进行伸展时,空白位置以 0 填充。

offset 参数必须大于或等于 0 ,小于 2^32 (bit 映射被限制在 512 MB 之内)。

返回指定偏移量原来储存的位。

【Java】深入学习Redis(二)

getbit命令

key 所储存的字符串值,获取指定偏移量上的位(bit)。

offset 比字符串值的长度大,或者 key 不存在时,返回 0

【Java】深入学习Redis(二)

bitcount命令

计算给定字符串中,被设置为 1 的比特位的数量。

一般情况下,给定的整个字符串都会被进行计数,通过指定额外的 startend 参数,可以让计数只在特定的位上进行。

不存在的 key 被当成是空字符串来处理,因此对一个不存在的 key 进行 BITCOUNT 操作,结果为 0

【Java】深入学习Redis(二)

bitop命令

对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。

operation 可以是 ANDORNOTXOR 这四种操作中的任意一种:

  • 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 中最长的字符串长度相等。

【Java】深入学习Redis(二)

bitpos命令

返回位图中第一个值为 bit 的二进制位的位置。

在默认情况下, 命令将检测整个位图, 但用户也可以通过可选的 start 参数和 end 参数指定要检测的范围。

【Java】深入学习Redis(二)

独立用户统计

  • 使用 set 和 bitmap
  • 1 亿用户,5 千万独立

数据类型每个 UserId 占用空间需要存储的用户量全部内存量
set32 位50,000,00032 位 * 50,000,000 = 200MB
bitmap1 位100,000,0001 位 * 100,000,000 = 12.5MB

但是如果只有 10 万独立用户的话,结果就不一样了。

数据类型每个 UserId 占用空间需要存储的用户量全部内存量
set32 位100,00032 位 * 100,000 = 4MB
bitmap1 位100,000,0001 位 * 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 命令将返回一个错误。

返回新添加到键里面的空间元素数量, 不包括那些已经存在但是被更新的元素。

【Java】深入学习Redis(二)

GEOPOS命令

从键里面返回所有给定位置元素的位置(经度和纬度)。

因为 GEOPOS 命令接受可变数量的位置元素作为输入, 所以即使用户只给定了一个位置元素, 命令也会返回数组回复。

GEOPOS 命令返回一个数组, 数组中的每个项都由两个元素组成: 第一个元素为给定位置元素的经度, 而第二个元素则为给定位置元素的纬度。 当给定的位置元素不存在时, 对应的数组项为空值。

【Java】深入学习Redis(二)

GEODIST命令

返回两个给定位置之间的距离。

如果两个位置之间的其中一个不存在, 那么命令返回空值。

指定单位的参数 unit 必须是以下单位的其中一个:

  • m 表示单位为米。

  • km 表示单位为千米。

  • mi 表示单位为英里。

  • ft 表示单位为英尺。

如果用户没有显式地指定单位参数, 那么 GEODIST 默认使用米作为单位。

GEODIST 命令在计算距离时会假设地球为完美的球形, 在极限情况下, 这一假设最大会造成 0.5% 的误差。

计算出的距离会以双精度浮点数的形式被返回。 如果给定的位置元素不存在, 那么命令返回空值。

【Java】深入学习Redis(二)

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)列表。
  • 在指定了 WITHCOORDWITHDISTWITHHASH 等选项的情况下, 命令返回一个二层嵌套数组, 内层的每个子数组就表示一个元素。

在返回嵌套数组时, 子数组的第一个元素总是位置元素的名字。 至于额外的信息, 则会作为子数组的后续元素, 按照以下顺序被返回:

  1. 以浮点数格式返回的中心与位置元素之间的距离, 单位与用户指定范围时的单位一致。
  2. geohash 整数。
  3. 由两个元素组成的坐标,分别为经度和纬度。

【Java】深入学习Redis(二)

以上是 【Java】深入学习Redis(二) 的全部内容, 来源链接: utcz.com/a/91407.html

回到顶部