开发规范与性能优化
3.客户端使用
1.【推荐】避免多个应用使用一个Redis示例
正例:不相干的业务拆分,公共数据库做服务化
2.【推荐】使用带有连接池的数据库,可以有效控制链接,同时提高效率,标准使用方式如代码所示
public class Main {
public static void main(String[] args){
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) {
log.error("op key {} error:{}", key, e);
} finally {
//注意这里不是关闭链接,在JedisPool模式下,Jedis会被归还给资源池
if (jedis != null) {
jedis.close()
}
}
}
}
连接池参数含义:
优化建议:
- 1.maxTotal:最大连接数,早期的版本叫maxActive实际上这个是一个很难回答的问题,
考虑的因素比较多:
1.1 业务希望Redis并发量
1.2 客户端执行命令时间
1.3 Redis资源:例如nodes(例如应用个数) * maxTotal是不能超过redis的最大连接数maxclients
1.4 资源开销:例如虽然希望控制空闲连接(连接池此可可马上使用的连接),但是不希望因为连接池
的频繁释放创建连接造成不必要开销
举个例子:假设
- 1.一次命令时间(borrow|return resource + Jedis执行命令(含网络))的平均耗时约为1ms,一个链接的QPS大约是1000
- 2.业务期望的QPS是50000那么理论上需要的资源池大小是50000 / 1000 = 50 个。但事实上这是个理论值,还要考虑到要比理论值预留一些资源,通常来讲maxTotal可以比理论值大一些。但这个值不是越大越好,一方面连接太多占用客户端和服务端资源,另一方面对于Redis这种高QPS的服务器,一个大命令的阻塞即时设置再大资源池仍然会无济于事。
优化建议:
- 2.maxIdle和minIdle
maxIdle实际上才是业务需要的最大连接数,maxTotal是为了给出雨量,所以maxIdle不要设置过小,否则会有new Jedis(新连接)开销
连接池的最佳性能是maxTotal = maxIdle,这样就避免连接池伸缩带来的性能干扰,到那时如果并发量不大或者maxTotal设置过高,会导致不必要的连接资源浪费。一般推荐maxIdle可以设置为上面的业务期望QPS计算出来的理论连接数,maxTotal可以再放大一倍。minIdle()最小空闲连接数,与其说是最小空闲连接数,不如说是"至少需要保持的空闲连接数",在使用连接的过程中如果连接数超过了minIdle那么继续建立连接,如果超过了maxIdle,当超过的连接执行完业务后会慢慢被移除连接池释放掉,如果系统启动完马上就会有很多的请求过来,那么可以给redis连接池
做预热,比如快速的创建一些redis连接,执行简单命令,类似ping(),快速地将连接池里地空闲连接提升到minIdle地数量
public class Main {
public static void main(String[] args){
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) {
log.error(e.getMessage(), e);
} finally {
// 注意,这里不能马上close将连接还回连接池,
// 否则最后连接池里只会建立1个连接
// jedis.close();
}
}
// 同一将预热的连接还回连接池
for (int i = 0; i < jedisPoolConfig.getMinDile(); i++) {
Jedis jedis = null;
try {
jedis = minIdleJedisList.get(i);
// 将连接该归还回连接池
jedis.close();
} catch (Exception e) {
log.error(e.getMessage(), e);
} finally {
}
}
}
}
3.【建议】高并发下建议客户端添加熔断功能(例如sentinel、hystrix)
4.【推荐】设置合理的密码,如有必要可以使用SSL加密访问
5.【建议】Redis对于过期键有三种清除策略:
- 5.1 被动删除:当读/写一个已经过期的key时,会触发惰性删除策略,直接删除掉这个过期key
- 5.2 主动删除:由于惰性删除策略无法保证冷数据被即时删除,所以Redis会定期主动淘汰一批已过期的key
- 5.3 当前医用内存超过maxmemory限定时,触发主动清理策略
主动清理策略在Redis4.0之前以供实现了6中内存淘汰策略,在4.0之后又加了2种策略,总共8种:
- a.针对设置了过期时间的key做处理
a.1 volatile-ttl 在筛选时,会针对设置了过期时间的键值对,根据过期时间的先后进行删除,越早
过期的越先被删除
a.2 volatile-random 就像它的名称一样,在设置了过期时间的键值对种,进行随机删除
a.3 volatile-lru 会使用LRU算法筛选设置了过期时间的键值对删除
a.4 volatile-lfu 会使用LFU算法筛选设置了过期时间的键值对删除 - b.针对所有的key做处理
b.1 allkeys-random:从所有键值对中随机选择并删除数据
b.2 allkeys-lru 使用LRU算法在所有数据中进行筛选删除
b.3 allkeys-lfu 使用LFU算法在所有数据中进行筛选删除 - c.不处理
c.1 noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息error OOM command not allowed when used memory,此时Redis只相应读操作
当存在热点数据时,LRU的效率很好,但偶发性的、周期性的批量操作会导致LRU命中率急剧下降,缓存污染情况比较严重。这时使用LFU可能更好点.
根据自身业务类型,配置好maxmemory-policy(默认时noeviction)推荐使用volatile-lru.如果不设置最大内存,当Redis内存超出物理内存限制时,内存的数据会开始和磁盘产生频繁的交换(swap)会让redis的性能急剧下降。当Redis运行在主从模式时,只有主节点才会执行过期删除策略,然后把删除操作"del key"同步到从节点删除数据
LRU算法(least Recently Used,最近最少使用)
淘汰很久没有被访问过的数据,以最近一次访问时间作为参考
LFU算法(Least Frequently Used,最不经常使用)
淘汰最近一段时间被访问次数最少的数据,以次数作为参考
4.系统内核参数优化
vm.swapiness
swap对于操作系统来说比较重要,当物理内存不足时可以将一部分内存页进行swap到硬盘上,以解燃眉之急。但世界上没有免费午餐,swap空间由硬盘提供,对于需要高并发、高吞吐的应用来说,磁盘IO通常会称为系统瓶颈。在Linux中,并不是等到所有物理内存都使用完才会使用到swap,
系统参数swappniess会决定操作系统使用swap的倾向程度。swappiness的取只范围是0~100,swappiness的值越大,说明操作系统可能使用swap的概率越高,swappiness值越低,表示操作系统更加倾向于使用物理内存。swappiness的取值越大,说明操作系统可能使用swap的概率越高,越低则越倾向于使用物理内存。如果linux内核版本小于3.5 那么wappiness设置为0,这样系统宁愿swap不会oom killer(杀掉进程)
如果linux内核版本>=3.5,那么swappiness设置为1,这样系统宁愿swap也不会oom killer一般需要保证redis不会被kill掉
cat /proc/version # 查看linux内核版本
echo 1> /proc/sys/vm/swappiness
echo vm.swappiness = 1 >> /etc/sysctl.cnf
PS:OOM killer机制是指Linux操作系统发现可用内存不足时,强制杀死一些用户进程(非内核进程),来保证系统有足够的可用内存进行分配
vm.overcommit_memory(默认0)
- 0:表示内核将检查是否有足够的可用物理内存(实际不一定用满)供应用进程使用;如果有足够的可用物理内存,内存申请允许;否则,内存申请失败,并把错误返回给应用进程
- 1:表示内核允许分配所有的物理内存,而不管当前的内存状态如何,如果是0的化,可能导致类似fork等操作执行失败,申请不倒足够的内存空间
Redis建议把这个值设置为1,就是为了让fork操作能够在低内存下也能执行成功
cat /proc/sys/vm/overcommit_memory
echo "vm.overcoimmit_memory=1" >> /etc/sysctl.conf
sysctl vm.overcommit_memory =1
合理设置文件句柄数。
操作系统进程试图打开一个文件(或者叫句柄),但是现在进程打开的句柄数已经达到了上限,继续打开会报错"Too many open files"
ulimit -a # 查看系统文件句柄数,看open files选项
ulimt -n 65535 # 设置系统文件句柄数
慢查询日志:slowlog
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 #重置慢查询