pg从磁盘读取文件

news2024/9/24 15:29:29

**瀚高数据库
目录
环境
文档用途
详细信息

环境
系统平台:Linux x86-64 Red Hat Enterprise Linux 7
版本:14

文档用途
了解存储管理器

详细信息

0. 相关数据类型

打开的每一个段用如下结构表示,pg中有MdfdVec数组并且记录了这个数组的长度。

typedef struct _MdfdVec
{

File        mdfd_vfd;                      /* fd number in fd.c's pool */ 虚拟文件描述符表的下标

BlockNumber mdfd_segno;        /* segment number, from 0 */ 段号,从0开始计数

} MdfdVec;

在这里插入图片描述

1.smgrinit

smgr是storage manager的缩写,即磁盘管理器,它作为磁盘管理器的上层,对下层操作进行一定程度的封装。后端进程启动时会做初始化操作。

由于历史原因,只保留对磁盘(文件系统支持的存储体,用磁盘代表)的操作,NSmgr为1。

初始化只是为磁盘管理器申请了一块内存。

void smgrinit(void)
{

int            i;



for (i = 0; i < NSmgr; i++)

{

    if (smgrsw[i].smgr_init)

    smgrsw[i].smgr_init();

}



/* register the shutdown proc */

on_proc_exit(smgrshutdown, 0);

}



void

mdinit(void)

{

MdCxt = AllocSetContextCreate(TopMemoryContext,

"MdSmgr",

ALLOCSET_DEFAULT_SIZES);

}

2.smgropen

不是真正打开磁盘文件,只是从hash表中查找一个项,该项缓存了包含了对当前关系中的打开的文件描述符以及文件描述符的数目。

hash表或者相应的entry没有就创建。

第一次初始化该项把表的各种类型打开的段的数目全都初始化为0。

关于该hash操作可看另一篇动态hash的support。


```cpp
/*
*    smgropen() -- Return an SMgrRelation object, creating it if need be.

*

*        This does not attempt to actually open the underlying file.

*/

SMgrRelation smgropen(RelFileLocator rlocator, BackendId backend)

{

RelFileLocatorBackend brlocator;

SMgrRelation reln;

bool        found;



if (SMgrRelationHash == NULL)

{

    /* First time through: initialize the hash table */

    HASHCTL        ctl;



    ctl.keysize = sizeof(RelFileLocatorBackend);

    ctl.entrysize = sizeof(SMgrRelationData);

    SMgrRelationHash = hash_create("smgr relation table", 400,

    &ctl, HASH_ELEM | HASH_BLOBS);

    dlist_init(&unowned_relns);

}



 /* Look up or create an entry */

brlocator.locator = rlocator;

brlocator.backend = backend;

reln = (SMgrRelation) hash_search(SMgrRelationHash,

&brlocator,

HASH_ENTER, &found);



/* Initialize it if not present before */

if (!found)

{

    /* hash_search already filled in the lookup key */

    reln->smgr_owner = NULL;

    reln->smgr_targblock = InvalidBlockNumber;

    for (int i = 0; i <= MAX_FORKNUM; ++i)

        reln->smgr_cached_nblocks[i] = InvalidBlockNumber;

    reln->smgr_which = 0;    /* we only have md.c at present */



    /* implementation-specific initialization */

    smgrsw[reln->smgr_which].smgr_open(reln);



    /* it has no owner yet */

    dlist_push_tail(&unowned_relns, &reln->node);

}



return reln;

}






void mdopen(SMgrRelation reln)

{

/* mark it not open */

for (int forknum = 0; forknum <= MAX_FORKNUM; forknum++)

    reln->md_num_open_segs[forknum] = 0;

}


**3. smgrread**

smgrread从非易失性存储上根据存储体类型,文件类型,块号读取指定的表中指定的块,根据本块内容初始化shared buffer中缓存页。

底层会调用md.c (magnetic disk)相关函数,文件名有误导性,因为凡是文件系统支持的存储介质操作都可以由md.c中函数完成该操作,不单单是磁盘。


```cpp
/*

*    smgrread() -- read a particular block from a relation into the supplied buffer.

*

*        This routine is called from the buffer manager in order to

*        instantiate pages in the shared buffer cache.  All storage managers

*        return pages in the format that POSTGRES expects.

*/

void smgrread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,char *buffer)

{

smgrsw[reln->smgr_which].smgr_read(reln, forknum, blocknum, buffer);

}

3.1 mdread

从一个关系中读取指定的块号,默认大小是8KB,比较关键的函数是_mdfd_getseg(),根据指定的参数获得一段,一段默认是1GB。

/*

*    mdread() -- Read the specified block from a relation.

*/

void mdread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, char *buffer)

{

off_t        seekpos;

int            nbytes;

MdfdVec    *v;

// 获取MdfdVec指针,包含了虚拟文件描述符和relation的segment number

v = _mdfd_getseg(reln, forknum, blocknum, false, EXTENSION_FAIL | EXTENSION_CREATE_RECOVERY);

      // 计算读取位置

seekpos = (off_t) BLCKSZ * (blocknum % ((BlockNumber) RELSEG_SIZE));

      // 断言,位置不超过1GB

Assert(seekpos < (off_t) BLCKSZ * RELSEG_SIZE);

      // 该函数主要通过pread系统调用,从指定位置读取BLCKSZ字节的数据到buffer中

nbytes = FileRead(v->mdfd_vfd, buffer, BLCKSZ, seekpos, WAIT_EVENT_DATA_FILE_READ);

// nbytes作为返回结果,判断是否是读出错还是有坏块以及是否在恢复模式

if (nbytes != BLCKSZ)

{

    if (nbytes < 0)

    ereport(ERROR,

    (errcode_for_file_access(),

    errmsg("could not read block %u in file \"%s\": %m",

    blocknum, FilePathName(v->mdfd_vfd))));



/*

* Short read: we are at or past EOF, or we read a partial block at

* EOF.  Normally this is an error; upper levels should never try to

* read a nonexistent block.  However, if zero_damaged_pages is ON or

* we are InRecovery, we should instead return zeroes without

* complaining.  This allows, for example, the case of trying to

* update a block that was later truncated away.

*/

if (zero_damaged_pages || InRecovery)

MemSet(buffer, 0, BLCKSZ);

else

ereport(ERROR,

(errcode(ERRCODE_DATA_CORRUPTED),

errmsg("could not read block %u in file \"%s\": read only %d of %d bytes",

blocknum, FilePathName(v->mdfd_vfd),

nbytes, BLCKSZ)));

}



}

3.2 _mdfd_getseg()

从relation中找一个segment,这个segment包含了blocknum指定的block。

  返回值:要根据behavior判断,可能为NULL,可能是指向MdfdVec的指针,中间可能会创建一个新的段。
/*

*    _mdfd_getseg() -- Find the segment of the relation holding the specified block.

*

*  If the segment doesn't exist, we ereport, return NULL, or create the segment, according to "behavior".  

*  Note: skipFsync is only used in the EXTENSION_CREATE case.

*/

static MdfdVec *_mdfd_getseg(SMgrRelation reln, ForkNumber forknum, BlockNumber blkno, bool skipFsync, int behavior)

{

MdfdVec    *v;

BlockNumber targetseg;

BlockNumber nextsegno;



/* some way to handle non-existent segments needs to be specified */

// 根据blkno算出来的segment可能并不存在,但是某些情况下并不是一种错误,比如在恢复模式

// 针对不存在的segment采取的措施主要是:报告错误、无则创建、返回NULL几种。

// EXTENSION_DONT_OPEN:当前段在磁盘存在,但是之前没有打开,那么将不进行打开操作 

// 该函数上层的调用者会根据自己的情况设置相应的behavior

Assert(behavior & (EXTENSION_FAIL | EXTENSION_CREATE | EXTENSION_RETURN_NULL | EXTENSION_DONT_OPEN));

      

      /* 默认情况下:RELSEG_SIZE = 2^30 / 2 ^ 13 = 2 ^17 = 131072 , 1GB的表文件,8KB的block */

      /* targetset是一个relation在物理存储上的segment number,从0开始 */

targetseg = blkno / ((BlockNumber) RELSEG_SIZE);



/* md_num_open_segs是文件类型数组,一个relation关联的类型有普通的数据文件、fsm、vm等,不同的文件类型在pg用fork(分支)表示

 * forknum是对应于某个fork,reln->md_num_open_segs[forknum]表示某个reln中某种fork打开的段的数量

 * 比如forknum = MAIN_FORKNUM(枚举类型),即普通的数据文件,reln->md_num_open_segs[forknum] == 5, 那么segment = {0, 1, 2, 3, 4}的都已经打开

 * 此时若满足条件,直接返回就可以。

 */

if (targetseg < reln->md_num_open_segs[forknum])

{

    v = &reln->md_seg_fds[forknum][targetseg];

    return v;

}

       

      /*若behavior中设置了EXTENSION_DONT_OPEN,表示只读打开的文件,但是走到这里意味着当前段没有打开,返回NULL */

/* The caller only wants the segment if we already had it open. */

if (behavior & EXTENSION_DONT_OPEN)

    return NULL;



/*打开段要保证连续,例如不能出现0,1, 3, 4只能是0,1,2,3,4

 *md_num_open_segs[forknum]确定当前类型有没有打开的segment, 有则直接打开最近的,没有就打开该类型上的第一个segment

 */

if (reln->md_num_open_segs[forknum] > 0)

    v = &reln->md_seg_fds[forknum][reln->md_num_open_segs[forknum] - 1];

else

{

    v = mdopenfork(reln, forknum, behavior);

    if (!v)

        return NULL; 

}

      

      // nextsegno表示还没打开的下一个段 

for (nextsegno = reln->md_num_open_segs[forknum]; nextsegno <= targetseg;  nextsegno++)

{

   /* 获取单个磁盘文件block的数量,该磁盘文件由pg虚拟文件描述符索引 */

    BlockNumber nblocks = _mdnblocks(reln, forknum, v);

    int            flags = 0;

   /*保证连续打开*/

    Assert(nextsegno == v->mdfd_segno + 1);

   /*一个磁盘文件(segment)中的block的数量总是应该<= RELSEG_SIZE(131072)

    if (nblocks > ((BlockNumber) RELSEG_SIZE))

        elog(FATAL, "segment too big");

            

        // 处于恢复模式,会在段的末尾增加一个block, 并设置flags

        if ((behavior & EXTENSION_CREATE) || (InRecovery && (behavior & EXTENSION_CREATE_RECOVERY)))

       {

              if (nblocks < ((BlockNumber) RELSEG_SIZE))

             {

                    char       *zerobuf = palloc0(BLCKSZ);

                    mdextend(reln, forknum, nextsegno * ((BlockNumber) RELSEG_SIZE) - 1, zerobuf, skipFsync);

                    pfree(zerobuf);

              }

              flags = O_CREAT;

       }

//  EXTENSION_DONT_CHECK_SIZE为checkpointer时设置的标志, 此时mdnblocks会失效,因为这个标志会导致segment在没有被填满的情况下,分配一个新的segment。

// 而计算mdnblocks会假设除最后的一个segment,所有的segment都有1GB/8KB个块(看配置,这里说的默认情况)

        else if (!(behavior & EXTENSION_DONT_CHECK_SIZE) && nblocks < ((BlockNumber) RELSEG_SIZE))

{

    if (behavior & EXTENSION_RETURN_NULL)

   {

         errno = ENOENT;

         return NULL;

   }



      ereport(ERROR,

     (errcode_for_file_access(),

     errmsg("could not open file \"%s\" (target block %u): previous segment is only %u blocks",

    _mdfd_segpath(reln, forknum, nextsegno),

    blkno, nblocks)));

}  // end else if 

      

      // 打开下一个段

v = _mdfd_openseg(reln, forknum, nextsegno, flags);

   

if (v == NULL)

{

    if ((behavior & EXTENSION_RETURN_NULL) && FILE_POSSIBLY_DELETED(errno))

         return NULL;

    ereport(ERROR,

    (errcode_for_file_access(),

    errmsg("could not open file \"%s\" (target block %u): %m",

    _mdfd_segpath(reln, forknum, nextsegno),

    blkno)));

}

} // end for 

return v;

}

3.3 _mdfd_openseg

打开一个段,更新md_num_open_segs和md_seg_fds两个成员变量,并返回MdfdVec类型的指针。

/*

* Open the specified segment of the relation,

* and make a MdfdVec object for it.  Returns NULL on failure.

*/

static MdfdVec *_mdfd_openseg(SMgrRelation reln, ForkNumber forknum, BlockNumber segno, int oflags)

{

MdfdVec    *v;

File        fd;

char       *fullpath;

      

      // 字符串,比如  pg_tblspc/PG_16_202208251/dbOid/relNumber.3 或者 base/dbOid/relNumber

fullpath = _mdfd_segpath(reln, forknum, segno);



// 获取虚拟文件描述符

fd = PathNameOpenFile(fullpath, O_RDWR | PG_BINARY | oflags);

      

     //释放fullpath内存

pfree(fullpath);



if (fd < 0)

    return NULL;



/*

* Segments are always opened in order from lowest to highest, so we must

* be adding a new one at the end.

*/

Assert(segno == reln->md_num_open_segs[forknum]);

      

_fdvec_resize(reln, forknum, segno + 1);



/* fill the entry */

v = &reln->md_seg_fds[forknum][segno];

v->mdfd_vfd = fd;

v->mdfd_segno = segno;



Assert(_mdnblocks(reln, forknum, v) <= ((BlockNumber) RELSEG_SIZE));



/* all done */

return v;

}

3.4 _mdfd_segpath

比较简单,为了记一下在路径后追加segno。

/*
* Return the filename for the specified segment of the relation. The

* returned string is palloc'd.

*/

static char *_mdfd_segpath(SMgrRelation reln, ForkNumber forknum, BlockNumber segno)

{

char       *path,*fullpath;



path = relpath(reln->smgr_rlocator, forknum);



if (segno > 0)

{

    fullpath = psprintf("%s.%u", path, segno);

    pfree(path);

}

else

    fullpath = path;



return fullpath;

}

3.5 _fdvec_resize

对SMgrRelationData中的两个成员变量做调整。

/*

*    _fdvec_resize() -- Resize the fork's open segments array

*/

static void _fdvec_resize(SMgrRelation reln, ForkNumber forknum, int nseg)

{

if (nseg == 0)

{

    if (reln->md_num_open_segs[forknum] > 0)

    {

        pfree(reln->md_seg_fds[forknum]);

        reln->md_seg_fds[forknum] = NULL;

    }

}

else if (reln->md_num_open_segs[forknum] == 0)

{

    reln->md_seg_fds[forknum] = MemoryContextAlloc(MdCxt, sizeof(MdfdVec) * nseg);

}

else

{

    reln->md_seg_fds[forknum] = repalloc(reln->md_seg_fds[forknum],sizeof(MdfdVec) * nseg);

}



reln->md_num_open_segs[forknum] = nseg;

}

3.6 _mdnblocks

获取单个文件占用的block数,用的整除没有把partial block计算在内。

/*
* Get number of blocks present in a single disk file

*/

static BlockNumber _mdnblocks(SMgrRelation reln, ForkNumber forknum, MdfdVec *seg)

{

off_t        len;

      // 从文件开始一直到EOF,该文件占用的字节数。

len = FileSize(seg->mdfd_vfd);



if (len < 0)

    ereport(ERROR,

    (errcode_for_file_access(),

    errmsg("could not seek to end of file \"%s\": %m",

    FilePathName(seg->mdfd_vfd))));



/* note that this calculation will ignore any partial block at EOF */

return (BlockNumber) (len / BLCKSZ);

}

4. mdextend

在指定的关系中增加1个block。

/*
*    mdextend() -- Add a block to the specified relation.

*

*        The semantics are nearly the same as mdwrite(): write at the

*        specified position.  However, this is to be used for the case of

*        extending a relation (i.e., blocknum is at or beyond the current

*        EOF).  Note that we assume writing a block beyond current EOF

*        causes intervening file space to become filled with zeroes.

*/

void mdextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,char *buffer, bool skipFsync)

{

off_t        seekpos;

int            nbytes;

MdfdVec    *v;



      // 这个断言时间成本高,因统计一个关系的所有block数量,要打开所有的段,每个段要用lseek定位到文件末尾获取大小,大文件lseek定位到文件尾慢。

/* This assert is too expensive to have on normally ... */

#ifdef CHECK_WRITE_VS_EXTEND

Assert(blocknum >= mdnblocks(reln, forknum));

#endif



      // 一个relation的大小不能超过0xFFFFFFFF个块,最大的块号大小为0xFFFFFFFE

      // 判断走不到这里,上层buffer manager先行判断

/*

* If a relation manages to grow to 2^32-1 blocks, refuse to extend it any

* more --- we mustn't create a block whose number actually is

* InvalidBlockNumber.  (Note that this failure should be unreachable

* because of upstream checks in bufmgr.c.)

*/

if (blocknum == InvalidBlockNumber)

ereport(ERROR,

(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),

errmsg("cannot extend file \"%s\" beyond %u blocks",

relpath(reln->smgr_rlocator, forknum),

InvalidBlockNumber)));



      // 获取段号

v = _mdfd_getseg(reln, forknum, blocknum, skipFsync, EXTENSION_CREATE);

      // 获取块号

seekpos = (off_t) BLCKSZ * (blocknum % ((BlockNumber) RELSEG_SIZE));

      

Assert(seekpos < (off_t) BLCKSZ * RELSEG_SIZE);

      // pwrite写入,因为信号打断或者没有足够的物理存储空间或者软资源限制,写入的字节数不够nbytes或者返回-1(出错)

if ((nbytes = FileWrite(v->mdfd_vfd, buffer, BLCKSZ, seekpos, WAIT_EVENT_DATA_FILE_EXTEND)) != BLCKSZ)

{

    if (nbytes < 0)

    ereport(ERROR,

    (errcode_for_file_access(),

    errmsg("could not extend file \"%s\": %m",

    FilePathName(v->mdfd_vfd)),

    errhint("Check free disk space.")));



    /* short write: complain appropriately */

    ereport(ERROR,

    (errcode(ERRCODE_DISK_FULL),

    errmsg("could not extend file \"%s\": wrote only %d of %d bytes at block %u",

    FilePathName(v->mdfd_vfd),

    nbytes, BLCKSZ, blocknum),

    errhint("Check free disk space.")));

}



if (!skipFsync && !SmgrIsTemp(reln))

    register_dirty_segment(reln, forknum, v);



Assert(_mdnblocks(reln, forknum, v) <= ((BlockNumber) RELSEG_SIZE));

}

5. mdwrite

/*

*    mdwrite() -- Write the supplied block at the appropriate location.

*

*        This is to be used only for updating already-existing blocks of a

*        relation (ie, those before the current EOF).  To extend a relation,

*        use mdextend().

*/

void mdwrite(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,char *buffer, bool skipFsync)

{

off_t        seekpos;

int            nbytes;

MdfdVec    *v;



/* This assert is too expensive to have on normally ... */

#ifdef CHECK_WRITE_VS_EXTEND

Assert(blocknum < mdnblocks(reln, forknum));

#endif



v = _mdfd_getseg(reln, forknum, blocknum, skipFsync,

EXTENSION_FAIL | EXTENSION_CREATE_RECOVERY);



seekpos = (off_t) BLCKSZ * (blocknum % ((BlockNumber) RELSEG_SIZE));



Assert(seekpos < (off_t) BLCKSZ * RELSEG_SIZE);



nbytes = FileWrite(v->mdfd_vfd, buffer, BLCKSZ, seekpos, WAIT_EVENT_DATA_FILE_WRITE);





if (nbytes != BLCKSZ)

{

if (nbytes < 0)

ereport(ERROR,

(errcode_for_file_access(),

errmsg("could not write block %u in file \"%s\": %m",

blocknum, FilePathName(v->mdfd_vfd))));





/* short write: complain appropriately */

ereport(ERROR,

(errcode(ERRCODE_DISK_FULL),

errmsg("could not write block %u in file \"%s\": wrote only %d of %d bytes",

blocknum,

FilePathName(v->mdfd_vfd),

nbytes, BLCKSZ),

errhint("Check free disk space.")));

}



if (!skipFsync && !SmgrIsTemp(reln))

    register_dirty_segment(reln, forknum, v);

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/452350.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

微信小程序开发--利用和风天气API实现天气预报小程序

本来是参照《微信小程序开发实战》做一个天气预报小程序的&#xff0c;实际运行的时候提示错误&#xff0c;code 400&#xff0c;参数错误。说明问题应该出在查询API的语句上&#xff0c;没有返回结果。 查阅后才知道&#xff0c;可能书籍出版时间较早&#xff0c;现在的和风获…

大数据学习初级入门教程(十六) —— Hadoop 3.x 完全分布式集群的安装、启动和测试

好久没用 Hadoop 集群了&#xff0c;参考以前写的《大数据学习初级入门教程&#xff08;一&#xff09; —— Hadoop 2.x 完全分布式集群的安装、启动和测试_孟郎郎的博客-CSDN博客》和《大数据学习初级入门教程&#xff08;十二&#xff09; —— Hadoop 2.x 集群和 Zookeeper…

C++代码使用 gperftools 工具进行性能分析

文章目录 前言gperftools 是什么使用方法安装工具代码插桩引入工具代码修改关键代码完整示例编译链接启动分析程序 数据分析总结 前言 一直想用 gperftools 做一下性能方面的尝试&#xff0c;之前一直忙着开发&#xff0c;目前已经到了后期&#xff0c;忙里抽闲亲自操作一遍&…

战争教育策略游戏 MiracleGame,开启新阶段重塑生态和玩法

香港 Web3 区块链周刚刚在一片喧嚣中结束。各路大V、KOL 们的 report 都对 GameFi 的前景非常自信。2021-2023年期间&#xff0c;大量资金涌入 GameFi 赛道&#xff0c;GameFi 一旦爆发将会是现象级的出圈事件。 MiracleGame 是一款基于 BNB Chain 构建的英雄和元神主题的战争教…

CorelDRAW2023最新版本图像设计软件

CorelDRAW 2023作为最新版的图像设计软件,在功能上做了较大提升,主要新的功能特性如下: 1. 全新界面设计:采用简约现代的 UI 设计,菜单和工具重新组织,更加直观易用。提供自动提示与设计指导,易于上手。 2. 智能工具与提示:运用 AI技术对用户操作行为和设计习惯进行分析,给出…

【AIGC】手把手使用扩散模型从文本生成图像

手把手使用扩散模型从文本生成图像 从 DALLE 到Stable Diffusion使用diffusers package从文本prompt生成图像参考资料 在这篇文章中&#xff0c;我们将手把手展示如何使用Hugging Face的diffusers包通过文本生成图像。 从 DALLE 到Stable Diffusion DALLE2是收费的&#xff0c…

Python数据分析第02课:环境准备

如果希望快速开始使用 Python 处理数据科学相关的工作&#xff0c;建议大家直接安装 Anaconda&#xff0c;然后使用 Anaconda 中集成的 Notebook 或 JupyterLab 工具来编写代码。因为对于新手来说&#xff0c;先安装官方的 Python 解释器&#xff0c;再逐个安装工作中会使用到的…

【JAVA】easyExcel导出导入使用

EasyExcel是阿里巴巴开源插件之一&#xff0c;主要解决了poi框架使用复杂&#xff0c;sax解析模式不容易操作&#xff0c;数据量大起来容易OOM&#xff0c;解决了POI并发造成的报错。主要解决方式&#xff1a;通过解压文件的方式加载&#xff0c;一行一行地加载&#xff0c;并且…

【剑指 offer】数组中出现次数超过一半的数字

✨个人主页&#xff1a;bit me&#x1f447; ✨当前专栏&#xff1a;算法训练营&#x1f447; 数 组 中 出 现 次 数 超 过 一 半 的 数 字 核心考点: 数组使用&#xff0c;简单算法的设计 描述&#xff1a; 给一个长度为 n 的数组&#xff0c;数组中有一个数字出现的次数超…

php:php-fpm平滑重启为什么无效

一、问题 今天修改了fpm一些配置&#xff0c;需要上线重启fpm&#xff0c;但是发现一瞬间出现很多502的错误请求&#xff0c;查看日志发现以下错误 fpm&#xff1a;重启日志 nginx&#xff1a;错误日志 2023/04/23 15:19:00 [error] 9#0: *1893053661 recv() failed (104: Co…

【服务器数据恢复】重装系统导致分区无法访问的数据恢复案例

服务器数据恢复环境&#xff1a; 磁盘柜raid卡15块磁盘组建一组raid5磁盘阵列&#xff0c;划分2个lun&#xff1b; 上层操作系统划分若干分区&#xff0c;通过LVM扩容方式将其中一个分区加入到了root_lv中&#xff0c;其他分区格式化为XFS文件系统。 服务器故障&#xff1a; 为…

S/MIME电子邮件证书,符合FDA邮件安全要求

美国食品和药物管理局 &#xff08;FDA&#xff09;要求合作伙伴提交或接收电子监管信息时&#xff0c;必须使用数字证书保障通信安全。 01 为什么FDA使用数字证书保障通信安全&#xff1f; 为了维护数据完整性、准确性,有组织地管理文件,FDA为接受机构的电子监管提交设置了电子…

基于开源 web3引擎的三维系统的开发

目录 结合图像特征的平滑恢复技术 1.开发步骤 &#xff12; 应用案例开发 结束语 应用 &#xff37;&#xff45;&#xff42;&#xff13;&#xff24; 引擎开发的计算机仿真系统或虚拟现实系统均需在 &#xff37;&#xff45;&#xff42; 浏览 器 上 运 行 &#xff0…

MII、 RMII、 GMII、 RGMII 接口介绍

1、RGMII 接口概要 以太网的通信离不开物理层 PHY 芯片的支持&#xff0c;以太网 MAC 和 PHY 之间有一个接口&#xff0c;常用的接口有MII、 RMII、 GMII、 RGMII 等。 MII&#xff08;Medium Independent Interface&#xff0c; 媒体独立接口&#xff09;&#xff1a; MII 支持…

技术招聘演化论:怎样从纸上答题升级到实战编程?

创新赛道的出现 一些企业或许已经对招聘管理系统&#xff08;Applicant Tracking System&#xff0c;简称 ATS&#xff09;有一定了解&#xff0c;ATS 可以帮助企业管理招聘流程&#xff0c;其中包括发布招聘信息、接收简历、筛选候选人和安排面试等。在中国&#xff0c;一些知…

seata1.6.0 单机,集群搭建 基于nacos注册中心 mysql数据库

seata1.6.0 单机&#xff0c;集群搭建 基于nacos注册中心 mysql数据库 大纲 1 单机搭建2 集群搭建 由于项目中的dubbo版本为2.6.0 故客户端程序&#xff08;TM RM&#xff09;使用seata-all 1.4.2 &#xff0c;服务端&#xff08;TC&#xff09;使用seata-server-1.6.0.zip …

SQL Server基础 第六章 聚合函数和分组查询

前言 在数据查询的很多场合&#xff0c;除了需要显示数据表的原始数据&#xff0c;还需要对这些数据进行分析、汇总以及求极值等&#xff0c; 如获取公司员工的人数、工资总额、最高工资、最低工资和平均工资等。本章我们将通过学习SQL Server 聚合函数轻松获取上述数据。另外&…

基于Java+SpringBoot+vue学生学习平台详细设计实现

基于JavaSpringBootvue学生学习平台详细设计实现 博主介绍&#xff1a;5年java开发经验&#xff0c;专注Java开发、定制、远程、指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 Java项目精品实战案例《500套》 欢迎点赞 收藏 ⭐留言 文末获取源码联系方式 文章目…

软件架构的演进(超详细)

文章目录 前言1、什么是单体架构2、分布式架构3、SOA架构4、微服务架构总结 前言 之前对各种架构一直有所了解&#xff0c;但也只是了解&#xff0c;没有过多的研究为什么架构在慢慢演进&#xff0c;新架构代替老架构的理由是什么&#xff0c;而刚好最近学习了一下架构的演进过…

人工智能会取代人工翻译吗?

当今社会正处于语言和技术高速发展的阶段&#xff0c;因此语言和技术的碰撞是不可避免的——甚至有些人说这种碰撞已经发生了&#xff0c;我们只是在等待尘埃落定。数字化、物联网、人工智能和机器学习&#xff0c;以及更进一步——智能手机、语音识别&#xff0c;以及互联网和…