IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Logback日志框架配置详解+异步输出 -> 正文阅读

[Java知识库]Logback日志框架配置详解+异步输出

1、配置文件logback-spring.xml

Spring Boot工程自带logback和slf4j的依赖,我们使用的时候重点只需放在编写配置文件上,需要引入什么依赖,日志依赖冲突只要不影响服务正常启动和访问的,统统都不需要我们管。

logback日志框架会默认加载classpath下命名为logback-spring.xml的配置文件。

将所有日志都存储在一个文件中,文件大小也随着应用的运行越来越大,并且不好排查问题。正确的做法应该是将各个级别的日志根据时间段记录存储在不同的日志文件中。但是由于咱们G4统一使用的是docker+k8s,所以暂时可不用按照日志级别进行区分。

<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别优先级:OFF(最高等级,关闭所有日志输出) > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL(最低等级,打开所有日志输出)-->
<!--如果定义为INFO级别,则程序中DEBUG级别的日志将不会被打印出来(大于等于INFO级别的日志才会输出),所以设置的日志等级越高打印出来的日志越少-->
<!--
    scan="true": 配置文件如果发生改变,将会被重新加载。
    scanPeriod="600 seconds": 每隔600秒监测一次配置文件是否有修改,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。
    debug: 当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。
-->
<configuration scan="true" scanPeriod="600 seconds" debug="false">
    <!-- 应用名称 -->
    <springProperty scope="context" name="app_name" source="spring.application.name"/>
    <springProperty scope="context" name="level" source="logging.level.logback"/>

    <!--日志文件的保存路径,首先查找系统属性-Dlog.dir,如果存在就使用其;否则,在当前目录下创建名为logs目录做日志存放的目录 -->
    <property name="log_home" value="${log.dir:-logs}/${app_name}"/>

    <!--日志输出格式:
        %d{HH:mm:ss.SSS}——日志输出时间
        %thread——输出日志的进程名字,这在Web应用以及异步任务处理中很有用
        %-5level——日志级别,并且使用5个字符靠左对齐
        %logger{36}——日志输出者的名字
        %msg——日志消息
        %n——平台的换行符
    -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>ts=%d{yyyy-MM-dd HH:mm:ss.SSS} app=${app_name} th=[%thread] lv=%-5level class=%logger{5} msg=%line-%msg%n</pattern>
            <charset>utf-8</charset>
        </encoder>
    </appender>

    <!-- 日志信息异步输出配置 -->
    <appender name="ASYNC-STDOUT" class="ch.qos.logback.classic.AsyncAppender">
        <!--默认情况下,当blockingQueue的容量高于阈值时(80%),会丢弃ERROR以下级别的日志。如果不希望丢弃日志(既每次都全量保存),那可以设置为0-->
        <discardingThreshold>0</discardingThreshold>
        <!--默认情况下,队列的深度为256,不过该值首次建议设置大一些,后续根据自己业务的特点去调优。注意:该值会影响性能-->
        <queueSize>512</queueSize>
        <!--添加需要异步输出appender,只能添加一个-->
        <appender-ref ref="STDOUT"/>
    </appender>

    <!--表示:在com.taobao.diamond包下面,日志级别大于等于error的日志才会输出到控制台或日志文件-->
    <logger name="com.taobao.diamond" level="ERROR" additivity="false">
        <appender-ref ref="STDOUT"/>
    </logger>
    <logger name="org.springframework" level="ERROR" additivity="false">
        <appender-ref ref="STDOUT"/>
    </logger>
    <logger name="DiamondConfigDataLog" level="ERROR" additivity="false">
        <appender-ref ref="STDOUT"/>
    </logger>
    <logger name="com.dhgate.apsaras.util.ApsarasClientProperties" level="ERROR" additivity="false">
        <appender-ref ref="STDOUT"/>
    </logger>

    <!--多环境配置-->
    <!--测试环境-->
    <springProfile name="qa">
        <root level="${level}">
            <!--日志信息同步输出-->
            <!--<appender-ref ref="STDOUT"/>-->
            <!--日志信息异步输出-->
            <appender-ref ref="ASYNC-STDOUT"/>
        </root>
    </springProfile>
</configuration>

按照级别分开存储的详细的日志配置:

<?xml version="1.0" encoding="UTF-8"?>

<!--日志级别优先级:OFF(最高等级,关闭所有日志输出) > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL(最低等级,打开所有日志输出)-->
<!--如果定义为INFO级别,则程序中DEBUG级别的日志将不会被打印出来(大于等于INFO级别的日志才会输出),所以设置的日志等级越高打印出来的日志越少-->
<!--
    scan="true": 配置文件如果发生改变,将会被重新加载。
    scanPeriod="600 seconds": 每隔600秒监测一次配置文件是否有修改,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。
    debug: 当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。
-->
<configuration scan="true" scanPeriod="600 seconds" debug="false">
    <!-- 应用名称 -->
    <springProperty scope="context" name="app_name" source="spring.application.name"/>
    <springProperty scope="context" name="level" source="logging.level.logback"/>

    <!--日志文件的保存路径,首先查找系统属性-Dlog.dir,如果存在就使用;否则,在当前目录下创建名为logs目录做日志存放的目录 -->
    <property name="log_home" value="${log.dir:-logs}/${app_name}"/>
    <!--日志输出格式:
        %d{HH: mm:ss.SSS}——日志输出时间
        %thread——输出日志的进程名字,这在Web应用以及异步任务处理中很有用
        %-5level——日志级别,并且使用5个字符靠左对齐
        %logger{36}——日志输出者的名字
        %msg——日志消息
        %n——平台的换行符
    -->
    <property name="basePattern" value="ts=%d{yyyy-MM-dd HH:mm:ss:SSS} app=${app_name} th=[%thread] lv=%-5level class=%logger msg=%line-%msg%n">
    </property>

    <!--info级别日志输出和存档设置:按天切分 %d{yyyy-MM-dd}-->
    <property name="rollInfoName" value="${log_home}/feignClient_web-%d{yyyy-MM-dd}.%i.log"/>
    <!--warn级别日志输出和存档设置:按天切分 %d{yyyy-MM-dd}-->
    <property name="rollWarnName" value="${log_home}/feignClient_web_warn-%d{yyyy-MM-dd}.%i.log"/>
    <!--error级别日志输出和存档设置:按天切分 %d{yyyy-MM-dd}-->
    <property name="rollErrorName" value="${log_home}/feignClient_web_error-%d{yyyy-MM-dd}.%i.log"/>

    <!-- 控制台日志输出设置 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${basePattern}</pattern>
            <!--设置字符集,解决乱码问题-->
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!-- INFO级别日志输出设置 -->
    <appender name="rollingFileInfo" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 日志记录器的滚动策略:基于大小和时间 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--会将当天的日志记录在<file>${log_home}/buyer_dispute_web.log</file>,然后将昨天的日志归档到下面的文件中-->
            <fileNamePattern>${rollInfoName}</fileNamePattern>
            <!--单个日志文件最大1024M,到了这个值,就会再创建一个日志文件,日志文件的名字最后+1-->
            <maxFileSize>1024MB</maxFileSize>
            <!--日志文件保留天数:保存最近30天的日志-->
            <maxHistory>30</maxHistory>
            <!--所有的日志文件最大30G,超过就会删除旧的日志-->
            <totalSizeCap>30GB</totalSizeCap>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--日志输出格式-->
            <pattern>${basePattern}</pattern>
            <!--设置字符集,解决乱码问题-->
            <charset>UTF-8</charset>
        </encoder>
        <!-- ThresholdFilter:临界值过滤器,过滤掉低于指定临界值的日志。当日志级别等于或高于临界值时,过滤器返回NEUTRAL;当日志级别低于临界值时,日志会被拒绝。 -->
        <!--LevelFilter:级别过滤器,根据日志级别进行过滤。如果日志级别等于配置级别,过滤器会根据onMath 和 onMismatch接收或拒绝日志。
            有以下子节点:
            <level>:设置过滤级别
            <onMatch>:用于配置符合过滤条件的操作
            <onMismatch>:用于配置不符合过滤条件的操作
            onMatch 和 onMismatch说明:
                onMatch="ACCEPT" 表示匹配该级别及以上
                onMatch="DENY" 表示不匹配该级别及以上
                onMatch="NEUTRAL" 表示该级别及以上的,由下一个filter处理,如果当前是最后一个,则表示匹配该级别及以上
                onMismatch="ACCEPT" 表示匹配该级别以下
                onMismatch="DENY" 表示不匹配该级别以下
                onMismatch="NEUTRAL" 表示该级别及以下的,由下一个filter处理,如果当前是最后一个,则不匹配该级别以下的
        -->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <!-- 接收INFO及以上级别的日志信息(onMatch="ACCEPT")输出到这个路径中,不匹配debug级别以下日志信息 -->
            <level>INFO</level><!--过滤掉所有低于INFO级别的日志-->
        </filter>
    </appender>

    <!-- warn级别日志输出设置 -->
    <appender name="rollingFileWarn" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${rollWarnName}</fileNamePattern>
            <maxFileSize>1024MB</maxFileSize>
            <maxHistory>30</maxHistory>
            <totalSizeCap>30GB</totalSizeCap>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${basePattern}</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>WARN</level>
        </filter>
    </appender>

    <!-- error级别日志输出设置 -->
    <appender name="rollingFileError" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${rollErrorName}</fileNamePattern>
            <maxFileSize>1024MB</maxFileSize>
            <maxHistory>30</maxHistory>
            <totalSizeCap>30GB</totalSizeCap>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${basePattern}</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>
    </appender>

    <!--性能检测日志-->
    <appender name="rollingFileAccess" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log_home}/access-%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>%msg%n</pattern>
        </encoder>
    </appender>

    <!-- 日志信息异步输出配置 start -->
    <appender name="ASYNC-STDOUT" class="ch.qos.logback.classic.AsyncAppender">
        <!--默认情况下,当blockingQueue的容量高于阈值时(80%),会丢弃ERROR以下级别的日志。如果不希望丢弃日志(既每次都全量保存),那可以设置为0-->
        <discardingThreshold>0</discardingThreshold>
        <!--默认情况下,队列的深度为256,不过该值首次建议设置大一些,后续根据自己业务的特点去调优。注意:该值会影响性能-->
        <queueSize>128</queueSize>
        <!--添加需要异步输出appender,只能添加一个-->
        <appender-ref ref="STDOUT"/>
    </appender>

    <appender name="ASYNC-INFO" class="ch.qos.logback.classic.AsyncAppender">
        <!--默认情况下,当blockingQueue的容量高于阈值时(80%),会丢弃ERROR以下级别的日志。如果不希望丢弃日志(既每次都全量保存),那可以设置为0-->
        <discardingThreshold>0</discardingThreshold>
        <!--默认情况下,队列的深度为256,不过该值首次建议设置大一些,后续根据自己业务的特点去调优。注意:该值会影响性能-->
        <queueSize>512</queueSize>
        <!--添加需要异步输出appender,只能添加一个-->
        <appender-ref ref="rollingFileInfo"/>
    </appender>

    <appender name="ASYNC-WARN" class="ch.qos.logback.classic.AsyncAppender">
        <!--默认情况下,当blockingQueue的容量高于阈值时(80%),会丢弃ERROR以下级别的日志。如果不希望丢弃日志(既每次都全量保存),那可以设置为0-->
        <discardingThreshold>0</discardingThreshold>
        <!--默认情况下,队列的深度为256,不过该值首次建议设置大一些,后续根据自己业务的特点去调优。注意:该值会影响性能-->
        <queueSize>256</queueSize>
        <!--添加需要异步输出appender,只能添加一个-->
        <appender-ref ref="rollingFileWarn"/>
    </appender>

    <appender name="ASYNC-ERROR" class="ch.qos.logback.classic.AsyncAppender">
        <!--默认情况下,当blockingQueue的容量高于阈值时(80%),会丢弃ERROR以下级别的日志。如果不希望丢弃日志(既每次都全量保存),那可以设置为0-->
        <discardingThreshold>0</discardingThreshold>
        <!--默认情况下,队列的深度为256,不过该值首次建议设置大一些,后续根据自己业务的特点去调优。注意:该值会影响性能-->
        <queueSize>256</queueSize>
        <!--添加需要异步输出appender,只能添加一个-->
        <appender-ref ref="rollingFileError"/>
    </appender>

    <appender name="ASYNC-ACCESS" class="ch.qos.logback.classic.AsyncAppender">
        <!--默认情况下,当blockingQueue的容量高于阈值时(80%),会丢弃ERROR以下级别的日志。如果不希望丢弃日志(既每次都全量保存),那可以设置为0-->
        <discardingThreshold>0</discardingThreshold>
        <!--默认情况下,队列的深度为256,不过该值首次建议设置大一些,后续根据自己业务的特点去调优。注意:该值会影响性能-->
        <queueSize>256</queueSize>
        <!--添加需要异步输出appender,只能添加一个-->
        <appender-ref ref="rollingFileAccess"/>
    </appender>
    <!-- 日志信息异步输出配置 end -->

    <!--日志输出过滤 start-->
    <!--additivity设为false后,则这个logger不会将日志流反馈到root中,只会输出到指定的日志文件输出,达到过滤日志的目的-->
    <logger name="com.taobao.diamond.utils" additivity="false">
        <level value="${level}"/>
        <!--此时该包或类里面,日志级别>=${level}的日志,只会在对应ref指定的地方输出-->
        <appender-ref ref="rollingFileInfo"/>
        <appender-ref ref="rollingFileWarn"/>
    </logger>
    <!--表示:在com.taobao.diamond.client包下面,日志级别大于等于error的日志只会在到控制台里打印-->
    <logger name="com.taobao.diamond.client" level="ERROR" additivity="false">
        <appender-ref ref="STDOUT"/>
    </logger>
    <!--表示:在com.alibaba.nacos.client包下面,日志级别大于等于warn的日志才会输出到控制台和info日志文件-->
    <logger name="com.alibaba.nacos.client" level="WARN" additivity="false">
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="rollingFileInfo"/>
    </logger>
    <logger name="org.springframework" additivity="false">
        <level value="${level}"/>
        <appender-ref ref="rollingFileWarn"/>
    </logger>
    <!--日志输出过滤 end-->

    <!--多环境日志输出配置,可以在bootstrap.yml中配置选择哪个 profiles:spring.profiles.active=qa-->
    <springProfile name="qa">
        <root level="${level}">
            <!--同步输出-->
            <!--<appender-ref ref="STDOUT"/>
            <appender-ref ref="rollingFileInfo"/>
            <appender-ref ref="rollingFileWarn"/>
            <appender-ref ref="rollingFileError"/>
            <appender-ref ref="rollingFileAccess"/>-->
            <!--异步输出-->
            <appender-ref ref="ASYNC-STDOUT"/>
            <appender-ref ref="ASYNC-INFO"/>
            <appender-ref ref="ASYNC-WARN"/>
            <appender-ref ref="ASYNC-ERROR"/>
            <appender-ref ref="ASYNC-ACCESS"/>
        </root>
    </springProfile>
</configuration>

2、logback 高级特性:异步输出日志

目前所有的日志记录方式采用的都是同步的方式,即直接将日志写入文件。每次日志输出到文件都会进行一次磁盘IO,在多应用的时候这种效果会导致一定的线程运行延迟,所以可以采用异步的方式处理。

采用异步写日志的方式,通过不让主线程去写日志文件而减少磁盘IO,避免并发下造成线程阻塞,从而减少不必要的性能损耗。

异步输出日志的方式很简单,添加一个基于异步写日志的appender,并指向原先配置的appender即可:

step1、原先的appender

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
        <pattern>ts=%d{yyyy-MM-dd HH:mm:ss.SSS} app=${app_name} th=[%thread] lv=%-5level class=%logger{5} msg=%line-%msg%n</pattern>
        <charset>utf-8</charset>
    </encoder>
</appender>

step2、异步的appender

<!-- 日志信息异步输出配置 -->
<appender name="ASYNC-STDOUT" class="ch.qos.logback.classic.AsyncAppender">
    <!--默认情况下,当blockingQueue的容量高于阈值时(80%),会丢弃ERROR以下级别的日志。如果不希望丢弃日志(既每次都全量保存),那可以设置为0-->
    <discardingThreshold>0</discardingThreshold>
    <!--默认情况下,队列的深度为256,不过该值首次建议设置大一些,后续根据自己业务的特点去调优。注意:该值会影响性能-->
    <queueSize>512</queueSize>
    <!--添加需要异步输出appender,只能添加一个,这里指向原先配置的appender即可-->
 <appender-ref ref="STDOUT"/>
</appender>

step3、在springProfile多环境配置里,为root标签指定日志异步输出的appender对应的ref值

<springProfile name="qa">
    <root level="${level}">
        <!--日志信息同步输出-->
        <appender-ref ref="STDOUT"/>
        <!--日志信息异步输出-->
        <!--<appender-ref ref="ASYNC-STDOUT"/>-->
    </root>
</springProfile>

3、同步、异步输出日志,性能对比测试

既然能提高性能的话,必须进行一次测试比对,同步和异步输出日志性能到底能提升多少倍?

服务器硬件和配置相同的前提下,用Apache Jmeter测试工具分别压。

3.1、200个线程跑10分钟。

在这里插入图片描述

3.2、服务接口代码(包含远程调用)

/**
 * 日志同步、异步输出性能对比测试接口
 *
 * @param timeout
 * @return
 */
// http://localhost:7997/feignClient/logbackAsyncAppender?timeout=4
@RequestMapping("/feignClient/logbackAsyncAppender")
public String testLogbackAsyncAppender(String timeout) throws InterruptedException {

    System.out.println("FeignClientController.logbackAsyncAppender--->测试日志同步和异步打印的性能 start");

    long startTime = System.currentTimeMillis();
    // 日志级别优先级:ERROR > WARN > INFO > DEBUG > TRACE
    for (int i = 0; i < 500; i++) {
        log.trace("测试日志同步和异步打印的性能trace,timeout=" + Integer.valueOf(timeout) * (75 + 25) + (int) (1 + Math.random() * (10000 - 1 + 1)));
        log.debug("测试日志同步和异步打印的性能debug,timeout=" + Integer.valueOf(timeout) * (75 + 25) + (int) (1 + Math.random() * (10000 - 1 + 1)));
        log.info("测试日志同步和异步打印的性能info,timeout=" + Integer.valueOf(timeout) * (75 + 25) + (int) (1 + Math.random() * (10000 - 1 + 1)));
        log.warn("测试日志同步和异步打印的性能warn,timeout=" + Integer.valueOf(timeout) * (75 + 25) + (int) (1 + Math.random() * (10000 - 1 + 1)));
        log.error("测试日志同步和异步打印的性能error,timeout=" + Integer.valueOf(timeout) * (75 + 25) + (int) (1 + Math.random() * (10000 - 1 + 1)));
    }

    long endTime = System.currentTimeMillis();

    System.out.println("FeignClientController.logbackAsyncAppender--->测试日志同步和异步打印的性能 end,耗时:" + (endTime - startTime) + "毫秒。");// 此for循环输出日志的耗时。同步模式下,平均耗时158ms;异步模式下,平均耗时:27ms

    return clientService.feignClientLogbackAsyncAppender(timeout);
}

3.3、结果对比

1)同步输出日志:
在这里插入图片描述

2)异步输出日志:
在这里插入图片描述

重点关注指标【TPS】吞吐量:系统在单位时间内处理请求的数量。在同步输出日志中TPS为15.3/sec,异步TPS为19.9/sec,可以明显看到性能有所提升!

实际线上的服务处理时间导致的暂停时间会远远大于模拟时间,因此异步的优势应该更大。

4、异步日志输出原理

异步输出日志中最关键的就是配置文件中ch.qos.logback.classic包下AsyncAppenderBase类中的append方法,感兴趣的可以查看一下源码了解下。

可以配置的项为queueSize,discardingThreshold。

discardingThreshold:通过队列情况判断是否需要丢弃日志,不丢弃的话将它放到阻塞队列中。默认情况下,当blockingQueue的容量高于阈值时(80%),会丢弃ERROR以下级别日志,如果不希望丢弃日志(既每次都全量保存),那可以设置为0。 如正常日志可以丢弃,那可以极大的提升性能,并保存关键的ERROR日志。
queueSize:这个阻塞队列为ArrayBlockingQueueu,默认大小为256,可以通过配置文件进行修改。

核心思想:写文件是通过新起一个线程去完成的,主线程将日志扔到阻塞队列中,然后继续做其他事情。

5、对比测试工程源码

https://download.csdn.net/download/want_you_gogo/84377177

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-03-12 17:18:07  更:2022-03-12 17:22:31 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 8:29:21-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码