目录
user:{uid}:friends:messages:{mid}
简化为 u:{uid}:fr:m:{mid}
在 Redis 中,一个字符串最大 512MB,一个二级数据结构(例如 hash、list、set、zset)可以存储大约 40 亿个 (2^32-1) 个元素,但实际中如果下面两种情况,就会认为它是 bigkey。
一般来说,string 类型控制在 10KB 以内,hash、list、set、zset 元素个数不要超过 5000。 反例:一个包含 200 万个元素的 list。
非字符串的 bigkey,不要使用 del 删除,使用 hscan、sscan、zscan 方式渐进式删除,同时要注意防止 bigkey 过期时间自动删除问题(例如一个 200 万的 zset 设置 1 小时过期,会触发 del 操作,造成阻塞)
一般来说,bigkey 的产生都是由于程序设计不当,或者对于数据规模预料不清楚造成的,来看几个例子:
拆
big list: list1、list2、…listN 就是把大列表分成几个小列表存储
big hash:可以讲数据分段存储,比如一个大的 key,假设存了 1 百万的用户数据,可以拆分成 200 个 key,每个 key 下面存放 5000 个用户数据
如果 bigkey 不可避免,也要思考一下要不要每次把所有元素都取出来(例如有时候仅仅需要 hmget,而不是 hgetall),删除也是一样,尽量使用优雅的方式来处理。
建议使用 expire 设置过期时间(条件允许可以打散过期时间,防止集中过期)。
例如 hgetall、lrange、smembers、zrange、sinter 等并非不能使用,但是需要明确 N 的值。有遍历的需求可以使用 hscan、sscan、zscan 代替。
禁止线上使用 keys、flushall、flushdb 等,通过 redis 的 rename 机制禁掉命令,或者使用 scan 的方式渐进式处理。
Redis 事务功能较弱,不建议过多使用,可以用 lua 替代
多个不相干的业务应该使用不同的 redis 服务,公共数据做服务化
可以有效控制连接数量,同时提高效率,标准使用方式如下:
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(5);
jedisPoolConfig.setMaxIdle(2);
jedisPoolConfig.setTestOnBorrow(true);
JedisPool jedisPool = new JedisPool(jedisPoolConfig, "192.168.0.60", 6379, 3000, null);
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
//具体的命令
jedis.executeCommand()
} catch (Exception e) {
logger.error("op key {} error: " + e.getMessage(), key, e);
} finally {
//注意这里不是关闭连接,在 JedisPool 模式下,Jedis 会被归还给资源池。
if (jedis != null)
jedis.close();
}
序号 | 参数名 | 含义 | 默认值 | 使用建议 |
---|---|---|---|---|
1 | maxTotal | 资源池中最大连接数 | 8 | 设置建议见下面 |
2 | maxIdle | 资源池允许最大空闲的连接数 | 8 | 设置建议见下面 |
3 | minIdle | 资源池确保最少空闲的连接数 | 0 | 设置建议见下面 |
4 | blockWhenExhausted | 当资源池用尽后,调用者是否要等待。只有当为 true 时,下面的 maxWaitMillis 才会生效 | true | 建议使用默认值 |
5 | maxWaitMillis | 当资源池连接用尽后,调用者的最大等待时间(单位为毫秒) | -1:表示永不超时 | 不建议使用默认值 |
6 | testOnBorrow | 向资源池借用连接时是否做连接有效性检测 (ping),无效连接会被移除 | false | 业务量很大时候建议设置为 false(多一次 ping 的开销)。 |
7 | testOnReturn | 向资源池归还连接时是否做连接有效性检测 (ping),无效连接会被移除 | false | 业务量很大时候建议设置为 false(多一次 ping 的开销)。 |
8 | jmxEnabled | 是否开启 jmx 监控,可用于监控 | true | 建议开启,但应用本身也要开启 |
如何设置这个值是比较难回答的,没有固定的计算方式,考虑的因素比较多:
例子:
假设:一次命令时间(borrow|return resource + Jedis 执行命令(含网络) )的平均耗时约为 1ms,一个连接的 QPS 大约是 1000, 业务期望的 QPS 是 50000
那么理论上需要的资源池大小是 50000 / 1000 = 50 个。但事实上这是个理论值,还要考虑到要比理论值预留一些资源,通常来讲 maxTotal 可以比理论值大一些。
但这个值不是越大越好,一方面连接太多占用客户端和服务端资源,另一方面对于 Redis 这种高 QPS 的服务器,一个大命令的阻塞即使设置再大资源池仍然会无济于事。
maxIdle 实际上才是业务需要的最大连接数,maxTotal 是为了给出结余量,所以 maxIdele 不要设置的太小,否则会不断的发生新建连接,释放连接的开销。
连接池的最佳性能是 maxTotal = maxIdle 这样就避免连接池伸缩带来的性能干扰,但是在并发量不大的时候,或者 maxTotal 设置过高,会导致不必要的连接资源浪费,一般推荐 maxIdle 按照上面的计算方式设置,maxTotal 可以再放大一倍。
minIdle(最小空闲连接数),与其说是最小空闲连接数,不如说是"至少需要保持的空闲连接数",在使用连接的过程中,如果连接数超过了 minIdle,那么继续建立连接,如果超过了 maxIdle,当超过的连接执行完业务后会慢慢被移出连接池释放掉
如果系统刚启动完,就马上有很多请求过来,那么可以给连接池做预热,比如快速的创建一些 redis 连接,执行简单命令,如 ping, 快速的将连接池中的连接提升到 minIdel 的数量
示例代码:
List<Jedis> minIdleJedisList = new ArrayList<Jedis>(jedisPoolConfig.getMinIdle());
for (int i = 0; i < jedisPoolConfig.getMinIdle(); i++) {
Jedis jedis = null;
try {
jedis = pool.getResource();
minIdleJedisList.add(jedis);
jedis.ping();
} catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {
//注意,这里不能马上 close 将连接还回连接池,否则最后连接池里只会建立 1 个连接。
//jedis.close();
}
}
//统一将预热的连接还回连接池
for (int i = 0; i < jedisPoolConfig.getMinIdle(); i++) {
Jedis jedis = null;
try {
jedis = minIdleJedisList.get(i);
//将连接归还回连接池,注意这里是规范连接,而不是关闭
jedis.close();
} catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {
}
}
swap 对操作系统比较重要,当物理内存不足时,可以将一部分内存也 swap 到硬盘上,已解燃眉之急,对于需要高并发,高吞吐的应用来说,磁盘 IO 通常会成为系统的瓶颈,再 Linux 中,并不是等所有的物理内存使用完后才用到 swap。系统参数 swapniess 决定了操作系统使用 swap 的倾向程度,swapniess 取值范围是 0-100,值越大,说明操作系统使用 swapniess 的概率越高。值越低,表示操作系统更加倾向于使用物理内存。
如果内核版本 < 3.5, 那么 swapniess 的值为 0,系统宁愿 swap 也不会 oom killer (杀掉进程)
如果内核版本 >= 3.5, 那么 swapniess 的值为 1,系统宁愿 swap 也不会 oom killer (杀掉进程)
OOM killer 机制是指 Linux 操作系统发现可用内存不足时,强制杀死一些用户进程(非内核进程),来保证系统有足够的可用内存进行分配。
一般要保证 redis 不会被 kill
先查看系统版本,再设值
cat /proc/version
echo 1 > /proc/sys/vm/swapniess
echo vm.swapniess=1 >> /etc/sysctl.conf
默认值是 0
如果是 0 的话,可能导致类似于 fork 等操作失败,申请不到足够的内存空间
Redis 建议把这个值设置为 1,就是为了让 fork 再低内存下也能运行
cat /proc/sys/vm/overcommit_memory
echo "vm.overcommit_memory=1" >> /etc/sysctl.conf
sysctl vm.overcommit_memory=1
操作系统进程试图打开一个文件(或者叫句柄),但是现在进程打开的句柄数已经达到了上限,继续打开会报错:“Too many open files”
ulimit -a #查看系统文件句柄数,看 open files 那项
ulimit -n 65535 #设置系统文件句柄数
Redis 慢日志命令说明:
config get slow* #查询有关慢日志的配置信息
config set slowlog-log-slower-than 20000 #设置慢日志使时间阈值,单位微秒,此处为 20 毫秒,即超过 20 毫秒的操作都会记录下来,生产环境建议设置 1000,也就是 1ms,这样理论上 redis 并发至少达到 1000,如果要求单机并发达到 1 万以上,这个值可以设置为 100
config set slowlog-max-len 1024 #设置慢日志记录保存数量,如果保存数量已满,会删除最早的记录,最新的记录追加进来。记录慢查询日志时 Redis 会对长命令做截断操作,并不会占用大量内存,建议设置稍大些,防止丢失日志
config rewrite #将服务器当前所使用的配置保存到 redis.conf
slowlog len #获取慢查询日志列表的当前长度
slowlog get 5 #获取最新的 5 条慢查询日志。慢查询日志由四个属性组成:标识 ID,发生时间戳,命令耗时,执行命令和参数
slowlog reset #重置慢查询日志