- 浏览: 67741 次
- 性别:
- 来自: 宜宾
文章分类
最新评论
Redis上踩过的一些坑-美团
上上周和同事(龙哥)参加了360组织的互联网技术训练营第三期,美团网的DBA负责人侯军伟给大家介绍了美团网在redis上踩得一些坑,讲的都是干货和坑。
一、背景
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代码示例:
- packagecom.sohu.cache.inspect.impl;
- importcom.sohu.cache.alert.impl.BaseAlertService;
- importcom.sohu.cache.entity.InstanceInfo;
- importcom.sohu.cache.inspect.InspectParamEnum;
- importcom.sohu.cache.inspect.Inspector;
- importcom.sohu.cache.util.IdempotentConfirmer;
- importcom.sohu.cache.util.TypeUtil;
- importorg.apache.commons.collections.MapUtils;
- importorg.apache.commons.lang.StringUtils;
- importredis.clients.jedis.Jedis;
- importjava.util.Collections;
- importjava.util.LinkedHashMap;
- importjava.util.List;
- importjava.util.Map;
- importjava.util.concurrent.TimeUnit;
- publicclassRedisIsolationPersistenceInspectorextendsBaseAlertServiceimplementsInspector{
- publicstaticfinalintREDIS_DEFAULT_TIME=5000;
- @Override
- publicbooleaninspect(Map<InspectParamEnum,Object>paramMap){
- //某台机器和机器下所有redis实例
- finalStringhost=MapUtils.getString(paramMap,InspectParamEnum.SPLIT_KEY);
- List<InstanceInfo>list=(List<InstanceInfo>)paramMap.get(InspectParamEnum.INSTANCE_LIST);
- //遍历所有的redis实例
- for(InstanceInfoinfo:list){
- finalintport=info.getPort();
- finalinttype=info.getType();
- intstatus=info.getStatus();
- //非正常节点
- if(status!=1){
- continue;
- }
- if(TypeUtil.isRedisDataType(type)){
- Jedisjedis=newJedis(host,port,REDIS_DEFAULT_TIME);
- try{
- //从redisinfo中索取持久化信息
- Map<String,String>persistenceMap=parseMap(jedis);
- if(persistenceMap.isEmpty()){
- logger.error("{}:{}getpersistenceMapfailed",host,port);
- continue;
- }
- //如果正在进行aof就不做任何操作,理论上要等待它完毕,否则
- if(!isAofEnabled(persistenceMap)){
- continue;
- }
- //上一次aof重写后的尺寸和当前aof的尺寸
- longaofCurrentSize=MapUtils.getLongValue(persistenceMap,"aof_current_size");
- longaofBaseSize=MapUtils.getLongValue(persistenceMap,"aof_base_size");
- //阀值大于60%
- longaofThresholdSize=(long)(aofBaseSize*1.6);
- doublepercentage=getPercentage(aofCurrentSize,aofBaseSize);
- //大于60%且超过60M
- if(aofCurrentSize>=aofThresholdSize&&aofCurrentSize>(64*1024*1024)){
- //bgRewriteAof异步操作。
- booleanisInvoke=invokeBgRewriteAof(jedis);
- if(!isInvoke){
- logger.error("{}:{}invokeBgRewriteAoffailed",host,port);
- continue;
- }else{
- logger.warn("{}:{}invokeBgRewriteAofstartedpercentage={}",host,port,percentage);
- }
- //等待Aof重写成功(bgRewriteAof是异步操作)
- while(true){
- try{
- //beforewait1s
- TimeUnit.SECONDS.sleep(1);
- Map<String,String>loopMap=parseMap(jedis);
- IntegeraofRewriteInProgress=MapUtils.getInteger(loopMap,"aof_rewrite_in_progress",null);
- if(aofRewriteInProgress==null){
- logger.error("loopwatch:{}:{}returnfailed",host,port);
- break;
- }elseif(aofRewriteInProgress<=0){
- //bgrewriteaofDone
- logger.warn("{}:{}bgrewriteaofDonelastSize:{}Mb,currentSize:{}Mb",host,port,
- getMb(aofCurrentSize),
- getMb(MapUtils.getLongValue(loopMap,"aof_current_size")));
- break;
- }else{
- //wait1s
- TimeUnit.SECONDS.sleep(1);
- }
- }catch(Exceptione){
- logger.error(e.getMessage(),e);
- }
- }
- }else{
- if(percentage>50D){
- longcurrentSize=getMb(aofCurrentSize);
- logger.info("checked{}:{}aofincreasepercentage:{}%currentSize:{}Mb",host,port,
- percentage,currentSize>0?currentSize:"<1");
- }
- }
- }finally{
- jedis.close();
- }
- }
- }
- returntrue;
- }
- privatelonggetMb(longbytes){
- return(long)(bytes/1024/1024);
- }
- privatebooleanisAofEnabled(Map<String,String>infoMap){
- IntegeraofEnabled=MapUtils.getInteger(infoMap,"aof_enabled",null);
- returnaofEnabled!=null&&aofEnabled==1;
- }
- privatedoublegetPercentage(longaofCurrentSize,longaofBaseSize){
- if(aofBaseSize==0){
- return0.0D;
- }
- Stringformat=String.format("%.2f",(Double.valueOf(aofCurrentSize-aofBaseSize)*100/aofBaseSize));
- returnDouble.parseDouble(format);
- }
- privateMap<String,String>parseMap(finalJedisjedis){
- finalStringBuilderbuilder=newStringBuilder();
- booleanisInfo=newIdempotentConfirmer(){
- @Override
- publicbooleanexecute(){
- StringpersistenceInfo=null;
- try{
- persistenceInfo=jedis.info("Persistence");
- }catch(Exceptione){
- logger.warn(e.getMessage()+"-{}:{}",jedis.getClient().getHost(),jedis.getClient().getPort(),
- e.getMessage());
- }
- booleanisOk=StringUtils.isNotBlank(persistenceInfo);
- if(isOk){
- builder.append(persistenceInfo);
- }
- returnisOk;
- }
- }.run();
- if(!isInfo){
- logger.error("{}:{}infoPersistencefailed",jedis.getClient().getHost(),jedis.getClient().getPort());
- returnCollections.emptyMap();
- }
- StringpersistenceInfo=builder.toString();
- if(StringUtils.isBlank(persistenceInfo)){
- returnCollections.emptyMap();
- }
- Map<String,String>map=newLinkedHashMap<String,String>();
- String[]array=persistenceInfo.split("\r\n");
- for(Stringline:array){
- String[]cells=line.split(":");
- if(cells.length>1){
- map.put(cells[0],cells[1]);
- }
- }
- returnmap;
- }
- publicbooleaninvokeBgRewriteAof(finalJedisjedis){
- returnnewIdempotentConfirmer(){
- @Override
- publicbooleanexecute(){
- try{
- Stringresponse=jedis.bgrewriteaof();
- if(response!=null&&response.contains("rewritingstarted")){
- returntrue;
- }
- }catch(Exceptione){
- Stringmessage=e.getMessage();
- if(message.contains("rewritingalready")){
- returntrue;
- }
- logger.error(message,e);
- }
- returnfalse;
- }
- }.run();
- }
- }
附图一张:
(磁带已死,磁盘是新磁带,闪存是新磁盘,随机存储器局部性是为王道)
userId(用户id) | weiboCount(微博数) |
1 | 2000 |
2 |
10 |
3 |
288 |
.... | ... |
1000000 | 1000 |
userId | hashKey | field |
1 | 0 | 1 |
2 | 0 |
2 |
3 | 0 |
3 |
... | .... | ... |
99 | 0 | 99 |
100 | 1 | 0 |
101 | 1 | 1 |
.... | ... | ... |
9999 | 99 | 99 |
100000 | 1000 | 0 |
3. 获取方法:
4. 内存占用量对比(100万用户 userId:1~1000000)
- #方法一Memory
- used_memory:85999592
- used_memory_human:82.02M
- used_memory_rss:96043008
- used_memory_peak:85999592
- used_memory_peak_human:82.02M
- used_memory_lua:36864
- mem_fragmentation_ratio:1.12
- mem_allocator:jemalloc-3.6.0
- #方法二Memory
- used_memory:101665632
- used_memory_human:96.96M
- used_memory_rss:110702592
- used_memory_peak:101665632
- used_memory_peak_human:96.96M
- used_memory_lua:36864
- mem_fragmentation_ratio:1.09
- mem_allocator:jemalloc-3.6.0
- #方法三Memory
- used_memory:9574136
- used_memory_human:9.13M
- used_memory_rss:17285120
- used_memory_peak:101665632
- used_memory_peak_human:96.96M
- used_memory_lua:36864
- mem_fragmentation_ratio:1.81
- mem_allocator:jemalloc-3.6.0
内存使用量:
- packagecom.carlosfu.redis;
- importjava.util.ArrayList;
- importjava.util.HashMap;
- importjava.util.List;
- importjava.util.Map;
- importjava.util.Random;
- importorg.junit.Test;
- importredis.clients.jedis.Jedis;
- /**
- *一次string-hash优化
- *@authorcarlosfu
- *@Date2015-11-8
- *@Time下午7:27:45
- */
- publicclassTestRedisMemoryOptimize{
- privatefinalstaticintTOTAL_USER_COUNT=1000000;
- /**
- *纯字符串
- */
- @Test
- publicvoidtestString(){
- Jedisjedis=null;
- try{
- jedis=newJedis("127.0.0.1",6379);
- List<String>kvsList=newArrayList<String>(200);
- for(inti=1;i<=TOTAL_USER_COUNT;i++){
- StringuserId=String.valueOf(i);
- kvsList.add(userId);
- StringweiboCount=String.valueOf(newRandom().nextInt(100000));
- kvsList.add(weiboCount);
- if(i%2000==0){
- System.out.println(i);
- jedis.mset(kvsList.toArray(newString[kvsList.size()]));
- kvsList=newArrayList<String>(200);
- }
- }
- }catch(Exceptione){
- e.printStackTrace();
- }finally{
- if(jedis!=null){
- jedis.close();
- }
- }
- }
- /**
- *纯hash
- */
- @Test
- publicvoidtestHash(){
- StringhashKey="allUserWeiboCount";
- Jedisjedis=null;
- try{
- jedis=newJedis("127.0.0.1",6379);
- Map<String,String>kvMap=newHashMap<String,String>();
- for(inti=1;i<=TOTAL_USER_COUNT;i++){
- StringuserId=String.valueOf(i);
- StringweiboCount=String.valueOf(newRandom().nextInt(100000));
- kvMap.put(userId,weiboCount);
- if(i%2000==0){
- System.out.println(i);
- jedis.hmset(hashKey,kvMap);
- kvMap=newHashMap<String,String>();
- }
- }
- }catch(Exceptione){
- e.printStackTrace();
- }finally{
- if(jedis!=null){
- jedis.close();
- }
- }
- }
- /**
- *segmenthash
- */
- @Test
- publicvoidtestSegmentHash(){
- intsegment=100;
- Jedisjedis=null;
- try{
- jedis=newJedis("127.0.0.1",6379);
- Map<String,String>kvMap=newHashMap<String,String>();
- for(inti=1;i<=TOTAL_USER_COUNT;i++){
- StringuserId=String.valueOf(i%segment);
- StringweiboCount=String.valueOf(newRandom().nextInt(100000));
- kvMap.put(userId,weiboCount);
- if(i%segment==0){
- System.out.println(i);
- inthash=(i-1)/segment;
- jedis.hmset(String.valueOf(hash),kvMap);
- kvMap=newHashMap<String,String>();
- }
- }
- }catch(Exceptione){
- e.printStackTrace();
- }finally{
- if(jedis!=null){
- jedis.close();
- }
- }
- }
- }
方案 | 优点 | 缺点 |
string |
直观、容易理解 |
|
hash |
直观、容易理解、整合整体 |
|
segment-hash |
内存占用量小,虽然理解不够直观,但是总体上是最优的。 |
理解不够直观。 |
由于演讲时间有限,有关Redis-Cluster,演讲者没做太多介绍,简单的介绍了一些Redis-Cluster概念作用和遇到的两个问题,我们在Redis-Cluster也有很多运维经验,将来的文章会介绍。
但是讲演者反复强调,不要听信网上对于Redis-Cluster的毁谤(实践出真知),对于这一点我很赞同,我们从Redis-Cluster beta版 RC1~4 到现在的3.0-release均没有遇到什么大问题(线上维护600个实例)。
一、Redis-Cluster
有关Redis-Cluster的详细介绍有很多这里就不多说了,可以参考:
3.本博客的一些Redis-Cluster的介绍(未更新完毕)
4. Redis设计与实现那本书(作者:黄建宏):非常的推荐看这本书。
总之Redis-Cluster是一个无中心的分布式Redis存储架构,解决了Redis高可用、可扩展等问题。
二、两个问题:
1. Redis-Cluster主从节点不要在同一个机器部署
(1) 以我们的经验看redis实例本身基本不会挂掉,通常是机器出了问题(断电、机器故障)、甚至是机架、机柜出了问题,造成Redis挂掉。
(2) 如果Redis-Cluster的主从都在一个机器上,那么如果这台机器挂了,主从全部挂掉,高可用就无法实现。(如果full converage=true,也就意味着整个集群挂掉)
(3) 通常来讲一对主从所在机器:不跨机房、要跨机架、可以在一个机柜。
2. Redis-Cluster误判节点fail进行切换
(1) Redis-Cluster是无中心的架构,判断节点失败是通过仲裁的方式来进行(gossip和raft),也就是大部分节点认为一个节点挂掉了,就会做fail判定。
(2) 如果某个节点在执行比较重的操作(flushall, slaveof等等)(可能短时间redis客户端连接会阻塞(redis单线程))或者由于网络原因,造成其他节点认为它挂掉了,会做fail判定。
(3) Redis-Cluster提供了cluster-node-timeout这个参数(默认15秒),作为fail依据(如果超过15秒还是没反应,就认为是挂掉了),具体可以参考这篇文章:Redis-Cluster的FailOver失败案例分析
以我们的经验看15秒完全够用。
三、未来要介绍的问题:
1. Redis-Cluster客户端实现Mget操作。
2.Redis-Cluster--Too many Cluster redirections异常。
3. Redis-Cluster无底洞问题解析。
4. 两个Redis-Cluster集群,meet操作问题后的恶果。
5.Redis-Cluster配置之full converage问题。
7. Redis-Cluster常用运维技巧。
8. Redis-Cluster一键开通。
9. Redis-Cluster客户端jedis详解。
四、附赠一些不错的资料:
- Redis-Cluster的FailOver失败案例分析
- Redis Cluster 迁移遇到的各种坑及解决方案
- Redis Cluster架构优化
- Redis常见集群方案、Codis实践及与Twemproxy比较
- Redis Cluster架构优化
- 【运维实践】鱼与熊掌:使用redis-cluster需要注意些什么?
- Docker及和Redis Cluster的化学反应(上)By 芒果TV
- Docker及和Redis Cluster的化学反应(下)By 芒果TV
- Redis cluster使用经验——网易有道
- Redis Cluster浅析和Bada对比
- 互联网Redis应用场景探讨
- Redis集群技术及Codis实践
- 谈Twitter的百TB级Redis缓存实践
- Hadoop、Spark、HBase与Redis的适用性讨论
- Codis作者黄东旭细说分布式Redis架构设计和踩过的那些坑们
以上转自:http://carlosfu.iteye.com/blog/2254154
相关推荐
3. **监控和报警**:通过工具如`redis-monitor.log`等监控Redis的运行状态,设置内存使用量的阈值报警。 4. **限流和降级策略**:在面临高并发请求时,实施限流和降级策略,保护Redis服务器。 5. **使用缓存穿透、...
tomcat-redis-session-manager-1.2-tomcat-7-java-7tomcat-redis-session-manager-1.2-tomcat-7-java-7tomcat-redis-session-manager-1.2-tomcat-7-java-7tomcat-redis-session-manager-1.2-tomcat-7-java-7tomcat-...
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-...
这个"redis-6.2.6-x64-windows.zip"压缩包包含了针对Windows 10和Windows Server 2016等64位操作系统的Redis版本。以下是关于Redis 6.2.6在Windows环境下的详细知识点: 1. **Redis版本**:6.2.6是Redis的一个稳定...
赠送jar包:spring-data-redis-2.6.1.jar; 赠送原API文档:spring-data-redis-2.6.1-javadoc.jar; 赠送源代码:spring-data-redis-2.6.1-sources.jar; 赠送Maven依赖信息文件:spring-data-redis-2.6.1.pom; ...
这个压缩包"redis-7.2-x64-for-windows-bin.zip"是Redis 7.2版本的64位Windows构建,包含了运行Redis服务所需的所有组件。 1. **Redis 7.2**: Redis是一个开源的内存数据结构存储系统,它可以用作数据库、缓存和...
**Redis 全面检查工具:redis-full-check** Redis 是一款高性能的键值存储系统,广泛应用于缓存、数据库和消息中间件等场景。在实际应用中,为了确保 Redis 的稳定性和数据一致性,需要定期对 Redis 实例进行健康...
接下来,关于“redis-desktop-manager-0.8.8.384.exe”文件,这是一个 Redis 客户端工具,名为 Redis Desktop Manager。它提供了一个图形用户界面(GUI),使得用户可以方便地管理 Redis 服务器,包括查看键值、执行...
4. **运行服务**:通过`redis-server`命令启动Redis服务,使用`redis-cli`进行客户端交互。 5. **配置文件**:修改`redis.conf`配置文件,根据需求设置端口、日志级别、持久化策略等参数。 6. **安全考虑**:默认...
NULL 博文链接:https://carlosfu.iteye.com/blog/2254154
redis-5.0.7-x64-for-windows编译-bin.rar Redis 是一个高性能的key-value数据库。 redis的出现,很大程度补偿了memcached这类keyvalue存储的不足,在部 分场合可以对关系数据库起到很好的补充作用。它提供了Python...
赠送jar包:flink-connector-redis_2.10-1.1.5.jar; 赠送原API文档:flink-connector-redis_2.10-1.1.5-javadoc.jar; 赠送源代码:flink-connector-redis_2.10-1.1.5-sources.jar; 赠送Maven依赖信息文件:flink-...
Windows下的Redis6.2.6版本 由于目前6版本以上Redis的windows版不好找到,故此上传至CSDN,方便大家下载使用。
redis-server --service-install redis.windows-service.conf --loglevel verbose 2 常用的redis服务命令。 卸载服务:redis-server --service-uninstall 开启服务:redis-server --service-start 停止服务:redis-...
RedisPlus是为Redis可视化管理开发的一款开源免费的桌面客户端软件,支持Windows 、Linux、Mac三大系统平台,RedisPlus提供更加高效、方便、快捷的使用体验,有着更加现代化的用户界面风格。该软件支持单机、集群...
赠送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-benchmark 崩溃 ( #8916 ) 其他错误修复: 修复 UNLINK 在 deleted consumer groups 的 stream key 上的崩溃( #8932 ) SINTERSTORE:当不存在任何 sources 时添加丢失的 keyspace ...
在Windows环境下,为了便于管理和使用Redis,通常会有一些专门的工具和包装程序,如本例中的"C# windows redis-2.4.5-win32-win64.rar",这个压缩包提供了在Windows上安装和运行Redis的解决方案。 Redis 2.4.5是...
赠送jar包:spring-data-redis-2.5.5.jar; 赠送原API文档:spring-data-redis-2.5.5-javadoc.jar; 赠送源代码:spring-data-redis-2.5.5-sources.jar; 赠送Maven依赖信息文件:spring-data-redis-2.5.5.pom; ...
redis 服务 redis可视化工具 redis-desktop-manager-0.8.8.384 redis-2.4.5-win32-win64 redis 服务 redis可视化工具 redis-desktop-manager-0.8.8.384 redis-2.4.5-win32-win64