转载请注明出处哈:http://carlosfu.iteye.com/blog/2254154
一、背景
1. AOF:
Redis的AOF机制有点类似于Mysql binlog,是Redis的提供的一种持久化方式(另一种是RDB),它会将所有的写命令按照一定频率(no, always, every seconds)写入到日志文件中,当Redis停机重启后恢复数据库。
2. AOF重写:
(1) 随着AOF文件越来越大,里面会有大部分是重复命令或者可以合并的命令(100次incr = set key 100)
(2) 重写的好处:减少AOF日志尺寸,减少内存占用,加快数据库恢复时间。
二、单机多实例可能存在Swap和OOM的隐患:
由于Redis的单线程模型,理论上每个redis实例只会用到一个CPU, 也就是说可以在一台多核的服务器上部署多个实例(实际就是这么做的)。但是Redis的AOF重写是通过fork出一个Redis进程来实现的,所以有经验的Redis开发和运维人员会告诉你,在一台服务器上要预留一半的内存(防止出现AOF重写集中发生,出现swap和OOM)。
三、最佳实践
1. meta信息:作为一个redis云系统,需要记录各个维度的数据,比如:业务组、机器、实例、应用、负责人多个维度的数据,相信每个Redis的运维人员都应该有这样的持久化数据(例如Mysql),一般来说还有一些运维界面,为自动化和运维提供依据
例如如下:
2. AOF的管理方式:
(1) 自动:让每个redis决定是否做AOF重写操作(根据auto-aof-rewrite-percentage和auto-aof-rewrite-min-size两个参数):
(2) crontab: 定时任务,可能仍然会出现多个redis实例,属于一种折中方案。
(3) remote集中式:
最终目标是一台机器一个时刻,只有一个redis实例进行AOF重写。
具体做法其实很简单,以机器为单位,轮询每个机器的实例,如果满足条件就运行(比如currentSize和baseSize满足什么关系)bgrewriteaof命令。
期间可以监控发生时间、耗时、频率、尺寸的前后变化
策略 | 优点 | 缺点 |
自动 | 无需开发 |
1. 有可能出现(无法预知)上面提到的Swap和OOM 2. 出了问题,处理起来其实更费时间。 |
AOF控制中心(remote集中式) |
1. 防止上面提到Swap和OOM。 2. 能够收集更多的数据(aof重写的发生时间、耗时、频率、尺寸的前后变化),更加有利于运维和定位问题(是否有些机器的实例需要拆分)。 |
控制中心需要开发。 |
一台机器轮询执行bgRewriteAof代码示例:
package com.sohu.cache.inspect.impl; import com.sohu.cache.alert.impl.BaseAlertService; import com.sohu.cache.entity.InstanceInfo; import com.sohu.cache.inspect.InspectParamEnum; import com.sohu.cache.inspect.Inspector; import com.sohu.cache.util.IdempotentConfirmer; import com.sohu.cache.util.TypeUtil; import org.apache.commons.collections.MapUtils; import org.apache.commons.lang.StringUtils; import redis.clients.jedis.Jedis; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; public class RedisIsolationPersistenceInspector extends BaseAlertService implements Inspector { public static final int REDIS_DEFAULT_TIME = 5000; @Override public boolean inspect(Map<InspectParamEnum, Object> paramMap) { // 某台机器和机器下所有redis实例 final String host = MapUtils.getString(paramMap, InspectParamEnum.SPLIT_KEY); List<InstanceInfo> list = (List<InstanceInfo>) paramMap.get(InspectParamEnum.INSTANCE_LIST); // 遍历所有的redis实例 for (InstanceInfo info : list) { final int port = info.getPort(); final int type = info.getType(); int status = info.getStatus(); // 非正常节点 if (status != 1) { continue; } if (TypeUtil.isRedisDataType(type)) { Jedis jedis = new Jedis(host, port, REDIS_DEFAULT_TIME); try { // 从redis info中索取持久化信息 Map<String, String> persistenceMap = parseMap(jedis); if (persistenceMap.isEmpty()) { logger.error("{}:{} get persistenceMap failed", host, port); continue; } // 如果正在进行aof就不做任何操作,理论上要等待它完毕,否则 if (!isAofEnabled(persistenceMap)) { continue; } // 上一次aof重写后的尺寸和当前aof的尺寸 long aofCurrentSize = MapUtils.getLongValue(persistenceMap, "aof_current_size"); long aofBaseSize = MapUtils.getLongValue(persistenceMap, "aof_base_size"); // 阀值大于60% long aofThresholdSize = (long) (aofBaseSize * 1.6); double percentage = getPercentage(aofCurrentSize, aofBaseSize); // 大于60%且超过60M if (aofCurrentSize >= aofThresholdSize && aofCurrentSize > (64 * 1024 * 1024)) { // bgRewriteAof 异步操作。 boolean isInvoke = invokeBgRewriteAof(jedis); if (!isInvoke) { logger.error("{}:{} invokeBgRewriteAof failed", host, port); continue; } else { logger.warn("{}:{} invokeBgRewriteAof started percentage={}", host, port, percentage); } // 等待Aof重写成功(bgRewriteAof是异步操作) while (true) { try { // before wait 1s TimeUnit.SECONDS.sleep(1); Map<String, String> loopMap = parseMap(jedis); Integer aofRewriteInProgress = MapUtils.getInteger(loopMap, "aof_rewrite_in_progress", null); if (aofRewriteInProgress == null) { logger.error("loop watch:{}:{} return failed", host, port); break; } else if (aofRewriteInProgress <= 0) { // bgrewriteaof Done logger.warn("{}:{} bgrewriteaof Done lastSize:{}Mb,currentSize:{}Mb", host, port, getMb(aofCurrentSize), getMb(MapUtils.getLongValue(loopMap, "aof_current_size"))); break; } else { // wait 1s TimeUnit.SECONDS.sleep(1); } } catch (Exception e) { logger.error(e.getMessage(), e); } } } else { if (percentage > 50D) { long currentSize = getMb(aofCurrentSize); logger.info("checked {}:{} aof increase percentage:{}% currentSize:{}Mb", host, port, percentage, currentSize > 0 ? currentSize : "<1"); } } } finally { jedis.close(); } } } return true; } private long getMb(long bytes) { return (long) (bytes / 1024 / 1024); } private boolean isAofEnabled(Map<String, String> infoMap) { Integer aofEnabled = MapUtils.getInteger(infoMap, "aof_enabled", null); return aofEnabled != null && aofEnabled == 1; } private double getPercentage(long aofCurrentSize, long aofBaseSize) { if (aofBaseSize == 0) { return 0.0D; } String format = String.format("%.2f", (Double.valueOf(aofCurrentSize - aofBaseSize) * 100 / aofBaseSize)); return Double.parseDouble(format); } private Map<String, String> parseMap(final Jedis jedis) { final StringBuilder builder = new StringBuilder(); boolean isInfo = new IdempotentConfirmer() { @Override public boolean execute() { String persistenceInfo = null; try { persistenceInfo = jedis.info("Persistence"); } catch (Exception e) { logger.warn(e.getMessage() + "-{}:{}", jedis.getClient().getHost(), jedis.getClient().getPort(), e.getMessage()); } boolean isOk = StringUtils.isNotBlank(persistenceInfo); if (isOk) { builder.append(persistenceInfo); } return isOk; } }.run(); if (!isInfo) { logger.error("{}:{} info Persistence failed", jedis.getClient().getHost(), jedis.getClient().getPort()); return Collections.emptyMap(); } String persistenceInfo = builder.toString(); if (StringUtils.isBlank(persistenceInfo)) { return Collections.emptyMap(); } Map<String, String> map = new LinkedHashMap<String, String>(); String[] array = persistenceInfo.split("\r\n"); for (String line : array) { String[] cells = line.split(":"); if (cells.length > 1) { map.put(cells[0], cells[1]); } } return map; } public boolean invokeBgRewriteAof(final Jedis jedis) { return new IdempotentConfirmer() { @Override public boolean execute() { try { String response = jedis.bgrewriteaof(); if (response != null && response.contains("rewriting started")) { return true; } } catch (Exception e) { String message = e.getMessage(); if (message.contains("rewriting already")) { return true; } logger.error(message, e); } return false; } }.run(); } }
附图一张:
相关推荐
在使用Redis的过程中,他们遇到了一些问题,特别是关于Redis内存占用飙升的问题。下面我们将深入探讨这个问题以及可能的解决方案。 Redis内存占用飙升的原因多种多样,可能是由于以下几点: 1. **数据结构不当**:...
redis-5.0.14-1.el7.remi.x86_64.rpm安装包(含有部署手册) redis-5.0.14-1.el7.remi.x86_64.rpm安装包(含有部署手册) redis-5.0.14-1.el7.remi.x86_64.rpm安装包(含有部署手册) redis-5.0.14-1.el7.remi.x86_64.rpm...
redis-5.0.5.redis-5.0.5.redis-5.0.5.redis-5.0.5.redis-5.0.5.redis-5.0.5.redis-5.0.5.redis-5.0.5.redis-5.0.5.redis-5.0.5.redis-5.0.5.redis-5.0.5.redis-5.0.5.redis-5.0.5.redis-5.0.5.redis-5.0.5.redis-...
2. **服务化**:为了使Redis在系统启动时自动运行,可以将`redis-server.exe`配置为Windows服务。这可以通过命令行工具`sc create`或使用`.msi`安装包实现。 3. **数据持久化**:Redis支持多种持久化方式,包括RDB...
- 可以通过`redis-benchmark.exe`进行性能测试,评估Redis在当前配置下的性能表现。 - 使用`redis-check-aof.exe`和`redis-check-rdb.exe`定期检查数据文件的完整性,确保数据安全。 5. **注意事项**: - ...
2. 解压:`tar -zxvf redis-2.8.13.tar.gz` 3. 编译:`cd redis-2.8.13`,然后`make` 4. 安装:`sudo make install` 5. 启动Redis服务:`src/redis-server` 6. 配置:可以通过修改`redis.conf`来配置Redis服务,例如...
赠送jar包:spring-session-data-redis-2.0.4.RELEASE.jar; 赠送原API文档:spring-session-data-redis-2.0.4.RELEASE-javadoc.jar; 赠送源代码:spring-session-data-redis-2.0.4.RELEASE-sources.jar; 赠送...
- **Redis on Windows Release Notes.docx**:这份文档详细记录了Redis在Windows平台上的发行说明,包括新特性、改进和已知问题。 - **Redis on Windows.docx**:可能包含关于在Windows环境下安装、配置和使用...
redis-stack-server-7.2.0-v9.arm64.snap redis-stack-server-7.2.0-v9.bionic.arm64.tar.gz redis-stack-server-7.2.0-v9.bionic.x86_64.tar.gz redis-stack-server-7.2.0-v9.bullseye.x86_64.tar.gz redis-stack-...
tomcat-redis-session-manager-2.0.0.jar,可用于Tomcat8下Redis的Session共享,亲测可用,还需要下载另外两个jar包:commons-pool2-2.4.2.jar和jedis-2.9.0.jar,maven仓库有,此处不再上传
在Redis-x64-5.0.14.1版本中,可能包括以下内容: - `redis-server`: Redis服务器进程,负责处理客户端请求。 - `redis-cli`: 官方提供的命令行客户端,用于与Redis服务器交互。 - `redis.conf`: 默认配置文件,...
redis可视化工具redis-desktop-manager-0.8.8.384。。。。
通过这个压缩包中的"redis-desktop-manager-0.9.3.817.exe"文件,用户可以安装和运行RedisDesktopManager。该可执行文件是经过编译的Windows程序,包含了所有必要的库和资源,使得用户无需额外配置环境即可直接使用...
windows系统redis安装文件,Redis-x64-3.0.504(稳定版);Redis-x64-3.2.100(预发行版);redis-desktop-manager-0.9.3.817(redis界面工具)。具体安装方法详见:...
在 Windows 环境下,通常需要通过安装过程来设置 Redis 服务,但这里提供的资源是“redis 免安装”,意味着我们可以跳过常规的安装步骤,直接使用。 首先,让我们详细了解 Redis 的核心概念和特性: 1. **键值存储...
spring-data-redis-1.8.1.RELEASE-sources.jar(spring-data-redis-1.8.1.RELEASE-sources.jar()
在给定的压缩包文件中,我们有两个与Redis相关的组件:`redis-desktop-manager-0.9.0.616.exe` 和 `Redis-x64-3.0.504.zip`。 1. **redis-desktop-manager-0.9.0.616.exe**: 这是一个Redis桌面管理器的可执行文件...
2. **Windows版**: 通常Redis是为Linux设计的,但通过移植,也可以在Windows操作系统上运行。这个压缩包针对Windows环境进行了优化,使得Windows用户也能利用Redis的强大功能。 3. **run-sentinel.bat**: 这个...
在 `redis-stack-server-6.2.6-v7` 压缩包中,我们可以期待找到以下关键组件: 1. **Redis Server**: 核心的键值存储服务,提供高速的数据读写操作。 2. **Redis Sentinel**: 高可用性解决方案,监控、故障检测以及...