【转载】Redis 4.0 自动内存碎片整理(Active Defrag)源码分析
click原文链接
原文链接:https://blog.csdn.net/zouhuajianclever/article/details/90669409
阅读本文前建议先阅读此篇博客: Redis源码从哪里读起
Redis 4.0 版本增加了许多不错的新功能,其中自动内存碎片整理功能 activedefrag 肯定是非常诱人的一个,这让 Redis 集群回收内存碎片相比 Redis 3.0 更加优雅,便利。我们升级 Redis 4.0 后直接开启了activedefrag,经过删除部分 key 测试,发现它确实能有效的释放内存碎片,但是并没有测试它其他相关参数。
一、问题现象
由于业务需要,我们删除了集群中占内存 2/3 的 Key,删除后集群平均碎片率在 1.3 ~ 1.4,内存明显下降,但是此时服务的响应猛然增高,我们通过 redis.cli -c -h 127.0.0.1 -p 5020 --latency 在服务端测试集群性能,发现响应(网络+排队)达到了 2-3ms,这对于 redis 来说已经非常高了,我们其他集群响应一般都在 0.2ms 左右。经过排查后,我们尝试将 activedefrag 功能关闭,并测试,发现 redis 服务端响应马上恢复正常,线上服务响应也降了下来,打开 activedefrag 响应马上飙高。
二、Redis 4.0 源码分析(基于分支 4.0)
Active Defrag 功能的核心代码都在 defrag.c 中的activeDefragCycle(void)函数
1. Active Defrag 介绍及相关参数
我们先看一下redis.conf 中关于 activedefrag 的注释(google 翻译)
功能介绍
- 警告此功能是实验性的。然而,即使在生产中也进行了压力测试,并且由多个工程师手动测试了一段时间。
- 什么是主动碎片整理?
- -------------------------------
- 自动(实时)碎片整理允许Redis服务器压缩内存中小数据分配和数据释放之间的空间,从而允许回收内存。
- 碎片化是每个分配器都会发生的一个自然过程(幸运的是,对于Jemalloc来说却不那么重要)和某些工作负载。通常需要重新启动服务器以降低碎片,或者至少刷新所有数据并再次创建。
- 但是,由于Oran Agra为Redis .0实现了这一功能,这个过程可以在运行时以“热”的方式发生,而服务器正在运行。
- 基本上当碎片超过一定水平时(参见下面的配置选项),Redis将开始通过利用某些特定的Jemalloc功能在相邻的内存区域中创建值的新副本(以便了解分配是否导致碎片并分配它在一个更好的地方),同时,将释放数据的旧副本。对于所有键,以递增方式重复此过程将导致碎片回退到正常值。
- 需要了解的重要事项:
- .默认情况下,此功能处于禁用状态,仅在您编译Redis以使用我们随Redis源代码提供的Jemalloc副本时才有效。这是Linux版本的默认设置。
- .如果没有碎片问题,则永远不需要启用此功能。
- .一旦遇到碎片,可以在需要时使用命令“CONFIG SET activedefrag yes”启用此功能。配置参数能够微调其行为碎片整理过程。如果您不确定它们的含义,最好保持默认设置不变。
参数介绍
- # 开启自动内存碎片整理(总开关)
- activedefrag yes
- # 当碎片达到 100mb 时,开启内存碎片整理
- active-defrag-ignore-bytes 100mb
- # 当碎片超过 % 时,开启内存碎片整理
- active-defrag-threshold-lower
- # 内存碎片超过 %,则尽最大努力整理
- active-defrag-threshold-upper
- # 内存自动整理占用资源最小百分比
- active-defrag-cycle-min
- # 内存自动整理占用资源最大百分比
- active-defrag-cycle-max
2. Active Defrag Timer 在那个线程中执行的?
Redis 是基于事件驱动的,Timer事件和I/O事件会注册到主线程当中,其中内存碎片整理Timer也是在主线程当中执行的。
原文引用[1]
注册timer事件回调。Redis作为一个单线程(single-threaded)的程序,它如果想调度一些异步执行的任务,比如周期性地执行过期key的回收动作,除了依赖事件循环机制,没有其它的办法。这一步就是向前面刚刚创建好的事件循环中注册一个timer事件,并配置成可以周期性地执行一个回调函数:serverCron。由于Redis只有一个主线程,因此这个函数周期性的执行也是在这个线程内,它由事件循环来驱动(即在合适的时机调用),但不影响同一个线程上其它逻辑的执行(相当于按时间分片了)。serverCron函数到底做了什么呢?实际上,它除了周期性地执行过期key的回收动作,还执行了很多其它任务,比如主从重连、Cluster节点间的重连、BGSAVE和AOF rewrite的触发执行,等等。这个不是本文的重点,这里就不展开描述了。
注册I/O事件回调。Redis服务端最主要的工作就是监听I/O事件,从中分析出来自客户端的命令请求,执行命令,然后返回响应结果。对于I/O事件的监听,自然也是依赖事件循环。前面提到过,Redis可以打开两种监听:对于TCP连接的监听和对于Unix domain socket的监听。因此,这里就包含对于这两种I/O事件的回调的注册,两个回调函数分别是acceptTcpHandler和acceptUnixHandler。对于来自Redis客户端的请求的处理,就会走到这两个函数中去。我们在下一部分就会讨论到这个处理过程。另外,其实Redis在这里还会注册一个I/O事件,用于通过管道(pipe)机制与module进行双向通信。这个也不是本文的重点,我们暂时忽略它。
初始化后台线程。Redis会创建一些额外的线程,在后台运行,专门用于处理一些耗时的并且可以被延迟执行的任务(一般是一些清理工作)。在Redis里面这些后台线程被称为bio(Background I/O service)。它们负责的任务包括:可以延迟执行的文件关闭操作(比如unlink命令的执行),AOF的持久化写库操作(即fsync调用,但注意只有可以被延迟执行的fsync操作才在后台线程执行),还有一些大key的清除操作(比如flushdb async命令的执行)。可见bio这个名字有点名不副实,它做的事情不一定跟I/O有关。对于这些后台线程,我们可能还会产生一个疑问:前面的初始化过程,已经注册了一个timer事件回调,即serverCron函数,按说后台线程执行的这些任务似乎也可以放在serverCron中去执行。因为serverCron函数也是可以用来执行后台任务的。实际上这样做是不行的。前面我们已经提到过,serverCron由事件循环来驱动,执行还是在Redis主线程上,相当于和主线程上执行的其它操作(主要是对于命令请求的执行)按时间进行分片了。这样的话,serverCron里面就不能执行过于耗时的操作,否则它就会影响Redis执行命令的响应时间。因此,对于耗时的、并且可以被延迟执行的任务,就只能放到单独的线程中去执行了。
3.Active Defrag Timer 的逻辑什么时候会执行?
在参数介绍中我们能看出,activedefrag是一个总开关,当开启时才有可能执行,而是否真正执行则需要下面几个参数控制。
- void activeDefragCycle(void) {
- /* ... */
- /* 每隔一秒,检查碎片情况,决定是否执行*/
- run_with_period() {
- size_t frag_bytes;
- /* 计算碎片率和碎片大小*/
- float frag_pct = getAllocatorFragmentation(&frag_bytes);
- /* 如果没有运行或碎片低于阈值,则不执行 */
- if (!server.active_defrag_running) {
- /* 根据计算的碎片率和大小与我们设置的参数进行比较判断,决定是否执行 */
- if(frag_pct < server.active_defrag_threshold_lower || frag_bytes < server.active_defrag_ignore_bytes)
- return;
- }
通过源码,我们可以看出碎片整理是否执行主要是通过active_defrag_running, active-defrag-ignore-bytes, active-defrag-threshold-lower 这几个参数共同决定的。
官方默认设置内存碎片率大于10%且内存碎片大小超过100mb。
4.Active Defrag 为什么会影响Redis集群的响应?
我们将 Redis 集群2/3的数据都删除了,碎片率很快降到 1.3 左右,内存也被很快释放,但是为什么 Redis 响应会变高呢?
首先,我们内存碎片整理是在主线程中执行的,通过源码发现,内存碎片整理操作会 scan (通过迭代进行)整个 redis 节点,并进行内存复制、转移等操作,因为 redis 是单线程的,所以这肯定会导致 redis 性能下降(通过调整相关配置可以控制内存整理对 redis 集群的影响,后面会详细说明)。
通过 redis 日志发现,碎片整理还在不停地执行,并使用了75%的CPU(我们将其解释为 redis 主线程资源的 75%),每次执行耗时82s(此处注意,虽然耗时82s,但是并不是 redis 主线程阻塞的这么久的时间,而是从第一次迭代到最后一次迭代之间的时间,在此时间之内主线程可能还会处理命令请求)。
从日志中可见frag=14%,我们配置的参数一直能达到内存碎片整理的阈值,主线程会不停的去进行内存碎片整理,导致redis集群性能变差。
- /* redis 配置及日志
- * activedefrag yes
- * active-defrag-ignore-bytes 100mb
- * active-defrag-threshold-lower 10
- * active-defrag-threshold-upper 100
- * active-defrag-cycle-min 25
- * active-defrag-cycle-max 75 */
- :M May ::17.430 - Starting active defrag, frag=%, frag_bytes=, cpu=%
- :M May ::40.424 - Active defrag done in 82993ms, reallocated=, frag=%, frag_bytes=484365248
# redis 性能
[service@bigdata src]$ ./redis-cli -h 127.0.0.1 -p 5020 --latency min: 0, max: 74, avg: 7.38 (110 samples)
我们先将 activedefrag 置为 no,此时响应马上恢复正常。
- # redis 性能
- min: , max: , avg: 0.14 ( samples)
5.Active Defrag 相关参数该怎么调整?
内存碎片整理的功能我们还是需要的,那么我们该如何调整参数才能在redis性能和内存碎片整理之间找到一个平衡点呢?于是我对这几个参数进行调整测试。
(1) 调整active-defrag-ignore-bytes和active-defrag-threshold-lower
此调整是相对简单的,仅用来判断是否进入内存碎片整理逻辑,如果将碎片率或碎片大小调大至一个能接受的阈值,redis 不进行内存碎片整理,则不会对集群有过多的影响。从下面的代码我们可以发现,当两个条件都满足时,则会进入内存碎片整理逻辑。
- if (!server.active_defrag_running) {
- if(frag_pct < server.active_defrag_threshold_lower || frag_bytes < server.active_defrag_ignore_bytes)
- return;
- }
此处需要注意,frag_pct和frag_bytes 并不等于 info 命令中的 mem_fragmentation_ratio,比如此次问题出现时,mem_fragmentation_ratio = 1.31, 而通过frag_pct计算的碎片率是 1.14,所以设置参数时不能完全参考info中的mem_fragmentation_ratio信息。
- /* frag_pct 是从 jemalloc 获取的 */
- /* Utility function to get the fragmentation ratio from jemalloc.
- * It is critical to do that by comparing only heap maps that belown to
- * jemalloc, and skip ones the jemalloc keeps as spare. Since we use this
- * fragmentation ratio in order to decide if a defrag action should be taken
- * or not, a false detection can cause the defragmenter to waste a lot of CPU
- * without the possibility of getting any results. */
- float getAllocatorFragmentation(size_t *out_frag_bytes) {
- size_t epoch = , allocated = , resident = , active = , sz = sizeof(size_t);
- /* Update the statistics cached by mallctl. */
- je_mallctl("epoch", &epoch, &sz, &epoch, sz);
- /* Unlike RSS, this does not include RSS from shared libraries and other non
- * heap mappings. */
- je_mallctl("stats.resident", &resident, &sz, NULL, );
- /* Unlike resident, this doesn't not include the pages jemalloc reserves
- * for re-use (purge will clean that). */
- je_mallctl("stats.active", &active, &sz, NULL, );
- /* Unlike zmalloc_used_memory, this matches the stats.resident by taking
- * into account all allocations done by this process (not only zmalloc). */
- je_mallctl("stats.allocated", &allocated, &sz, NULL, );
- float frag_pct = ((float)active / allocated)* - ;
- size_t frag_bytes = active - allocated;
- float rss_pct = ((float)resident / allocated)* - ;
- size_t rss_bytes = resident - allocated;
- if(out_frag_bytes)
- *out_frag_bytes = frag_bytes;
- serverLog(LL_DEBUG,
- "allocated=%zu, active=%zu, resident=%zu, frag=%.0f%% (%.0f%% rss), frag_bytes=%zu (%zu%% rss)",
- allocated, active, resident, frag_pct, rss_pct, frag_bytes, rss_bytes);
- return frag_pct;
- }
- /* mem_fragmentation_ratio */
- /* Fragmentation = RSS / allocated-bytes */
- float zmalloc_get_fragmentation_ratio(size_t rss) {
- return (float)rss/zmalloc_used_memory();
- }
(2)调整active-defrag-cycle-min和active-defrag-cycle-max
这两个参数是占用主线程资源比率的上下限,如果想保证内存碎片整理功能不过度影响 redis 集群性能,则需要仔细斟酌着两个参数的配置。
当我调整这两个参数时,我通过观察内存整理时的耗时、资源占用、redis响应等情况发现——当资源占用越多时,内存碎片整理力度越大,时间越短,当然对redis性能的影响也更大。
- # active-defrag-cycle-min
- # active-defrag-cycle-max
- # 日志记录-耗时、资源占用
- :M May ::39.458 - Starting active defrag, frag=%, frag_bytes=, cpu=%
- :M May ::26.160 - Active defrag done in 466700ms, reallocated=, frag=%, frag_bytes=
- # redis 响应
- min: , max: , avg: 2.69 ( samples)
- # active-defrag-cycle-min
- # active-defrag-cycle-max
- # 日志记录-耗时、资源占用
- :M May ::29.988 - Starting active defrag, frag=%, frag_bytes=, cpu=%
- :M May ::58.225 - Active defrag done in 868237ms, reallocated=, frag=%, frag_bytes=
- # redis 响应
- min: , max: , avg: 0.44 ( samples)
(3) 综合调整
在此之前,我们还需要再看一下activeDefragCycle(void)这个函数的具体逻辑
defrag.c
Tips: C 语言中被 static 修饰的变量是全局的,如下代码中的cursor
- /* 从serverCron执行增量碎片整理工作。
- * 这与activeExpireCycle的工作方式类似,我们在调用之间进行增量工作。 */
- void activeDefragCycle(void) {
- static int current_db = -;
- /* 游标,通过迭代scan 整个 redis 节点*/
- static unsigned long cursor = ;
- static redisDb *db = NULL;
- static long long start_scan, start_stat;
- /* 迭代计数器 */
- unsigned int iterations = ;
- unsigned long long defragged = server.stat_active_defrag_hits;
- long long start, timelimit;
- if (server.aof_child_pid!=- || server.rdb_child_pid!=-)
- return; /* Defragging memory while there's a fork will just do damage. */
- /* Once a second, check if we the fragmentation justfies starting a scan
- * or making it more aggressive. */
- run_with_period() {
- size_t frag_bytes;
- float frag_pct = getAllocatorFragmentation(&frag_bytes);
- /* If we're not already running, and below the threshold, exit. */
- if (!server.active_defrag_running) {
- if(frag_pct < server.active_defrag_threshold_lower || frag_bytes < server.active_defrag_ignore_bytes)
- return;
- }
- /* 计算内存碎片整理所需要占用的主线程资源 */
- int cpu_pct = INTERPOLATE(frag_pct,
- server.active_defrag_threshold_lower,
- server.active_defrag_threshold_upper,
- server.active_defrag_cycle_min,
- server.active_defrag_cycle_max);
- /* 限制占用资源范围 */
- cpu_pct = LIMIT(cpu_pct,
- server.active_defrag_cycle_min,
- server.active_defrag_cycle_max);
- /* We allow increasing the aggressiveness during a scan, but don't
- * reduce it. */
- if (!server.active_defrag_running ||
- cpu_pct > server.active_defrag_running)
- {
- server.active_defrag_running = cpu_pct;
- serverLog(LL_VERBOSE,
- "Starting active defrag, frag=%.0f%%, frag_bytes=%zu, cpu=%d%%",
- frag_pct, frag_bytes, cpu_pct);
- }
- }
- if (!server.active_defrag_running)
- return;
- /* See activeExpireCycle for how timelimit is handled. */
- start = ustime();
- /* 计算每次迭代的时间限制 */
- timelimit = *server.active_defrag_running/server.hz/;
- if (timelimit <= ) timelimit = ;
- do {
- if (!cursor) {
- /* Move on to next database, and stop if we reached the last one. */
- if (++current_db >= server.dbnum) {
- long long now = ustime();
- size_t frag_bytes;
- float frag_pct = getAllocatorFragmentation(&frag_bytes);
- serverLog(LL_VERBOSE,
- "Active defrag done in %dms, reallocated=%d, frag=%.0f%%, frag_bytes=%zu",
- (int)((now - start_scan)/), (int)(server.stat_active_defrag_hits - start_stat), frag_pct, frag_bytes);
- start_scan = now;
- current_db = -;
- cursor = ;
- db = NULL;
- server.active_defrag_running = ;
- return;
- }
- else if (current_db==) {
- /* Start a scan from the first database. */
- start_scan = ustime();
- start_stat = server.stat_active_defrag_hits;
- }
- db = &server.db[current_db];
- cursor = ;
- }
- do {
- cursor = dictScan(db->dict, cursor, defragScanCallback, defragDictBucketCallback, db);
- /* Once in 16 scan iterations, or 1000 pointer reallocations
- * (if we have a lot of pointers in one hash bucket), check if we
- * reached the tiem limit. */
- /* 一旦进入16次扫描迭代,或1000次指针重新分配(如果我们在一个散列桶中有很多指针),检查我们是否达到了tiem限制。*/
- if (cursor && (++iterations > || server.stat_active_defrag_hits - defragged > )) {
- /* 如果超时则退出,等待下次获取线程资源后继续执行,*/
- if ((ustime() - start) > timelimit) {
- return;
- }
- iterations = ;
- defragged = server.stat_active_defrag_hits;
- }
- } while(cursor);
- } while();
- }
通过代码逻辑分析,我们注意到有两个计算cpu_pct(资源占用率)的函数
- int cpu_pct = INTERPOLATE(frag_pct,
- server.active_defrag_threshold_lower,
- server.active_defrag_threshold_upper,
- server.active_defrag_cycle_min,
- server.active_defrag_cycle_max);
- cpu_pct = LIMIT(cpu_pct,
- server.active_defrag_cycle_min,
- server.active_defrag_cycle_max);
- /* 插值运算函数 */
- #define INTERPOLATE(x, x1, x2, y1, y2) ( (y1) + ((x)-(x1)) * ((y2)-(y1)) / ((x2)-(x1)) )
- /* 极值函数 */
- #define LIMIT(y, min, max) ((y)<(min)? min: ((y)>(max)? max: (y)))
假设我们设置参数如下(产线配置)
- active-defrag-ignore-bytes 500mb
- active-defrag-threshold-lower
- active-defrag-threshold-upper
- active-defrag-cycle-min
- active-defrag-cycle-max
(1) 我们可以得出第一个计算 cpu_pct的第一个函数 y = 0.1x
(2) 假设此时的 frag_pct = 100 & frag_bytes > 500mb, 则cpu_pct = 10
(3) 在经过求极值函数计算后,得到最后的 cpu_pct的值 10
(4) 然后通过这个值进而计算出timelimit = 1000000*server.active_defrag_running(10)/server.hz(in redis.conf 10)/100 = 10000μs = 10ms
(5) 最后 Redis 自动内存碎片整理功能通过timelimit的值来尽可能的保证不集中性地占用主线程资源
6.Memory Purge 手动整理内存碎片
此处顺便介绍一下 Memory Purge 功能。
memory purge是手动触发整理内存碎片的 Command,它会以一个I/O事件的形式注册到主线程当中去执行。值得注意的是,它和 activedefrag回收的并不是同一块区域的内存,它尝试清除脏页以便内存分配器回收使用。
具体逻辑,我们来看一下源码中的实现,object.c
- /*必须是使用jemalloc内存分配器时才可用*/
- #if defined(USE_JEMALLOC)
- char tmp[];
- unsigned narenas = ;
- size_t sz = sizeof(unsigned);
- /*获取arenas的个数,然后调用jemalloc的接口进行清理 */
- if (!je_mallctl("arenas.narenas", &narenas, &sz, NULL, )) {
- sprintf(tmp, "arena.%d.purge", narenas);
- if (!je_mallctl(tmp, NULL, , NULL, )) {
- addReply(c, shared.ok);
- return;
- }
- }
- addReplyError(c, "Error purging dirty pages");
- #else
- addReply(c, shared.ok);
- /* Nothing to do for other allocators. */
- #endif
关于arenas相关的知识,可以参考这篇文章的解释。原文引用[2]
从产线实际使用的情况中来看,memory purge 的效果相比于activedefrag并没有那么的理想,这也是其机制决定的,但是某些内存碎片率比较极端的情况下,也会起到一定的作用。建议根据实际情况,和activedefrag配合使用。
三、Active Defrag 参数调整建议
综上,我们总结出,我们通过active-defrag-ignore-bytes和active-defrag-threshold-lower来控制是否进行内存碎片整理,通过active-defrag-cycle-min和active-defrag-cycle-max来控制整理内存碎片的力度。
由于各个公司的Redis集群大小,存储的数据结构都会存在差异,所以在开启自动的内存碎片整理的开关后,一定要依据自身的实际情况来设置整理内存碎片的力度的参数。
————————————————
原文链接:https://blog.csdn.net/zouhuajianclever/article/details/90669409
【转载】Redis 4.0 自动内存碎片整理(Active Defrag)源码分析的更多相关文章
- 【转载】Android异步消息处理机制详解及源码分析
PS一句:最终还是选择CSDN来整理发表这几年的知识点,该文章平行迁移到CSDN.因为CSDN也支持MarkDown语法了,牛逼啊! [工匠若水 http://blog.csdn.net/yanbob ...
- RyuBook1.0案例二:Traffic Monitor项目源码分析
Traffic Monitor源码分析 从simple_switch_13.SimpleSwitch13控制器继承并开发 class SimpleMonitor13(simple_switch_13. ...
- Spring Security(3):配置与自动配置的介绍及源码分析
基于注解的配置(Java Configuration)从Spring Security 3.2开始就已经支持,本篇基于Spring boot注解的配置进行讲解,如果需要基于XML配置(Security ...
- Redis核心原理与实践--事务实践与源码分析
Redis支持事务机制,但Redis的事务机制与传统关系型数据库的事务机制并不相同. Redis事务的本质是一组命令的集合(命令队列).事务可以一次执行多个命令,并提供以下保证: (1)事务中的所有命 ...
- Redis(5.0.0)持久化AOF和 RDB 结合源码分析
主要是挖个坑.候补(代码还没看完..) https://github.com/antirez/redis/tree/5.0 一.Redis保存持久化文件 二.Redis启动加载持久化文件 src/se ...
- RyuBook1.0案例一:Switching Hub项目源码分析
开发目标 实现一个带MAC地址学习功能的二层交换机 Openflow交换机与Openflow控制器安全通道建立步骤 switch and controller建立未加密TCP连接或者加密的TLS连接 ...
- Redis学习之zskiplist跳跃表源码分析
跳跃表的定义 跳跃表是一种有序数据结构,它通过在每个结点中维持多个指向其他结点的指针,从而达到快速访问其他结点的目的 跳跃表的结构 关于跳跃表的学习请参考:https://www.jianshu.co ...
- element-ui Carousel 走马灯源码分析整理笔记(十一)
Carousel 走马灯源码分析整理笔记,这篇写的不详细,后面有空补充 main.vue <template> <!--走马灯的最外层包裹div--> <div clas ...
- C# DateTime的11种构造函数 [Abp 源码分析]十五、自动审计记录 .Net 登陆的时候添加验证码 使用Topshelf开发Windows服务、记录日志 日常杂记——C#验证码 c#_生成图片式验证码 C# 利用SharpZipLib生成压缩包 Sql2012如何将远程服务器数据库及表、表结构、表数据导入本地数据库
C# DateTime的11种构造函数 别的也不多说没直接贴代码 using System; using System.Collections.Generic; using System.Glob ...
随机推荐
- delphi十六进制字符串hex转byte数组互相转换bmp图片
procedure Hex2Png(str: string; out png: TPngObject); var stream: TMemoryStream; begin if not Assigne ...
- 【JS新手教程】replace替换一个字符串中所有的某单词
JS中的replace方法可以替换一个字符串中的单词.语句的格式是: 需要改的字符串.replace(字符串或正则表达式,替换成的字符串) 如果第一个参数用字符串,默认是找到该字符串中的第一个匹配的字 ...
- Python3之调试
程序能一次写完并正常运行的概率很小,基本不超过1%.总会有各种各样的bug需要修正.有的bug很简单,看看错误信息就知道,有的bug很复杂,我们需要知道出错时,哪些变量的值是正确的,哪些变量的值是错误 ...
- SpringBoot学习笔记:Swagger实现文档管理
SpringBoot学习笔记:Swagger实现文档管理 Swagger Swagger是一个规范且完整的框架,用于生成.描述.调用和可视化RESTful风格的Web服务.Swagger的目标是对RE ...
- Node.js学习笔记(4):Yarn简明教程
Node.js学习笔记(4):Yarn简明教程. 引入Yarn NPM是常用的包管理工具,现在我们引入是新一代的包管理工具Yarn.其具有快速.安全.可靠的特点. 安装方式 使用npm工具安装yarn ...
- ubuntu 搭建jdk1.8运行环境
参照了:https://blog.csdn.net/smile_from_2015/article/details/80056297 首先下载linux对应的安装包 下载地址:http://www.o ...
- laravel 提交空字符串会被转成null解决方法
在app\Http\Kernel.php文件夹中,注释全局中间件: \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull:: ...
- SSH无密码(密钥验证)登录的配置
进入到我的home目录 cd ~/.ssh [root@shtppATTSTPUBv03 home]# cd ~/.ssh [root@shtppATTSTPUBv03 .ssh]# pwd /roo ...
- 027 Android 可扩展的listview:ExpandableListView的使用案例
1.ExpandableListView简介 ExpandableListView是一种用于垂直滚动展示两级列表的视图,和 ListView 的不同之处就是它可以展示两级列表,分组可以单独展开显示子选 ...
- [转帖]TPC-C解析系列05_TPC-C基准测试之存储优化
TPC-C解析系列05_TPC-C基准测试之存储优化 http://www.itpub.net/2019/10/08/3332/ 蚂蚁金服科技 2019-10-08 11:27:02 本文共3664个 ...