核心
一、Redis单机多实例原理
每个实例对应不同的配置文件,配置文件对应不同的端口、数据库文件位置、日志位置。
二、Redis单实例多数据库
每个Redis实例都有16个数据库,下标从0-15,当 set 一个数据时,默认添加到 db0,而select 命令可以切换当前使用的数据库,例:select 15 Redis
三、数据库构造
每个数据库相当于一棵树的根节点,子节点可以是Redis定义的数据类型之一,也可以是命名空间(namespace),最终子节点(叶子)一定是Redis定义的数据类型之一。
四、Redis命名空间
这个很重要,命名空间用冒号定义,例1:set test1:test2:test3 123 例2:set test1:test5 345 其中 test3和test5 是叶子 命名空间可以扩展树的深度
五、Redis 与 Json
JavaScript对象 与 Redis数据库的构造很相似,所以二者可以相互转化。
六、通配符
-
*
:匹配0个或多个字符 -
?
:匹配1个字符 -
[]
:匹配其中一个字符
通用命令
- keys [pattern]
遍历所以符合条件的key,生产环境不使用
# 以he开头的key
keys he*
# 以he开头并且后面跟着a到c字符,此时如果有heb,是可以匹配到的
keys he[a-c] # 等价于he[abc]
- dbsize
计算key的总数
# mset k1 v1 k2 v2
dbsize
# (integer) 2
sadd myset 1 2 3
dbsize
# (integer) 3
- exists key
- del key [key …]
删除key,可以删除多个。 - expire key seconds
设置过期时间(秒)
EXPIRE hello 60
#(integer) 1
ttl hello # -1表示持久化 -2表示不存在 正整数表示剩余秒数
#(integer) 57
- type key
返回key所对应的值的类型
数据结构和内部编码
- string
- raw
- int
- embstr
- hash
- hashtable
- ziplist
- list
- linkedlist
- ziplist
- set
- hashtable
- intset
- zset
- skiplist
- ziplist
字符串
# key自增1,如果key不存在,自增后get key 返回1
incr key
# 与incr相反
decr key
# 以步长为2自增
incrby key 2
# 以步长为3自增
decrby key 3
# 不管key是否存在都设置
set key value
# setIfAbsent
setnx key value
# setIfPresent
set key value xx
# 同时获取多个key值
mget k1 k2 k3 k4
# 同时set多个k-v对
mset k1 v1 k2 v2
# 原子操作,输入新值返回旧值
getset key newValue
# 追加
append key subfix
# 返回字符串长度,需要注意中文
strlen key
# 增加key对应的值3.5
incrbyfloat key 3.5
# 获取字符串指定下标所有的值
getrange key start_index end_index
# 设置指定下标所有对应的值
setrange key index value
哈希
实际上就是一个Map<String, Map<String, Object>>
# hset key field value
# 为user设置年龄&名字属性值
hset user:1 age 33
hset user:1 name yan
hget user:1 age
hget user:1 name
hdel user:1 age
hexists user:1 age
hlen user:1 name
hmset user:2 age 33 name yan
hmget user:2 age name
列表
- 有序
- 可重复
- 两边弹出
# 右边PUSH
RPUSH key v1 v2 v3
# 左边弹出
LPOP key
LPUSH key v0
LPOP key
# case count
# >0: 从左到右,删除最多count个和value相等的元素
# <0: 从右向左,删除最多Math.abs(count)个和value相等的元素
# =0: 删除所有和value相等的元素
LREM key count v2
# 获取子列表,下标范围[2-3]
LRANGE key 2 3
# 在list指定的值前/后插入newValue
linsert key [before|after] value newValue
# 按照索引范围修剪列表,保留索引范围[start-end]的元素
ltrim key start end
# 设置列表指定索引处值
lset key index newValue
# 阻塞左|右弹出 timeout为阻塞超时时间
blpop key timeout
brpop key timeout
集合
无序不重复
# 向集合key添加元素,如果存在则失败
sadd key element
# 将集合key中的元素移除
srem key element
# 展示所有元素,无序,小心使用
smembers key
# 从集合中弹出count个元素
spop key [count]
# 随机展示count个元素,不会破坏集合
srandmember key [count]
# 差集
sdiff k1 k2
# 交集
sinter k1 k2
# 并集
sunion k1 k2
有序集合
# 添加score和element,按照score从小到大
zadd key score element...
zrem key element...
# 获取key-ele所对应的分值
zscore key element
# 返回指定索引范围内的升序元素,索引指的是排名
# 这里的索引可以是负数
zrange key start end [withscores]
# 返回
zrangebyscore key min max [withscores]
# 统计在某个分值范围内的元素数量
zcount key minScore maxScore
# 根据排名批量删除
zremrangebyrank key start stop
# 根据分值批量删除
zremrangebyrank key start stop
Jedis
Jedis是java客户端的基础实现。
依赖
gradle:
compile group: 'redis.clients', name: 'jedis', version: '3.2.0'
maven:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
实现
Jedis jedis = new Jedis("127.0.0.1");
//jedis = new Jedis("127.0.0.1", 6379);
//jedis = new Jedis("127.0.0.1", 6379, 100);
JedisPool
使用CommonsPool为底层,降低频繁单点连接造成的资源消耗。
依赖与Jedis相同。
JedisPool jedisPool = new JedisPool("127.0.0.1", 6379);
// 自定义连接参数
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig<>();
// poolConfig.set...
poolConfig.setMaxIdle(20);
poolConfig.setMaxTotal(20);
poolConfig.setMinIdle(1);
poolConfig.setMaxWaitMillis(10000);
// 新建连接池
JedisPool jedisPool = new JedisPool(poolConfig, InetAddress.getLocalHost().getHostAddress());
Jedis resource = jedisPool.getResource();
// opt
System.out.println(resource.ping());
// 归还连接池
resource.close();
方案对比
优点 | 缺点 | |
直连 | 1. 简单方便 2. 适用于少量长期连接的场景 | 1. 存在每次新建、关闭TCP开销 2. 资源无法控制,存在连接泄露的可能 3. Jedis对象线程不安全 |
连接池 | 1. Jedis预先生成,降低开销使用 2. 连接池的形式保护和控制资源的使用 | 相对麻烦,尤其是在资源的管理上需要很多参数来保证,一旦规划不当也会出现问题。 |
由此可见,对于连接池的配置尤其重要。
资源池(commons-pool)参数
参数名 | 含义 | 默认值 | 使用建议 |
maxTotal | 资源池最大连接数 | 8 | |
maxIdle | 资源池允许最大空闲连接数 | 8 | |
minIdle | 资源池确保最少空闲连接数 | 0 | 建议设为正数,提前预热 |
jmxEnabled | 是否开启jmx监控,用于监控 | true | 开启 |
blockWhenExhausted | 当资源池用尽后,调用者是否要等待。只有当为true时,下面的maxWaitMillis才会生效 | true | 使用默认值 |
maxWaitMillis | 当资源池连接用尽后,调用者的最大等待时间(毫秒) | -1:永久不超时 | 不建议使用默认值 |
testOnBorrow | 向资源池借用连接时,是否做连接有效性检测(ping),无效连接会被移除 | false | false |
testOnReturn | 向资源池归还连接时,是否做连接有效性检测(ping),无效连接会被移除 | false | false |
maxTotal
- 比较难确定,这里举个例子:
- 命令平均执行时间0.1ms=0.001s。
- 业务需要50000QPS。
- maxTotal理论值=0.001*50000=50个。实际偏大一些。
- 其他因素:
- 业务希望Redis并发量
- 客户端执行命令时间
- Redis资源:例如nodes(应用个数)* maxTotal是不能超过redis的最大连接数的。(config get maxclients)
- 资源开销:例如虽然希望控制空闲连接,但是不希望因为连接池的频繁释放创建连接造成不必要的开销。
- 建议:
maxTotal=maxIdle,减少创建新连接的开销
建议预热minIdle–设置为正数,而不是默认的0,旨在减少第一次启动后的新连接开销
线程池问题解决思路
- 慢查询阻塞:池子连接都被hang住
- 资源池参数不合理:QPS高,池子小
- 连接泄露(没有close):此类问题比较难定位,例如client list、netstat等,最重要的是代码
- DNS异常等。
模拟
public static void main(String[] args) {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxIdle(1);
jedisPoolConfig.setMaxTotal(1);
// 设置获取资源最大等待时间
jedisPoolConfig.setMaxWaitMillis(1000);
JedisPool jedisPool = new JedisPool(jedisPoolConfig, "localhost");
jedisPool.getResource();
jedisPool.getResource();
}
由于第一次获取资源后没有调用Jedis::close
,即未将资源归还连接池,第二次获取时,由于不满足连接池配置条件(1.最大连接数;2.最大等待100ms)而抛出异常
Exception in thread "main" redis.clients.jedis.exceptions.JedisExhaustedPoolException: Could not get a resource since the pool is exhausted
at redis.clients.jedis.util.Pool.getResource(Pool.java:53)
at redis.clients.jedis.JedisPool.getResource(JedisPool.java:234)
at Main.main(Main.java:14)
Caused by: java.util.NoSuchElementException: Timeout waiting for idle object
at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:439)
at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:349)
at redis.clients.jedis.util.Pool.getResource(Pool.java:50)
... 2 more