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 #动静批改配置OK127.0.0.1:6379> config get slowlog-max-len1) "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 #动静批改配置OK127.0.0.1:6379> config get slowlog-log-slower-than1) "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) 0127.0.0.1:6379> slowlog resetOK

运维教训

  • slowlog-max-len 不要设置的过大,默认 10ms,通常设置 1ms。
  • slowlog-log-slower-than不要设置过小,通常设置 1000 左右。
  • 了解命令生命周期。
  • 定期长久化慢查问。

Pipeline

什么是流水线

1 次网络命令通信模型

批量网络命令通信模型

什么是流水线

流水线的作用

命令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 等都是原子操作。

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 baiduReading messages... (press Ctrl-C to quit)1) "subscribe"2) "baidu"3) (integer) 11) "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) 1127.0.0.1:6379> publish baidu world(integer) 1127.0.0.1:6379> publish baidu java(integer) 1127.0.0.1:6379> publish baidu python(integer) 1127.0.0.1:6379> publish baidu go(integer) 1

应用 unsubscribe 勾销订阅频道

127.0.0.1:6379> unsubscribe baidu1) "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 bigOK127.0.0.1:6379> getbit hello 0(integer) 0127.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 的比特位的数量。

个别状况下,给定的整个字符串都会被进行计数,通过指定额定的 startend 参数,能够让计数只在特定的位上进行。

不存在的 key 被当成是空字符串来解决,因而对一个不存在的 key 进行 BITCOUNT 操作,后果为 0

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

bitpos命令

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

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

独立用户统计

  • 应用 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 。

APIPFADD key element [element …]

127.0.0.1:6379> pfadd data 'java' 'python' 'go'(integer) 1127.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 蕴含的惟一元素的近似数量。

APIPFCOUNT key [key …]

127.0.0.1:6379> pfadd data 'java' 'python' 'go'(integer) 1127.0.0.1:6379> pfcount data(integer) 3

PFCOUNT命令

将多个 HyperLogLog 合并(merge)为一个 HyperLogLog , 合并后的 HyperLogLog 的基数靠近于所有输出 HyperLogLog 的可见汇合(observed set)的并集。

合并得出的 HyperLogLog 会被贮存在 destkey 键外面, 如果该键并不存在, 那么命令在执行之前, 会先为该键创立一个空的 HyperLogLog 。

字符串回复:返回 OK

APIPFMERGE destkey sourcekey [sourcekey …]

127.0.0.1:6379> PFADD  nosql  "Redis"  "MongoDB"  "Memcached"(integer) 1127.0.0.1:6379> PFADD  RDBMS  "MySQL" "MSSQL" "PostgreSQL"(integer) 1127.0.0.1:6379> PFMERGE databases nosql RDBMSOK127.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)列表。
  • 在指定了 WITHCOORDWITHDISTWITHHASH 等选项的状况下, 命令返回一个二层嵌套数组, 内层的每个子数组就示意一个元素。

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

  1. 以浮点数格局返回的核心与地位元素之间的间隔, 单位与用户指定范畴时的单位统一。
  2. geohash 整数。
  3. 由两个元素组成的坐标,别离为经度和纬度。