Redis开发规范与性能优化(二)

开发规范与性能优化

3.客户端使用

1.【推荐】避免多个应用使用一个Redis示例

正例:不相干的业务拆分,公共数据库做服务化

2.【推荐】使用带有连接池的数据库,可以有效控制链接,同时提高效率,标准使用方式如代码所示

java 复制代码
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地数量
java 复制代码
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掉

c 复制代码
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操作能够在低内存下也能执行成功
c 复制代码
cat /proc/sys/vm/overcommit_memory
echo "vm.overcoimmit_memory=1" >> /etc/sysctl.conf
sysctl vm.overcommit_memory =1

合理设置文件句柄数。

操作系统进程试图打开一个文件(或者叫句柄),但是现在进程打开的句柄数已经达到了上限,继续打开会报错"Too many open files"

c 复制代码
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 #重置慢查询

相关推荐
CoderIsArt1 小时前
Redis的三种模式:主从模式,哨兵与集群模式
数据库·redis·缓存
XiaoLeisj2 小时前
【JavaEE初阶 — 多线程】单例模式 & 指令重排序问题
java·开发语言·java-ee
paopaokaka_luck2 小时前
【360】基于springboot的志愿服务管理系统
java·spring boot·后端·spring·毕业设计
dayouziei2 小时前
java的类加载机制的学习
java·学习
师太,答应老衲吧3 小时前
SQL实战训练之,力扣:2020. 无流量的帐户数(递归)
数据库·sql·leetcode
Yaml44 小时前
Spring Boot 与 Vue 共筑二手书籍交易卓越平台
java·spring boot·后端·mysql·spring·vue·二手书籍
小小小妮子~4 小时前
Spring Boot详解:从入门到精通
java·spring boot·后端
hong1616884 小时前
Spring Boot中实现多数据源连接和切换的方案
java·spring boot·后端
wheeldown4 小时前
【数据结构】选择排序
数据结构·算法·排序算法
Channing Lewis4 小时前
salesforce case可以新建一个roll up 字段,统计出这个case下的email数量吗
数据库·salesforce