设为首页 收藏本站
查看: 1046|回复: 0

[经验分享] 美团在Redis上踩过的一些坑-2.bgrewriteaof问题

[复制链接]

尚未签到

发表于 2016-12-21 08:13:16 | 显示全部楼层 |阅读模式
   转载请注明出处哈:http://carlosfu.iyunv.com/blog/2254154


    
  一、背景
  1. AOF:
  Redis的AOF机制有点类似于Mysql binlog,是Redis的提供的一种持久化方式(另一种是RDB),它会将所有的写命令按照一定频率(no, always, every seconds)写入到日志文件中,当Redis停机重启后恢复数据库。
DSC0000.jpg

  2. AOF重写:
  (1) 随着AOF文件越来越大,里面会有大部分是重复命令或者可以合并的命令(100次incr = set key 100)
  (2) 重写的好处:减少AOF日志尺寸,减少内存占用,加快数据库恢复时间。
DSC0001.jpg

  二、单机多实例可能存在Swap和OOM的隐患:
  由于Redis的单线程模型,理论上每个redis实例只会用到一个CPU, 也就是说可以在一台多核的服务器上部署多个实例(实际就是这么做的)。但是Redis的AOF重写是通过fork出一个Redis进程来实现的,所以有经验的Redis开发和运维人员会告诉你,在一台服务器上要预留一半的内存(防止出现AOF重写集中发生,出现swap和OOM)。
DSC0002.jpg

  三、最佳实践
  1. meta信息:作为一个redis云系统,需要记录各个维度的数据,比如:业务组、机器、实例、应用、负责人多个维度的数据,相信每个Redis的运维人员都应该有这样的持久化数据(例如Mysql),一般来说还有一些运维界面,为自动化和运维提供依据
  例如如下:
DSC0003.jpg

DSC0004.jpg

DSC0005.jpg

  2. AOF的管理方式:
  (1) 自动:让每个redis决定是否做AOF重写操作(根据auto-aof-rewrite-percentage和auto-aof-rewrite-min-size两个参数):
DSC0006.png

  (2) crontab: 定时任务,可能仍然会出现多个redis实例,属于一种折中方案。
  (3) remote集中式:
  最终目标是一台机器一个时刻,只有一个redis实例进行AOF重写。
  具体做法其实很简单,以机器为单位,轮询每个机器的实例,如果满足条件就运行(比如currentSize和baseSize满足什么关系)bgrewriteaof命令。
  期间可以监控发生时间、耗时、频率、尺寸的前后变化            
DSC0007.jpg


策略优点缺点
自动无需开发  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();
}
}

  附图一张:
DSC0008.jpg


 

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-317097-1-1.html 上篇帖子: (分析比较到位)NoSql 分析 hbase,mongodb,redis 下篇帖子: jedis使用线程池封装redis基本操作
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表