前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >你好,SLF4J

你好,SLF4J

作者头像
程序猿杜小头
发布于 2022-12-01 13:45:03
发布于 2022-12-01 13:45:03
73200
代码可运行
举报
文章被收录于专栏:程序猿杜小头程序猿杜小头
运行总次数:0
代码可运行

作为 CV 工程师,咱们开发的应用并不总是按预期运行,为了方便排查出潜在的问题,一般会在代码中添加日志记录语句。但在 Java 刚刚问世时,日志记录方式好像除了System.outSystem.err之外也没啥别的选择了,主要痛点有:1) 日志无法分级,有些日志纯属 DEBUG,在生产环境是不需要的;2) 日志内容不支持格式化,如 XML、HTML。后来,一位名叫Ceki Gülcü的大神无奈之下发布了大名鼎鼎的log4j。尽管现在 log4j 逐渐退出历史舞台,但在当时却备受 Java 开发人员的喜爱,甚至 JDK 1.4 也是借鉴了 log4j 之后,终于在官方类库中补齐了日志记录这一短板,它就是j.u.l包。

在《阿里 Java 开发手册》中有一条日志规约:【强制】应用中不可直接使用日志系统 (log4j、logback) 中的 API,而应依赖日志门面 (slf4j、jcl) 中的 APIslf4j (Simple Logging Facade for Java) 同样由 Ceki Gülcü 完成开发,作为一款日志门面,其精心设计了org.slf4j.Logger接口,用于抽象日志记录行为,开发人员只需要与该接口交互即可,这样也就将 Java 应用与具体的日志系统 (jul、log4j、logback 和 log4j2) 解耦开来了。本文重点关注 slf4j 是如何实现 Java 应用与一众日志系统解耦的!

slf4j-1.7.36 于2022年2月份发布,至此,1.X 系列也走到了尽头,后续 slf4j 将基于 2.X 演进下去。目前大多数项目都还在使用 1.X 系列,因此,笔者这里基于 1.7.36 来分析。源码中有十几个模块,笔者以为可以将其核心模块归为三类,如下图所示。

1. binding module

如果当前应用的classpath下不仅有日志系统,而且还有与该日志系统匹配的绑定模块,那么面向 slf4j-api 的日志记录请求最终会被路由到该日志系统中去。

slf4j 共计提供了3个绑定模块,分别是 slf4j-jdk14slf4j-log4j12slf4j-jcl。难道没有为logbacklog4j2提供绑定模块吗?这俩可是当前主流的日志系统啊!非也,logback 也是 Ceki Gülcü 大佬干出来的,绑定模块直接集成到其 logback-classic 模块了;而 log4j2 的绑定模块位于 log4j2 源码的 log4j-slf4j-impl 模块内。

我们一般通过org.slf4j.LoggerFactory.getLogger()这种方式来获取实现了org.slf4j.Logger接口的实例,LoggerFactory 主要内容如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package org.slf4j;

public final class LoggerFactory {

    static final int UNINITIALIZED = 0;
    static final int ONGOING_INITIALIZATION = 1;
    static final int FAILED_INITIALIZATION = 2;
    static final int SUCCESSFUL_INITIALIZATION = 3;
    static final int NOP_FALLBACK_INITIALIZATION = 4;

    static volatile int INITIALIZATION_STATE = UNINITIALIZED;
    static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";
    
    private LoggerFactory() {}

    public static Logger getLogger(Class<?> clazz) {
        return getLogger(clazz.getName());
    }

    public static Logger getLogger(String name) {
        ILoggerFactory iLoggerFactory = getILoggerFactory();
        return iLoggerFactory.getLogger(name);
    }

    public static ILoggerFactory getILoggerFactory() {
        if (INITIALIZATION_STATE == UNINITIALIZED) {
            synchronized (LoggerFactory.class) {
                if (INITIALIZATION_STATE == UNINITIALIZED) {
                    INITIALIZATION_STATE = ONGOING_INITIALIZATION;
                    performInitialization();
                }
            }
        }
        switch (INITIALIZATION_STATE) {
            case SUCCESSFUL_INITIALIZATION:
                return StaticLoggerBinder.getSingleton().getLoggerFactory();
            case NOP_FALLBACK_INITIALIZATION:
                return NOP_FALLBACK_FACTORY;
            case FAILED_INITIALIZATION:
                throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
        }
    }

    private final static void performInitialization() {
        bind();
    }

    private final static void bind() {
        try {
            Set<URL> staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
            if(staticLoggerBinderPathSet.size() > 1) {
                Util.report("Class path contains multiple SLF4J bindings.");
                for (URL path : staticLoggerBinderPathSet) {
                    Util.report("Found binding in [" + path + "]");
                }
            }
            // the next line does the binding
            StaticLoggerBinder.getSingleton();
            INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
            if (staticLoggerBinderPathSet != null && staticLoggerBinderPathSet.size() > 1) {
                Util.report("Actual binding is of type [" + StaticLoggerBinder.getSingleton().getLoggerFactoryClassStr() + "]");
            }
        } catch (NoClassDefFoundError ncde) {
            String msg = ncde.getMessage();
            if (msg.contains("org/slf4j/impl/StaticLoggerBinder") || msg.contains("org.slf4j.impl.StaticLoggerBinder")) {
                INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
                Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");
                Util.report("Defaulting to no-operation (NOP) logger implementation");
            } else {
                INITIALIZATION_STATE = FAILED_INITIALIZATION;
                Util.report("Failed to instantiate SLF4J LoggerFactory", ncde);
                throw ncde;
            }
        } catch (java.lang.NoSuchMethodError nsme) {
            String msg = nsme.getMessage();
            if (msg != null && msg.contains("org.slf4j.impl.StaticLoggerBinder.getSingleton()")) {
                INITIALIZATION_STATE = FAILED_INITIALIZATION;
                Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding.");
                Util.report("Your binding is version 1.5.5 or earlier.");
                Util.report("Upgrade your binding to version 1.6.x.");
            }
            throw nsme;
        } catch (Exception e) {
            INITIALIZATION_STATE = FAILED_INITIALIZATION;
            Util.report("Failed to instantiate SLF4J LoggerFactory", e);
            throw new IllegalStateException("Unexpected initialization failure", e);
        }
    }

    static Set<URL> findPossibleStaticLoggerBinderPathSet() {
        Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>();
        try {
            ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
            Enumeration<URL> paths;
            if (loggerFactoryClassLoader == null) {
                paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
            } else {
                paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
            }
            while (paths.hasMoreElements()) {
                URL path = paths.nextElement();
                staticLoggerBinderPathSet.add(path);
            }
        } catch (IOException ioe) {
            Util.report("Error getting resources from path", ioe);
        }
        return staticLoggerBinderPathSet;
    }
}

可见,LoggerFactory 中维护了一个名 INITIALIZATION_STATE 的类变量,用来标识其是否已初始化。初始化逻辑由performInitialization()方法来承载,具体是执行bind()方法内的绑定逻辑。在 bind() 方法内,首先会在当前应用的classpath下搜寻org/slf4j/impl/StaticLoggerBinder.class资源,如果存在多个绑定模块,staticLoggerBinderPathSet 这一 Set<URL> 类型的局部变量中肯定是会有多个元素的;然后进行真正的绑定操作,即StaticLoggerBinder.getSingleton()。细心点的同学应该能发现,在 slf4j-api 和一众绑定模块中,均含有org/slf4j/impl/StaticLoggerBinder.java,那么如何确保一定是绑定模块中的StaticLoggerBinder脱颖而出呢?slf4j-api 模块在打包的时候会排除掉该 Class 文件。另外,StaticLoggerBinder.getSingleton() 这一行代码究竟有何深意呢?毕竟 getSingleton() 方法返回的 StaticLoggerBinder 实例并没有在 bind() 方法内使用。这是为了唤醒 JVM 去主动加载 StaticLoggerBinder,JVM 并不会一次性将所有 Class 文件加载到内存中,而是基于一种延迟加载策略。当 StaticLoggerBinder 调用其 getSingleton() 这一静态方法时,JVM 便会执行 StaticLoggerBinder 中的<clinit>()方法,<clinit>() 方法并不是咱们在 Java 代码中直接编写的方法,而是由编译器自动收集类中静态变量(不包含由 final 关键字修饰的静态常量)的赋值语句和静态初始化代码块合并而产生的;而执行 <clinit>() 方法的过程恰恰对应着类的初始化,初始化是类加载过程的最后一个阶段,必须先执行完加载连接这俩阶段才行,进而针对 StaticLoggerBinder 类加载的三个阶段也就完成了,使得绑定模块中的 StaticLoggerBinder 成为可以被 JVM 直接使用的 Java 类型,这正是类加载机制的一个典型应用场景,即通过类加载机制来满足灵活的可插拔需求,也许这才是这一行代码背后的深意吧。注意:如果当前应用的classpath下有多个绑定模块,那么 JVM 究竟加载哪一个绑定模块中的 StaticLoggerBinder 呢?这就取决于类加载的顺序了!

下面以 slf4j-jdk14 这一绑定模块为例,分析下该模块提供的 StaticLoggerBinder。如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package org.slf4j.impl;

import org.slf4j.ILoggerFactory;
import org.slf4j.LoggerFactory;
import org.slf4j.spi.LoggerFactoryBinder;

public class StaticLoggerBinder implements LoggerFactoryBinder {

    private static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder();

    public static final StaticLoggerBinder getSingleton() {
        return SINGLETON;
    }

    private static final String loggerFactoryClassStr = org.slf4j.impl.JDK14LoggerFactory.class.getName();

    private final ILoggerFactory loggerFactory;

    private StaticLoggerBinder() {
        loggerFactory = new org.slf4j.impl.JDK14LoggerFactory();
    }

    public ILoggerFactory getLoggerFactory() {
        return loggerFactory;
    }

    public String getLoggerFactoryClassStr() {
        return loggerFactoryClassStr;
    }
}

从上述内容来看,slf4j-jdk14 中 StaticLoggerBinder 实现了 slf4j-apiLoggerFactoryBinder接口的两个方法,分别是:getLoggerFactory()getLoggerFactoryClassStr()。记住,每一个绑定模块中的 StaticLoggerBinder 总是会实现 LoggerFactoryBinder 接口。getLoggerFactory() 方法返回的是JDK14LoggerFactory,JDK14LoggerFactory 是绑定模块 slf4j-jdk14 提供的,其实现了 slf4j-api 中的ILoggerFactory接口,具体如下。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class JDK14LoggerFactory implements ILoggerFactory {

    ConcurrentMap<String, Logger> loggerMap;

    public JDK14LoggerFactory() {
        loggerMap = new ConcurrentHashMap<String, Logger>();
        // ensure jul initialization
        java.util.logging.Logger.getLogger("");
    }

    public Logger getLogger(String name) {
        // the root logger is called "" in JUL
        if (name.equalsIgnoreCase(Logger.ROOT_LOGGER_NAME)) {
            name = "";
        }

        Logger slf4jLogger = loggerMap.get(name);
        if (slf4jLogger != null)
            return slf4jLogger;
        else {
            java.util.logging.Logger julLogger = java.util.logging.Logger.getLogger(name);
            Logger newInstance = new JDK14LoggerAdapter(julLogger);
            Logger oldInstance = loggerMap.putIfAbsent(name, newInstance);
            return oldInstance == null ? newInstance : oldInstance;
        }
    }
}

原来,绑定模块 slf4j-jdk14 针对 JDK 日志系统的java.util.logging.Logger进行了适配,这个适配器就是JDK14LoggerAdapter,其核心内容如下。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public final class JDK14LoggerAdapter implements Logger {
    transient final java.util.logging.Logger logger;

    JDK14LoggerAdapter(java.util.logging.Logger logger) {
        this.logger = logger;
    }

    public void trace(String format, Object arg) {
        if (logger.isLoggable(Level.FINEST)) {
            FormattingTuple ft = MessageFormatter.format(format, arg);
            log(SELF, Level.FINEST, ft.getMessage(), ft.getThrowable());
        }
    }

    public void debug(String format, Object arg) {
        if (logger.isLoggable(Level.FINE)) {
            FormattingTuple ft = MessageFormatter.format(format, arg);
            log(SELF, Level.FINE, ft.getMessage(), ft.getThrowable());
        }
    }

    public void info(String format, Object arg) {
        if (logger.isLoggable(Level.INFO)) {
            FormattingTuple ft = MessageFormatter.format(format, arg);
            log(SELF, Level.INFO, ft.getMessage(), ft.getThrowable());
        }
    }

    public void warn(String format, Object arg) {
        if (logger.isLoggable(Level.WARNING)) {
            FormattingTuple ft = MessageFormatter.format(format, arg);
            log(SELF, Level.WARNING, ft.getMessage(), ft.getThrowable());
        }
    }

    public void error(String format, Object arg) {
        if (logger.isLoggable(Level.SEVERE)) {
            FormattingTuple ft = MessageFormatter.format(format, arg);
            log(SELF, Level.SEVERE, ft.getMessage(), ft.getThrowable());
        }
    }

    private void log(String callerFQCN, Level level, String msg, Throwable t) {
        LogRecord record = new LogRecord(level, msg);
        record.setLoggerName(getName());
        record.setThrown(t);
        fillCallerData(callerFQCN, record);
        logger.log(record);
    }
}

2. bridge module

如果你开发了一个组件,而且希望上层应用直接通过该组件的 GAV 信息来引入,那么在你这个组件中建议使用日志门面来记录日志,但往往事与愿违,很多开发者还是直接选择具体的日志系统来记录日志,这就有点不规范了。想象一下,如果上层应用使用的日志系统是 logback,而你的组件选用的日志系统却是 log4j,为了将整个应用的日志系统拉齐为 logback,此时你可以用 slf4j 提供的针对 log4j 的桥接模块去替换 log4j 依赖,这样你就不用修改任何代码。从这个例子中,大家可能猜到了桥接模块的作用,就是将面向特定日志系统的日志记录请求路由到 slf4j-api 中去。但有一点需要注意,针对同一款日志系统的绑定模块不能与桥接模块同时存在于当前应用的classpath中,否则会造成死循环!

slf4j 共计提供了3个桥接模块,分别是 jul-to-slf4jlog4j-over-slf4jjcl-over-slf4j。难道也没有为 logback 和 log4j2 提供桥接模块吗?笔者还真的没找到针对 logback 的桥接模块,这也许是大神的自信吧;而 log4j2 的桥接模块位于 log4j2 源码的 log4j-to-slf4j 模块内。

针对 jul 日志系统的桥接模块比较简单,就一个SLF4JBridgeHandler类文件,因而这里选择 jul-to-slf4j 来简单分析下。主要内容有:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package org.slf4j.bridge;

public class SLF4JBridgeHandler extends Handler {
    private static final int TRACE_LEVEL_THRESHOLD = Level.FINEST.intValue();
    private static final int DEBUG_LEVEL_THRESHOLD = Level.FINE.intValue();
    private static final int INFO_LEVEL_THRESHOLD = Level.INFO.intValue();
    private static final int WARN_LEVEL_THRESHOLD = Level.WARNING.intValue();
    
    public static void install() {
        java.util.logging.Logger rootLogger = LogManager.getLogManager().getLogger("");
        rootLogger.addHandler(new org.slf4j.bridge.SLF4JBridgeHandler());
    }
    
    public static void uninstall() throws SecurityException {
        java.util.logging.Logger rootLogger = LogManager.getLogManager().getLogger("");
        Handler[] handlers = rootLogger.getHandlers();
        for (int i = 0; i < handlers.length; i++) {
            if (handlers[i] instanceof org.slf4j.bridge.SLF4JBridgeHandler) {
                rootLogger.removeHandler(handlers[i]);
            }
        }
    }

    public void publish(LogRecord record) {
        Logger slf4jLogger = getSLF4JLogger(record);
        callPlainSLF4JLogger(slf4jLogger, record);
    }
    
    public void close() {}
    
    public void flush() {}
    
    protected Logger getSLF4JLogger(LogRecord record) {
        return LoggerFactory.getLogger(record.getLoggerName());
    }

    protected void callPlainSLF4JLogger(Logger slf4jLogger, LogRecord record) {
        String i18nMessage = getMessageI18N(record);
        int julLevelValue = record.getLevel().intValue();
        if (julLevelValue <= TRACE_LEVEL_THRESHOLD) {
            slf4jLogger.trace(i18nMessage, record.getThrown());
        } else if (julLevelValue <= DEBUG_LEVEL_THRESHOLD) {
            slf4jLogger.debug(i18nMessage, record.getThrown());
        } else if (julLevelValue <= INFO_LEVEL_THRESHOLD) {
            slf4jLogger.info(i18nMessage, record.getThrown());
        } else if (julLevelValue <= WARN_LEVEL_THRESHOLD) {
            slf4jLogger.warn(i18nMessage, record.getThrown());
        } else {
            slf4jLogger.error(i18nMessage, record.getThrown());
        }
    }
}

要想读懂上述源码内容,需要简单铺垫一下 jul 的相关知识。在 jul 中,有几个概念比较重要,LogManager用于读取日志配置文件、创建和维护 Logger 实例;Handler类似于 log4j 中的Appender,它决定了日志记录的输出方向,比如:Console、File 和 Memory 等,一个 Logger 实例可以持有多个 Handler;Formatter用于格式化日志记录,它决定了日志记录的样式。Logger通常是有名称的,一般是包的名称;每一个 Logger 实例会根据命名空间跟踪其父级 Logger,子 Logger 可以继承父 Logger 中的LogLevel和 Handler 等资源;根 (Root) Logger 的名称为""。

SLF4JBridgeHandler 继承了 jul 的 Handler 接口,重写了其publish()方法,主要逻辑就是委派 slf4j 来记录请求。现在自定义的 Handler 有了,还必须将其添加到 jul 的根 (Root) Logger 中去,这也恰是上述install()方法中的内容。

3. 总结

其实之所以写这篇文章,是看了美团一篇技术文章《日志级别动态调整——小工具解决大问题》,原文把绑定模块说成了桥接模块,这应该是一处错误的表述。另外,作者分享了一个真实案例,很值得我们警惕:发配送服务化项目由于间接依赖,引入了 logback 日志系统。在项目启动加载时,slf4j 动态绑定到 logback 上,但是由于发配送项目使用的 log4j,并未配置 logback.xml文件,导致在打印日志时,logback 无法匹配到具体的日志配置,从而为项目自动创建了一个日志级别为 DEBUG 的 Root Logger,所有的 Logger 以该级别打印输出,导致发配送服务化项目在中午左右高峰期,短时间内打印过多的系统日志,引起系统负载飙高,造成服务化宕机,损失非常严重。

参考文档

  1. https://github.com/apache/commons-logging
  2. https://github.com/qos-ch/slf4j
  3. https://github.com/apache/logging-log4j1
  4. https://github.com/qos-ch/reload4j5
  5. https://github.com/qos-ch/logback
  6. https://github.com/apache/logging-log4j2
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-10-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序猿杜小头 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
redis 的高并发高可用
对于性能来说,单主用来写入数据,单机几万QPS,多从用来查询数据,多个从实例可以提供每秒 10w 的 QPS。
Tim在路上
2020/08/04
1.4K0
面试题:能否讲讲Redis是如何做到高可用的?
sentinel,中文名是哨兵。哨兵是 redis 集群机构中非常重要的一个组件,主要有以下功能:
用户1263954
2019/07/05
5940
面试题:能否讲讲Redis是如何做到高可用的?
Redis哨兵(Sentinel)机制 --高可用的保障
哨兵机制是用来解决主从同步Master宕机后的动态自动主从切换问题。 主要有以下作用
名字是乱打的
2021/12/24
6150
关于Redis的几件小事 | 高并发和高可用
如果你用redis缓存技术的话,肯定要考虑如何用redis来加多台机器,保证redis是高并发的,还有就是如何让Redis保证自己不是挂掉以后就直接死掉了。
王知无-import_bigdata
2019/07/23
1.7K0
关于Redis的几件小事 | 高并发和高可用
Redis 常见面试题
(1)Redis 支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
Tim在路上
2020/08/05
7220
Redis 常见面试题
Redis哨兵机制
Redis哨兵机制 一. Sentinel介绍 Sentinel,中文为哨兵,是Redis集群架构中一个非常重要的组件。 主要功能: 集群监控:负责监控主从集群中的Master和Slave进程是否正常工作。 故障转移(failover):如果Master宕机,会自动从Slave中选举出新的Master,进行主从自动切换。 配置中心:如果发生了故障转移,Sentinel负责通知客户端新的Master的地址。 消息通知:如果某个redis节点有故障,那么Sentsinel会发送报警消息给系统管理员。 目前采用
张申傲
2020/09/03
7570
Redis哨兵机制
Redis 哨兵机制以及底层原理深入解析,这次终于搞清楚了
前面我们基于实际案例搭建了缓存高可用方案(分布式缓存高可用方案,我们都是这么干的)同时提到了redis主从架构下是如何保证高可用的,讲到了它是通过redis sentinel的机制来实现的。
架构师修炼
2020/07/20
5490
Redis 哨兵机制以及底层原理深入解析,这次终于搞清楚了
中华石杉Java面试突击第一季笔记三(分布式缓存)
整合了商品名称、价格、图片、简介的商品详情页就是典型的场景,可以把通过复杂操作耗时查询出来的结果,确定短时间内不会频繁更新变化,但是对这个数据会有大量读请求,这个时候就可以直接把结果存放在缓存中,后面的请求就直接读取缓存即可。
chenchenchen
2022/03/09
5850
中华石杉Java面试突击第一季笔记三(分布式缓存)
Redis学习三:Redis高可用之哨兵模式
Redis 的 Sentinel 系统用于管理多个 Redis 服务器(instance), 该系统执行以下三个任务:
一枝花算不算浪漫
2020/04/22
6410
Redis学习三:Redis高可用之哨兵模式
Redis读写分离和分布式缓存算法原理
当启动一个slave node的时候,它会发送一个PSYNC命令给master node
丁D
2022/08/12
4860
Redis 知识总结
作者:leobhao,腾讯 CSIG 研发工程师。 1. Redis 概览 Redis 和 memcache 的区别,Redis 支持的数据类型应用场景 redis 支持的数据结构更丰富(string,hash,list,set,zset)。memcache 只支持 key-value 的存储; redis 原生支持集群,memcache 没有原生的集群模式。 2. Redis 单线程模型 redis 单线程处理请求流程 redis 采用 IO 多路复用机制来处理请求,采用 reactor IO 模型, 处
腾讯技术工程官方号
2022/06/14
6050
Redis 知识总结
突破Java面试(23-9) - 深入解析Redis哨兵底层原理
通过Redis的pub/sub实现哨兵互相之间的发现,每个哨兵都会往__sentinel__:hello这个channel发一个消息,此时所有其他哨兵都可消费到该消息,于是感知到其他哨兵的存在.
JavaEdge
2019/07/06
4240
突破Java面试(23-9) - 深入解析Redis哨兵底层原理
对线面试官-Redis(八 基于哨兵HA的原理)
面试官:之前聊了基于哨兵模式的Redis高可用,那哨兵如何部署才能保证故障转移成功呢?
@派大星
2023/08/10
1500
对线面试官-Redis(八 基于哨兵HA的原理)
为什么Redis哨兵集群至少3节点?
接上篇,由于Redis主从无法达到高可用性,所以Redis通过哨兵的方式在Redis主从基础上实现高可用Redis集群
李俊鹏
2022/09/21
5490
Redis Sentinel-深入浅出原理和实战
之前的文章聊到了Redis的主从复制,聊到了其相关的原理和缺点,具体的建议可以看看我之前写的文章Redis的主从复制。
SH的全栈笔记
2022/08/17
3450
Redis Sentinel-深入浅出原理和实战
高可用的Redis主从复制集群,从理论到实践
我们都知道,服务如果只部署一个节点,很容易出现单点故障,从而导致服务不可用。所以服务做成高可用是很有必要的,Redis服务自然也是这样。本文主要从理论和实践两方面讲解Redis高可用集群。手把手教你搭建一个高可用的redis主从复制集群。
用户2781897
2020/10/10
5880
高可用的Redis主从复制集群,从理论到实践
Redis 高可用讲解
Redis 高并发架构中的哨兵(Sentinel)角色及其功能在确保分布式系统的稳定性和可用性方面扮演着至关重要的角色。在本文中,我们将进一步深入探讨哨兵的关键功能以及与故障转移相关的核心概念。
泽霖
2023/11/14
2050
Redis的七个核心机制底层原理
(1)、S_DOWN是主观宕机,就一个哨兵如果自己觉得一个master宕机了,那么就是主观宕机 sdown达成的条件很简单,如果一个哨兵ping一个master,超过了is-master-down-after-milliseconds指定的毫秒数之后,就主观认为master宕机
Java_老男孩
2019/12/02
9050
《【面试突击】— Redis篇》-- Redis的主从复制?哨兵机制?
高并发:redis的单机吞吐量可以达到几万不是问题,如果想提高redis的读写能力,可以用redis的主从架构,redis天热支持一主多从的准备模式,单主负责写请求多从负责读请求,主从之间异步复制,把主的数据同步到从。
编程大道
2020/02/19
4930
Redis哨兵机制
如果master异常,则会进行master-slave切换,将其中一个slave作为master,将之前的master作为slave。
码农编程进阶笔记
2021/07/20
4660
Redis哨兵机制
推荐阅读
相关推荐
redis 的高并发高可用
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档