ffmpeg 的内存分配架构
------------------------------------------------------------
author: hjjdebug
date: 2024年 08月 01日 星期四 18:00:47 CST
descripton: ffmpeg 的内存分配架构1
------------------------------------------------------------
ffmpeg 的内配分配搞的人晕菜,来,我们分析一下:
看看下面的调用图,就知道其中关系了!
0 in av_malloc of libavutil/mem.c:86 //跟malloc 差不多! 一个参数(大小),只是64字节对齐
1 in av_mallocz of libavutil/mem.c:239 //带z就是内存清0
2 in av_buffer_create of libavutil/buffer.c:49 //引入AVBuffer的概念
3 in av_buffer_alloc of libavutil/buffer.c:76 //跟malloc 差不多! 一个参数(大小),返回是AVBufferRef *
4 in av_buffer_allocz of libavutil/buffer.c:85 //带z就是内存清0
5 in pool_alloc_buffer of libavutil/buffer.c:352 //无空闲项,分配一个pool_entry,包含一个AVBufferRef* ,数据大小由pool->size决定
6 in av_buffer_pool_get of libavutil/buffer.c:388 //从内存池(AVBufferPool)中申请内存,返回AVBufferRef *
7 in video_get_buffer of libavcodec/decode.c:1663 // 需要拿到YUV3个平面的内存地址,需要3个AVBufferPool
8 in avcodec_default_get_buffer2 of libavcodec/decode.c:1702 //初始化好FramePool内存池(3个AVBufferPool池),然后从video或者从audio拿内存
9 in ff_get_buffer of libavcodec/decode.c:1937 //为frame分配内存地址及copy一些属性
10 in thread_get_buffer_internal of libavcodec/pthread_frame.c:1006 //只是个简单包装
11 in ff_thread_get_buffer of libavcodec/pthread_frame.c:1098 //只是个简单包装
0,1,2,3,4, 这4层是最基础的内存分配架构
5,6是内存池
7,8是video,audio内存分配
9. codec解码frame时前期基本属性copy及内存分配.
10,11: 只是简单封装.
以上即是codec 解码数据包时的内存分配工作. 由此来了解它的底层工作流程
----------------------------------------
第0层: av_malloc
----------------------------------------
void *av_malloc(size_t size)
{
void *ptr=NULL;
posix_memalign(&ptr, 64, size)
return ptr;
}
问题: av_malloc 与 malloc 有什么区别?
答: av_malloc 其实等价于malloc,
但它实际调用的c 库函数是posix_memalign,为的是实现64字节对齐
----------------------------------------
第1层: av_mallocz
----------------------------------------
void *av_mallocz(size_t size)
{
void *ptr = av_malloc(size);
if (ptr) memset(ptr, 0, size);
return ptr;
}
av_mallocz 对返回的内存,用0进行了初始化.
----------------------------------------
第2层: av_buffer_create
----------------------------------------
用途: 为一个已有的内存data,size 分配一个AVBuffer 结构,以后你用AVBufferRef* 结构去访问它
AVBuffer 结构,40个字节, 是一个带有refcount 和 free函数的管理结构,管理传来的buffer(即data,size)
AVBufferRef结构,24个字节
下面看代码:
调用参数比较多,先要搞清楚意思.
第1,2参数: data,size: 被保护的内存
第3 参数: free: 释放该内存的函数
第4 参数: opaque: 内存释放函数的参数
第5 参数: flags: 标志位
返回值: AVBufferRef指针. 返回的指针结构(24byte)虽小,却能访问到所有的东西.
AVBufferRef *av_buffer_create(uint8_t *data, buffer_size_t size,
void (*free)(void *opaque, uint8_t *data),
void *opaque, int flags)
{
AVBuffer *buf = av_mallocz(sizeof(*buf));
buf->data = data;
buf->size = size;
buf->free = free ? free : av_buffer_default_free;//有free给free,否则给个默认的.
buf->opaque = opaque;
atomic_init(&buf->refcount, 1);
buf->flags = flags;
AVBufferRef *ref = av_mallocz(sizeof(*ref));
ref->buffer = buf;
ref->data = data;
ref->size = size;
return ref;
}
问1: 为什么要包装这么一层?
答2: 这样是为了当数据克隆时,我们只需要clone一下AVBufferRef 结构,才24个字节,
把AVBuffer的 refcount加1, 而其所指向的数据可能很大就不用clone了. 岂不妙哉!
问2:那个free 函数有哪些?
答2:再说.
----------------------------------------
第3层 av_buffer_alloc
----------------------------------------
代码:
AVBufferRef *av_buffer_alloc(buffer_size_t size)
{
uint8_t *data = av_malloc(size);
AVBufferRef *ret = av_buffer_create(data, size, av_buffer_default_free, NULL, 0);
return ret;
}
av_buffer_alloc 与 malloc 有什么区别?
前者返回的是AVBufferRef 指针,数据被保护了,而后者返回的直接是数据指针.
原来我们能直接找到总统,现在总统带保镖了.你只能找到保镖,不能直接找到总统了.
----------------------------------------
第4层: av_buffer_allocz
----------------------------------------
AVBufferRef *av_buffer_allocz(buffer_size_t size)
{
AVBufferRef *ret = av_buffer_alloc(size);
memset(ret->data, 0, size);
return ret;
}
带z的函数多了一个数据清0的过程.
----------------------------------------
第5层: pool_alloc_buffer
----------------------------------------
该函数是有内存池创建一个内存池项,
同时创建一个AVBufferRef 项,ref所指内存数据的大小是由pool->size决定的
内存池项p_entry 是隐含的,透明的,但它与ref指针密切相关.
它返回的是AVBufferRef 指针
代码如下:
static AVBufferRef *pool_alloc_buffer(AVBufferPool *pool)
{
AVBufferRef *b_ref = av_buffer_alloc(pool->size);
BufferPoolEntry *p_entry = av_mallocz(sizeof(*p_entry));
p_entry->data = b_ref->buffer->data; // entry中的数据元素来源于ref
p_entry->opaque = b_ref->buffer->opaque;
p_entry->free = b_ref->buffer->free;
p_entry->pool = pool;
b_ref->buffer->opaque = p_entry; //ref 中的参数是p_entry地址
b_ref->buffer->free = pool_release_buffer; //修改buffer中的free函数地址
return b_ref;
}
内存池和内存池项的结构我就不copy了.
内存池就是内存池项的链表管理. 它能增加或减少内存池项.有指针指向空闲链表头
内存池项是内存的又一个保镖,它有指针指向数据,同时还有内存释放函数.
关于内存池项对用户是透明的,看不见的. 当我们也应该清楚其内部操作.
用内存池分配的内存,它把buffer->free 改成了pool_release_buffer.
这样当释放内存的时候不是通过 free 返回系统, 而是通过pool_release_buffer 返回给pool
pool 只是把它作为空闲项加入链表,没有归还系统,留给以后分配使用.
----------------------------------------
第6层: av_buffer_pool_get
----------------------------------------
AVBufferRef *av_buffer_pool_get(AVBufferPool *pool)
{
AVBufferRef *b_ref;
ff_mutex_lock(&pool->mutex);
BufferPoolEntry *p_entry = pool->pool; //从pool 的空闲项链表取得一项
if (p_entry) { // 如果拿到了, 则创建一个AVBufferRef 来返回,只需分配40+24bytes
b_ref = av_buffer_create(p_entry->data, pool->size, pool_release_buffer, p_entry, 0);
if (b_ref) { //修改pool 的空闲项指针
pool->pool = p_entry->next;
p_entry->next = NULL;
}
} else {
b_ref = pool_alloc_buffer(pool); //pool 没有空闲置项,则分配一项,就是上面的分析过程.
}
ff_mutex_unlock(&pool->mutex);
// pool 的引用计数加1,
if (b_ref)
atomic_fetch_add_explicit(&pool->refcount, 1, memory_order_relaxed);
return b_ref;
}
调用相关函数,使pool引用计数减少,当pool引用计数减到0时,整个pool 会释放掉,所有分配的内存返回系统.
----------------------------------------
第7层: video_get_buffer
----------------------------------------
它为AVFrame 分配了内存, 使frame->buf,frame->data 有了数值
视频包含3个平面Y,U,V 所以从3个内存池中申请内存
static int video_get_buffer(AVCodecContext *s, AVFrame *frame)
{
int i;
if (frame->data[0] || frame->data[1] || frame->data[2] || frame->data[3]) {
av_log(s, AV_LOG_ERROR, "frame->data[*]!=NULL in avcodec_default_get_buffer\n");
return -1;
}
//frame->data 是8个指针 unsigned char *[8], 不过一般用3个就够了.
memset(frame->data, 0, sizeof(frame->data));
frame->extended_data = frame->data;
//这里有FramePool的概念,它有4个AVBufferPool 及其它一些参数
//惊叹号!s->internal->pool是一个AVBufferRef
FramePool *fr_pool = (FramePool*)s->internal->pool->data;
for (i = 0; i < 4 && fr_pool->pools[i]; i++) {
frame->linesize[i] = fr_pool->linesize[i];
frame->buf[i] = av_buffer_pool_get(fr_pool->pools[i]); //frame 从缓冲池中得到内存,上面的分析
frame->data[i] = frame->buf[i]->data;
}
for (; i < AV_NUM_DATA_POINTERS; i++) {
frame->data[i] = NULL;
frame->linesize[i] = 0;
}
return 0;
fail:
av_frame_unref(frame);
return AVERROR(ENOMEM);
}
缓冲池由AVCodecContext 来保存! 生命周期是整个数据周期
----------------------------------------
第8层: avcodec_default_get_buffer2
----------------------------------------
int avcodec_default_get_buffer2(AVCodecContext *avctx, AVFrame *frame, int flags)
{
int ret;
//该函数的意义?参看补充
if ((ret = update_frame_pool(avctx, frame)) < 0) return ret;
switch (avctx->codec_type) {
case AVMEDIA_TYPE_VIDEO:
return video_get_buffer(avctx, frame); // 这就是我们上面分析的.
case AVMEDIA_TYPE_AUDIO:
return audio_get_buffer(avctx, frame);
default:
return -1;
}
}
----------------------------------------
第9层 ff_get_buffer
----------------------------------------
为frame分配内存地址及copy一些属性,属性来源于pkt(发来的数据)及avctx
int ff_get_buffer(AVCodecContext *avctx, AVFrame *frame, int flags)
{
if (frame->width <= 0 || frame->height <= 0) { //从avctx中设置 frame的width,height属性
frame->width = FFMAX(avctx->width, AV_CEIL_RSHIFT(avctx->coded_width, avctx->lowres));
frame->height = FFMAX(avctx->height, AV_CEIL_RSHIFT(avctx->coded_height, avctx->lowres));
override_dimensions = 0;
}
ret = ff_decode_frame_props(avctx, frame); // 给frame设置一些属性
avctx->sw_pix_fmt = avctx->pix_fmt;
ret = avctx->get_buffer2(avctx, frame, flags); //它指向avcodec_default_get_buffer2,就是上面的分析
validate_avframe_allocation(avctx, frame); //检查frame 内存分配是否合法, 简单!
ret = ff_attach_decode_data(frame); //简单,创建了一个FrameDecodeData 对象,作为frame->private_ref
return ret;
}
int ff_decode_frame_props(AVCodecContext *avctx, AVFrame *frame)
{
// 从未解码包中(就是发来的packet数据)copy一些属性
AVPacket *pkt = avctx->internal->last_pkt;
frame->pts = pkt->pts;
frame->pkt_pts = pkt->pts;
frame->pkt_pos = pkt->pos;
frame->pkt_duration = pkt->duration;
frame->pkt_size = pkt->size;
//把pkt的size_data 也copy 过来 ...
...
//从avctx 中copy 一些属性
frame->reordered_opaque = avctx->reordered_opaque;
if (frame->color_primaries == AVCOL_PRI_UNSPECIFIED)
frame->color_primaries = avctx->color_primaries;
if (frame->color_trc == AVCOL_TRC_UNSPECIFIED)
frame->color_trc = avctx->color_trc;
if (frame->colorspace == AVCOL_SPC_UNSPECIFIED)
frame->colorspace = avctx->colorspace;
if (frame->color_range == AVCOL_RANGE_UNSPECIFIED)
frame->color_range = avctx->color_range;
if (frame->chroma_location == AVCHROMA_LOC_UNSPECIFIED)
frame->chroma_location = avctx->chroma_sample_location;
switch (avctx->codec->type) {
case AVMEDIA_TYPE_VIDEO:
frame->format = avctx->pix_fmt;
if (!frame->sample_aspect_ratio.num)
frame->sample_aspect_ratio = avctx->sample_aspect_ratio;
}
break;
case AVMEDIA_TYPE_AUDIO:
if (!frame->sample_rate) frame->sample_rate = avctx->sample_rate;
if (frame->format < 0) frame->format = avctx->sample_fmt;
frame->channels = avctx->channels;
break;
}
return 0;
}
----------------------------------------
第10层 thread_get_buffer_internal
----------------------------------------
static int thread_get_buffer_internal(AVCodecContext *avctx, ThreadFrame *f, int flags)
{
#define FF_THREAD_FRAME 1 ///< Decode more than one frame at once
#define FF_THREAD_SLICE 2 ///< Decode more than one part of a single frame at once
if (!(avctx->active_thread_type & FF_THREAD_FRAME)) //active_thread_type 为3
return ff_get_buffer(avctx, f->f, flags); //直接走上面的分析
}
----------------------------------------
第11层 ff_thread_get_buffer
----------------------------------------
int ff_thread_get_buffer(AVCodecContext *avctx, ThreadFrame *f, int flags)
{
return thread_get_buffer_internal(avctx, f, flags);
}
----------------------------------------
补充1: update_frame_pool
----------------------------------------
分配FramePool 并依据avctx, frame初始化FramePool 的参数
根据frame的宽度,高度得到平面的大小size, 即为pool->size, 创建3个pool
static int update_frame_pool(AVCodecContext *avctx, AVFrame *frame)
{
//先查看是否分配过, 分配过就直接返回了.
FramePool *frame_pool = avctx->internal->pool ?
(FramePool*)avctx->internal->pool->data : NULL;
if (frame_pool && frame_pool->format == frame->format) {
if (avctx->codec_type == AVMEDIA_TYPE_VIDEO &&
frame_pool->width == frame->width && frame_pool->height == frame->height)
return 0;
if (avctx->codec_type == AVMEDIA_TYPE_AUDIO && frame_pool->planes == planes &&
frame_pool->channels == ch && frame->nb_samples == frame_pool->samples)
return 0;
}
//显然它是分配了一个FramePool, 又创建了一个AVBuffer来包围FramePool, 返回了AVBufferRef*
AVBufferRef *buf_ref = frame_pool_alloc();
frame_pool = (FramePool*)buf_ref->data;
switch (avctx->codec_type) {
case AVMEDIA_TYPE_VIDEO: {
int linesize[4];
int w = frame->width; //要注意frame->width的值是怎么得来的,留后续补充.
int h = frame->height;
int unaligned;
ptrdiff_t linesize1[4];
size_t size[4];
avcodec_align_dimensions2(avctx, &w, &h, frame_pool->stride_align);
do { //从w 得到linesize, linesize 是被调整过的width
ret = av_image_fill_linesizes(linesize, avctx->pix_fmt, w);
w += w & ~(w - 1);
unaligned = 0;
for (i = 0; i < 4; i++)
unaligned |= linesize[i] % frame_pool->stride_align[i];
} while (unaligned);
for (i = 0; i < 4; i++)
linesize1[i] = linesize[i];
//根据linesize及高度,得到平面的大小size, YUV 是3个平面, 所以4个空间足够
ret = av_image_fill_plane_sizes(size, avctx->pix_fmt, h, linesize1);
for (i = 0; i < 4; i++) {
frame_pool->linesize[i] = linesize[i]; //保留linesize
if (size[i]) { //按size 来初始化内存池
frame_pool->pools[i] = av_buffer_pool_init(size[i] + 16 + STRIDE_ALIGN - 1,
CONFIG_MEMORY_POISONING ?
NULL :
av_buffer_allocz);
}
}
}
frame_pool->format = frame->format; //保留其它参数
frame_pool->width = frame->width;
frame_pool->height = frame->height;
break;
}
case AVMEDIA_TYPE_AUDIO: {
ret = av_samples_get_buffer_size(&frame_pool->linesize[0], ch,
frame->nb_samples, frame->format, 0);
frame_pool->pools[0] = av_buffer_pool_init(frame_pool->linesize[0], NULL);
frame_pool->format = frame->format;
frame_pool->planes = planes;
frame_pool->channels = ch;
frame_pool->samples = frame->nb_samples;
break;
}
default: av_assert0(0);
}
av_buffer_unref(&avctx->internal->pool);
avctx->internal->pool = buf_ref; //重新更新framepool
return 0;
}
相关文章:
ffmpeg 的内存分配架构
------------------------------------------------------------ author: hjjdebug date: 2024年 08月 01日 星期四 18:00:47 CST descripton: ffmpeg 的内存分配架构1 ------------------------------------------------------------ ffmpeg 的内配分配搞的人晕菜&#…...

Vue+live2d实现虚拟人物互动(一次体验叙述)
目录 故事的开头: 最终的实现效果: 实现步骤: 第一步:下载重要文件 第二步:创建vue项目文件,将刚下载文件拷贝到public目录下 第三步:在index.html文件中引入js 第四步:使用&…...
内联函数的概念和用途以及区别
内联函数(Inline Function)是C(以及C99之后的C语言)中的一个特性,旨在通过减少函数调用的开销来提高程序的执行效率。在正常情况下,当程序调用一个函数时,会发生一系列的操作,包括保…...

rust 桌面 sip 软电话(基于tauri 、pjsip库)
本文尝试下rust 的tauri 桌面运用 原因在于体积小 1、pjsip 提供了rust 接口官方的 rust demo 没编译出来 在git找了个sip-phone-rs-master https://github.com/Charles-Schleich/sip-phone-rs 可以自己编译下pjsip lib库替换该项目的lib 2、创建一个tauri demo 引用 [depe…...

Linux 进程优先级、程序地址空间、进程控制
个人主页:仍有未知等待探索-CSDN博客 专题分栏: Linux 目录 一、进程优先级 1、什么是进程优先级? 2、为什么要有优先级? 3、Linux的优先级特点、查看方式 4、命令行参数和环境变量 1.命令行参数 2.环境变量 获取环境变量的…...
学习笔记一
vector 在创建时指定初始大小和初始值: vector<int> a(5, 1) // 包含 5 个整数的 vector,每个值都为 1 可以使用 push_back 方法向 vector 中添加元素: a.push_back(7) // 将整数 7 添加到 vector 的末尾 可以使用 size(…...

Linux中信号的发送及信号的自定义捕捉方法
预备知识: 信号产生时进程早已知道该信号如何处理。 信号产生时进程可能并不能立即处理信号而是等到合适的时候处理。 信号其他相关常见概念 实际执行信号的处理动作称为信号递达(Delivery) 信号从产生到递达之间的状态,称为信号未决(Pending)。 进程可以选择阻…...

yum仓库的制作与使用
目录 前言: 1 查看系统内核 2 获取网络源 3 搭建yum网络仓库 4 rpm包的下载 4.1 将rpm包下载至本地 4.2 对下载的rpm包进行备份 5 制作本地yum源 5.1 软件仓库制作工具createrepo 5.2 使用createrepo创建本地yum仓库 6 搭建docker本地仓库 前言&#x…...
牛客周赛54:D.清楚姐姐跳格子(bfs)
链接:登录—专业IT笔试面试备考平台_牛客网 来源:牛客网 题目描述 \,\,\,\,\,\,\,\,\,\,老妪遂递一羊皮卷轴,上面什么都没有,清楚欲问,老妪却缄口不言。 \,\,\,\,\,\,\,\,\,\,清楚性格刚直&…...

用户空间 lmkd
用户空间 lmkd 1、概览1.1 配置lmkd 2、lmkd2.1 lmkd启动2.2 时序图 Android LowMemoryKiller原理分析 AOSP>文档>核心主题低内>存终止守护程序 1、概览 Android Low Memory Killer Daemon :system/memory/lmkd/README.md Android 低内存终止守护程序 (lm…...

二叉树专题
Leetcode 104. 二叉树的最大深度 class Solution { public:int maxDepth(TreeNode* root) {if(!root) return 0;int leftd maxDepth(root -> left) 1;int rightd maxDepth(root -> right) 1;return max(leftd, rightd);} }; Leetcode 100. 相同的树 class Solution…...
Spring MVC 之简介及常见注解
一、什么是 Spring MVC Spring Web MVC 是基于 Servlet API 构建的原始 Web 框架,从一开始就包含在 Spring 框架中。它的正式名称 “Spring Web MVC” 来自其源模块的名称 (Spring-webmvc),但它通常被称为"Spring MVC"。 什么是Servlet呢? S…...
除了使用本地存储,还有哪些方法可以实现只出现一次的弹窗?
除了使用本地存储,还有以下几种方法可以实现只出现一次的弹窗: 1.使用 Cookie:可以将一个标识符存储在浏览器 Cookie 中,下次用户访问页面时检查 Cookie 中是否存在该标识符,从而判断是否需要显示弹窗。 2.使用服务器端…...

微软蓝屏事件揭示的网络安全深层问题与未来应对策略
目录 微软蓝屏事件揭示的网络安全深层问题与未来应对策略 一、事件背景 二、事件影响 2.1、跨行业连锁反应 2.2、经济损失和社会混乱 三、揭示的网络安全问题 3.2、软件更新管理与风险评估 3.2、系统复杂性与依赖关系 3.3、网络安全意识与培训 四、未来的网络安全方向…...
C#:通用方法总结—第11集
大家好,今天继续分享我们的通用方法系列。 下面是今天要分享的通用方法: (1)这个通用方法为Ug’校验选中体的个数: /// <summary> /// 输出选中体个数 /// </summary> public int CheckOneBody() { int …...

Web开发-html篇-下
这篇是接着上篇的内容,接着介绍html的其他标签及属性的用法,感兴趣的可以从我的html上篇看起 1. 超链接示例 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport&…...

【C++从小白到大牛】多态那些事儿(上)
一、多态的概念 1.1概念: 通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。 二、 多态的定义及实现 2.1多态的构成条件 多态是在不同继承关系的类对象,去调用同一函数,产…...

网站在线查询工具箱源码分享
终极网络工具系统”(SAAS),是一款功能强大的PHP脚本在线查询工具。本版集合了超过470种快速且易用的Web工具,为日常任务处理和开发人员提供了极大的便利。作为一款综合性的网络工具系统,66toolkit不仅满足了用户的基本网络需求,更…...
SSH简写且免密登陆终端设备
问题 通常使用ssh连接远程设备时,需要先执行ssh <username><ip>,然后再输入终端设备的用户密码。比较麻烦。 解决 可以用如下方法设置命令缩写以及免密登陆: 免密 首先在本地生成私钥: ssh-keygen -t rsa # or …...

算力共享中神经网络切片和算力分配策略
目录 神经网络切片 按照算力的分布进行网络层数切片;就是算力越强,运算神经网络层数越多 神经网络切片和算力占比进行映射 算力分配策略 get_current_shard 神经网络切片 按照算力的分布进行网络层数切片;就是算力越强,运算神经网络层数越多 神经网络切片和算力占比进…...
浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)
✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义(Task Definition&…...

Docker 离线安装指南
参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性,不同版本的Docker对内核版本有不同要求。例如,Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本,Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...

【大模型RAG】Docker 一键部署 Milvus 完整攻略
本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...
Linux云原生安全:零信任架构与机密计算
Linux云原生安全:零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言:云原生安全的范式革命 随着云原生技术的普及,安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测,到2025年,零信任架构将成为超…...

Python爬虫(一):爬虫伪装
一、网站防爬机制概述 在当今互联网环境中,具有一定规模或盈利性质的网站几乎都实施了各种防爬措施。这些措施主要分为两大类: 身份验证机制:直接将未经授权的爬虫阻挡在外反爬技术体系:通过各种技术手段增加爬虫获取数据的难度…...

(转)什么是DockerCompose?它有什么作用?
一、什么是DockerCompose? DockerCompose可以基于Compose文件帮我们快速的部署分布式应用,而无需手动一个个创建和运行容器。 Compose文件是一个文本文件,通过指令定义集群中的每个容器如何运行。 DockerCompose就是把DockerFile转换成指令去运行。 …...

Redis数据倾斜问题解决
Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中,部分节点存储的数据量或访问量远高于其他节点,导致这些节点负载过高,影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...

Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中,新增了一个本地验证码接口 /code,使用函数式路由(RouterFunction)和 Hutool 的 Circle…...

初探Service服务发现机制
1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能:服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源…...
return this;返回的是谁
一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请,不同级别的经理有不同的审批权限: // 抽象处理者:审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...