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 #重置慢查询

相关推荐
VALENIAN瓦伦尼安教学设备3 分钟前
设备对中不良的危害
数据库·嵌入式硬件·算法
yyt3630458415 分钟前
spring单例bean线程安全问题讨论
java·spring
小兔崽子去哪了15 分钟前
Docker 安装 PostgreSQL
数据库·后端·postgresql
野犬寒鸦19 分钟前
Redis热点key问题解析与实战解决方案(附大厂实际方案讲解)
服务器·数据库·redis·后端·缓存·bootstrap
我是大猴子25 分钟前
事务失效的几种情况以及是为什么(详解)
java·开发语言
mldlds44 分钟前
Windows安装Redis图文教程
数据库·windows·redis
Nyarlathotep01131 小时前
Redis的对象(5):有序集合对象
redis·后端
feng68_1 小时前
Redis架构实践
linux·运维·redis·架构·bootstrap
wertyuytrewm1 小时前
Java面试——Java基础
java·jvm·面试
czlczl200209251 小时前
RAG实现思路流程
java·jvm