如何在不会导致服务器宕机的情况下,用 PHP 读取大文件

作为PHP开发人员,我们并不经常需要担心内存管理。PHP 引擎在我们背后做了很好的清理工作,短期执行上下文的 Web 服务器模型意味着即使是最潦草的代码也不会造成持久的影响。

很少情况下我们可能需要走出这个舒适的地方 ——比如当我们试图在一个大型项目上运行 Composer 来创建我们可以创建的最小的 VPS 时,或者当我们需要在一个同样小的服务器上读取大文件时。

后面的问题就是我们将在本教程中深入探讨的。

在 GitHub上可以找到本教程的源码。

衡量成功的标准
确保我们对代码有改进的唯一方法是测试一个不好的情况,然后将我们修复之后的测量与另一个进行比较。换句话说,除非我们知道“解决方案”对我们有多大的帮助(如果有的话),否则我们不知道它是否真的是一个解决方案。

这里有两个我们可以关系的衡量标准。首先是CPU使用率。我们要处理的进程有多快或多慢?第二是内存使用情况。脚本执行时需要多少内存?这两个通常是成反比的 – 这意味着我们可以以CPU使用率为代价来降低内存使用,反之亦然。

在一个异步执行模型(如多进程或多线程的PHP应用程序)中,CPU和内存的使用率是很重要的考量因素。在传统的PHP架构中,当任何一个值达到服务器的极限时,这些通常都会成为问题。

测量PHP内的CPU使用率是不切实际的。如果这是你要关注的领域,请考虑在Ubuntu或MacOS上使用类似top的工具。对于Windows,请考虑使用Linux子系统,以便在Ubuntu中使用top。

为了本教程的目的,我们将测量内存使用情况。我们将看看在“传统”的脚本中使用了多少内存。我们将执行一些优化策略并对其进行度量。最后,我希望你能够做出一个有经验的选择。
我们查看内存使用多少的方法是:

// formatBytes is taken from the php.net documentation

memory_get_peak_usage();

function formatBytes($bytes, $precision = 2) {
$units = array(“b”, “kb”, “mb”, “gb”, “tb”);

$bytes = max($bytes, 0);
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
$pow = min($pow, count($units) - 1);

$bytes /= (1 << (10 * $pow));

return round($bytes, $precision) . " " . $units[$pow];

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// formatBytes is taken from the php.net documentation

memory_get_peak_usage();

function formatBytes($bytes, $precision = 2) {
$units = array(“b”, “kb”, “mb”, “gb”, “tb”);

$bytes = max($bytes, 0);
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
$pow = min($pow, count($units) - 1);

$bytes /= (1 << (10 * $pow));

return round($bytes, $precision) . " " . $units[$pow];

}
我们将在脚本的最后使用这些函数,以便我们能够看到哪个脚本一次使用最大的内存。

我们的选择是什么?
这里有很多方法可以有效地读取文件。但是也有两种我们可能使用它们的情况。我们想要同时读取和处理所有数据,输出处理过的数据或根据我们所读取的内容执行其他操作。我们也可能想要转换一个数据流,而不需要真正访问的数据。
让我们设想一下,对于第一种情况,我们希望读取一个文件,并且每10,000行创建一个独立排队的处理作业。我们需要在内存中保留至少10000行,并将它们传递给排队的工作管理器(无论采取何种形式)。
对于第二种情况,我们假设我们想要压缩一个特别大的API响应的内容。我们不在乎它的内容是什么,但我们需要确保它是以压缩形式备份的。
在这两种情况下,如果我们需要读取大文件,首先,我们需要知道数据是什么。第二,我们并不在乎数据是什么。让我们来探索这些选择吧…

逐行读取文件
有许多操作文件的函数,我们把部分结合到一个简单的文件阅读器中(封装为一个方法):

// from memory.php

function formatBytes($bytes, $precision = 2) {
$units = array(“b”, “kb”, “mb”, “gb”, “tb”);

$bytes = max($bytes, 0);
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
$pow = min($pow, count($units) - 1);

$bytes /= (1 << (10 * $pow));

return round($bytes, $precision) . " " . $units[$pow];

}

print formatBytes(memory_get_peak_usage());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// from memory.php

function formatBytes($bytes, $precision = 2) {
$units = array(“b”, “kb”, “mb”, “gb”, “tb”);

$bytes = max($bytes, 0);
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
$pow = min($pow, count($units) - 1);

$bytes /= (1 << (10 * $pow));

return round($bytes, $precision) . " " . $units[$pow];

}

print formatBytes(memory_get_peak_usage());
// from reading-files-line-by-line-1.php

function readTheFile($path) {
$lines = [];
h a n d l e = f o p e n ( handle = fopen( handle=fopen(path, “r”);

while(!feof($handle)) {
    $lines[] = trim(fgets($handle));
}

fclose($handle);
return $lines;

}

readTheFile(“shakespeare.txt”);

require “memory.php”;

7
8
9
10
11
12
13
14
15
// formatBytes is taken from the php.net documentation

memory_get_peak_usage();

function formatBytes($bytes, $precision = 2) {
$units = array(“b”, “kb”, “mb”, “gb”, “tb”);

$bytes = max($bytes, 0);
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
$pow = min($pow, count($units) - 1);

$bytes /= (1 << (10 * $pow));

return round($bytes, $precision) . " " . $units[$pow];

}
// from reading-files-line-by-line-1.php

function readTheFile($path) {
$lines = [];
h a n d l e = f o p e n ( handle = fopen( handle=fopen(path, “r”);

while(!feof($handle)) {
    $lines[] = trim(fgets($handle));
}

fclose($handle);
return $lines;

}

readTheFile(“shakespeare.txt”);

require “memory.php”;
我们读取一个文本文件为莎士比亚全集。文件大小为5.5MB,内存占用峰值为12.8MB。现在让我们用一个生成器来读取每一行:

// from reading-files-line-by-line-2.php

function readTheFile($path) {
h a n d l e = f o p e n ( handle = fopen( handle=fopen(path, “r”);

while(!feof($handle)) {
    yield trim(fgets($handle));
}

fclose($handle);

}

readTheFile(“shakespeare.txt”);

require “memory.php”;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// from reading-files-line-by-line-2.php

function readTheFile($path) {
h a n d l e = f o p e n ( handle = fopen( handle=fopen(path, “r”);

while(!feof($handle)) {
    yield trim(fgets($handle));
}

fclose($handle);

}

readTheFile(“shakespeare.txt”);

require “memory.php”;
文本文件大小不变,但内存使用峰值只是393KB。即使我们能把读取到的数据做一些事情也并不意味着什么。也许我们可以在看到两条空白时把文档分割成块,像这样:

// from reading-files-line-by-line-3.php

$iterator = readTheFile(“shakespeare.txt”);

$buffer = “”;

foreach ($iterator as $iteration) {
preg_match("/\n{3}/", $buffer, $matches);

if (count($matches)) {
    print ".";
    $buffer = "";
} else {
    $buffer .= $iteration . PHP_EOL;
}

}

require “memory.php”;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// from reading-files-line-by-line-3.php

$iterator = readTheFile(“shakespeare.txt”);

$buffer = “”;

foreach ($iterator as $iteration) {
preg_match("/\n{3}/", $buffer, $matches);

if (count($matches)) {
    print ".";
    $buffer = "";
} else {
    $buffer .= $iteration . PHP_EOL;
}

}

require “memory.php”;
猜到我们使用了多少内存吗?我们把文档分割为1216块,仍然只使用了459KB的内存,这是否让你惊讶?考虑到生成器的性质,我们使用的最多内存是使用在迭代中我们需要存储的最大文本块。在本例中,最大的块为101985字符。

我已经撰写了使用生成器提示性能和Nikita Popov的迭代器库,如果你感兴趣就去看看吧!

生成器还有其它用途,但是最明显的好处就是高性能读取大文件。如果我们需要处理这些数据,生成器可能是最好的方法。

管道间的文件
在我们不需要处理数据的情况下,我们可以把文件数据传递到另一个文件。通常被称为管道(大概是因为我们看不到除了两端的管子里面,当然,它也是不透明的),我们可以通过使用流方法实现。让我们先写一个脚本从一个文件传到另一个文件。这样我们可以测量内存的占用情况:

// from piping-files-1.php

file_put_contents(
“piping-files-1.txt”, file_get_contents(“shakespeare.txt”)
);

require “memory.php”;
1
2
3
4
5
6
7
// from piping-files-1.php

file_put_contents(
“piping-files-1.txt”, file_get_contents(“shakespeare.txt”)
);

require “memory.php”;
不出所料,这个脚本使用更多的内存来进行文本文件复制。这是因为它读取(和保留)文件内容在内存中,直到它被写到新文件中。对于小文件这种方法也许没问题。当为更大的文件时,就捉襟见肘了…

让我们尝试用流(管道)来传送一个文件到另一个:

// from piping-files-2.php

$handle1 = fopen(“shakespeare.txt”, “r”);
$handle2 = fopen(“piping-files-2.txt”, “w”);

stream_copy_to_stream($handle1, $handle2);

fclose( h a n d l e 1 ) ; f c l o s e ( handle1); fclose( handle1);fclose(handle2);

require “memory.php”;
1
2
3
4
5
6
7
8
9
10
11
// from piping-files-2.php

$handle1 = fopen(“shakespeare.txt”, “r”);
$handle2 = fopen(“piping-files-2.txt”, “w”);

stream_copy_to_stream($handle1, $handle2);

fclose( h a n d l e 1 ) ; f c l o s e ( handle1); fclose( handle1);fclose(handle2);

require “memory.php”;
这段代码稍微有点陌生。我们打开了两文件的句柄,第一个是只读模式,第二个是只写模式,然后我们从第一个复制到第二个中。最后我们关闭了它,也许使你惊讶,内存只占用了393KB

这似乎很熟悉。像代码生成器在存储它读到的每一行代码?那是因为第二个参数fgets规定了每行读多少个字节(默认值是-1或者直到下一行为止)。

第三个参数stream_copy_to_stream和第二个参数是同一类参数(默认值相同),stream_copy_to_stream一次从一个数据流里读一行,同时写到另一个数据流里。它跳过生成器只有一个值的部分(因为我们不需要这个值)。

这篇文章对于我们来说可能是没用的,所以让我们想一些我们可能会用到的例子。假设我们想从我们的CDN中输出一张图片,作为一种重定向的路由应用程序。我们可以参照下边的代码来实现它:

// from piping-files-3.php

file_put_contents(
“piping-files-3.jpeg”, file_get_contents(
https://github.com/assertchris/uploads/raw/master/rick.jpg
)
);

// …or write this straight to stdout, if we don’t need the memory info

require “memory.php”;
1
2
3
4
5
6
7
8
9
10
11
// from piping-files-3.php

file_put_contents(
“piping-files-3.jpeg”, file_get_contents(
https://github.com/assertchris/uploads/raw/master/rick.jpg
)
);

// …or write this straight to stdout, if we don’t need the memory info

require “memory.php”;
设想一下,一个路由应用程序让我们看到这段代码。但是,我们想从CDN获取一个文件,而不是从本地的文件系统获取。我们可以用一些其他的东西来更好的替换file_get_contents(就像Guzzle),即使在引擎内部它们几乎是一样的。

图片的内存大概有581K。现在,让我们来试试这个

// from piping-files-4.php

$handle1 = fopen(
https://github.com/assertchris/uploads/raw/master/rick.jpg”, “r”
);

$handle2 = fopen(
“piping-files-4.jpeg”, “w”
);

// …or write this straight to stdout, if we don’t need the memory info

stream_copy_to_stream($handle1, $handle2);

fclose( h a n d l e 1 ) ; f c l o s e ( handle1); fclose( handle1);fclose(handle2);

require “memory.php”;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// from piping-files-4.php

$handle1 = fopen(
https://github.com/assertchris/uploads/raw/master/rick.jpg”, “r”
);

$handle2 = fopen(
“piping-files-4.jpeg”, “w”
);

// …or write this straight to stdout, if we don’t need the memory info

stream_copy_to_stream($handle1, $handle2);

fclose( h a n d l e 1 ) ; f c l o s e ( handle1); fclose( handle1);fclose(handle2);

require “memory.php”;
内存使用明显变少(大概400K),但是结果是一样的。如果我们不关注内存信息,我们依旧可以用标准模式输出。实际上,PHP提供了一个简单的方式来完成:

$handle1 = fopen(
https://github.com/assertchris/uploads/raw/master/rick.jpg”, “r”
);

$handle2 = fopen(
“php://stdout”, “w”
);

stream_copy_to_stream($handle1, $handle2);

fclose( h a n d l e 1 ) ; f c l o s e ( handle1); fclose( handle1);fclose(handle2);

// require “memory.php”;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$handle1 = fopen(
https://github.com/assertchris/uploads/raw/master/rick.jpg”, “r”
);

$handle2 = fopen(
“php://stdout”, “w”
);

stream_copy_to_stream($handle1, $handle2);

fclose( h a n d l e 1 ) ; f c l o s e ( handle1); fclose( handle1);fclose(handle2);

// require “memory.php”;
其它流
还有其它一些流,我们可以通过管道来写入和读取(或只读取/只写入):

php://stdin (只读)
php://stderr (只写, 如php://stdout)
php://input (只读) 这使我们能够访问原始请求体
php://output (只写) 让我们写入输出缓冲区
php://memory 和 php://temp (读-写) 是我们可以临时存储数据的地方。 不同之处在于一旦它变得足够大 php://temp 会将数据存储在文件系统中,而 php://memory 将一直持存储在内存中直到资源耗尽。
过滤器
还有一个我们可以在stream上使用的技巧,称为过滤器。它们是一种中间的步骤,提供对stream数据的一些控制,但不把他们暴露给我们。想象一下,我们会使用Zip扩展名来压缩我们的shakespeare.txt文件。

// from filters-1.php

$zip = new ZipArchive();
$filename = “filters-1.zip”;

z i p − &gt; o p e n ( zip-&gt;open( zip>open(filename, ZipArchive::CREATE);
$zip->addFromString(“shakespeare.txt”, file_get_contents(“shakespeare.txt”));
$zip->close();

require “memory.php”;
1
2
3
4
5
6
7
8
9
10
// from filters-1.php

$zip = new ZipArchive();
$filename = “filters-1.zip”;

z i p − &gt; o p e n ( zip-&gt;open( zip>open(filename, ZipArchive::CREATE);
$zip->addFromString(“shakespeare.txt”, file_get_contents(“shakespeare.txt”));
$zip->close();

require “memory.php”;
这是一小段整洁的代码,但它测量内存占用在10.75MB左右。使用过滤器的话,我们可以减少内存:

// from filters-2.php

$handle1 = fopen(
“php://filter/zlib.deflate/resource=shakespeare.txt”, “r”
);

$handle2 = fopen(
“filters-2.deflated”, “w”
);

stream_copy_to_stream($handle1, $handle2);

fclose( h a n d l e 1 ) ; f c l o s e ( handle1); fclose( handle1);fclose(handle2);

require “memory.php”;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// from filters-2.php

$handle1 = fopen(
“php://filter/zlib.deflate/resource=shakespeare.txt”, “r”
);

$handle2 = fopen(
“filters-2.deflated”, “w”
);

stream_copy_to_stream($handle1, $handle2);

fclose( h a n d l e 1 ) ; f c l o s e ( handle1); fclose( handle1);fclose(handle2);

require “memory.php”;
此处,我们可以看到名为php://filter/zlib.deflate的过滤器,它读取并压缩资源的内容。我们可以在之后将压缩数据导出到另一个文件中。这仅使用了896KB.

我知道这是不一样的格式,或者制作zip存档是有好处的。你不得不怀疑:如果你可以选择不同的格式并节省约12倍的内存,为什么不选呢?

为了解压此数据,我们可以通过执行另一个zlib filter将压缩后的数据还原:

// from filters-2.php

file_get_contents(
“php://filter/zlib.inflate/resource=filters-2.deflated”
);
1
2
3
4
5
// from filters-2.php

file_get_contents(
“php://filter/zlib.inflate/resource=filters-2.deflated”
);
Streams have been extensively covered in Stream在“理解PHP中的流”和“U高效使用PHP中的流”中已经被全面介绍了。如果你喜欢一个完全不同的视角,可以阅读一下。

定制流
fopen和file_get_contents有它们自己的一套默认选项,但是这些都是完全可定制的。为了定义它们,我们需要创建一个新的流上下文:

// from creating-contexts-1.php

$data = join("&", [
“twitter=assertchris”,
]);

KaTeX parse error: Can't use function '\r' in math mode at position 17: …eaders = join("\̲r̲\n", [ "Con…data),
]);

$options = [
“http” => [
“method” => “POST”,
“header”=> $headers,
“content” => $data,
],
];

c o n t e x t = s t r e a m c o n t e n t c r e a t e ( context = stream_content_create( context=streamcontentcreate(options);

$handle = fopen(“https://example.com/register”, “r”, false, $context);
r e s p o n s e = s t r e a m g e t c o n t e n t s ( response = stream_get_contents( response=streamgetcontents(handle);

fclose($handle);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// from creating-contexts-1.php

$data = join("&", [
“twitter=assertchris”,
]);

KaTeX parse error: Can't use function '\r' in math mode at position 17: …eaders = join("\̲r̲\n", [ "Con…data),
]);

$options = [
“http” => [
“method” => “POST”,
“header”=> $headers,
“content” => $data,
],
];

c o n t e x t = s t r e a m c o n t e n t c r e a t e ( context = stream_content_create( context=streamcontentcreate(options);

$handle = fopen(“https://example.com/register”, “r”, false, $context);
r e s p o n s e = s t r e a m g e t c o n t e n t s ( response = stream_get_contents( response=streamgetcontents(handle);

fclose($handle);
在这个例子中,我们正在尝试向API发出POST请求。 API终端是安全的,但我们仍然需要使用http上下文属性(用于http和https)。我们设置一些消息头参数,并打开一个文件句柄到API。由于上下文处理写操作,我们可以将句柄打开为只读。

查看文档了解更多。

制定自定义协议和过滤器
在我们结束之前,让我们谈谈制定自定义协议。 如果你查看文档,你可以找到一个示例类来实现:

Protocol {
public resource $context;
public __construct ( void )
public __destruct ( void )
public bool dir_closedir ( void )
public bool dir_opendir ( string $path , int $options )
public string dir_readdir ( void )
public bool dir_rewinddir ( void )
public bool mkdir ( string $path , int $mode , int $options )
public bool rename ( string $path_from , string $path_to )
public bool rmdir ( string $path , int $options )
public resource stream_cast ( int $cast_as )
public void stream_close ( void )
public bool stream_eof ( void )
public bool stream_flush ( void )
public bool stream_lock ( int $operation )
public bool stream_metadata ( string $path , int $option , mixed $value )
public bool stream_open ( string $path , string $mode , int KaTeX parse error: Expected 'EOF', got '&' at position 26: … string &̲opened_path )
public string stream_read ( int $count )
public bool stream_seek ( int $offset , int $whence = SEEK_SET )
public bool stream_set_option ( int $option , int $arg1 , int $arg2 )
public array stream_stat ( void )
public int stream_tell ( void )
public bool stream_truncate ( int $new_size )
public int stream_write ( string $data )
public bool unlink ( string $path )
public array url_stat ( string $path , int $flags )
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Protocol {
public resource $context;
public __construct ( void )
public __destruct ( void )
public bool dir_closedir ( void )
public bool dir_opendir ( string $path , int $options )
public string dir_readdir ( void )
public bool dir_rewinddir ( void )
public bool mkdir ( string $path , int $mode , int $options )
public bool rename ( string $path_from , string $path_to )
public bool rmdir ( string $path , int $options )
public resource stream_cast ( int $cast_as )
public void stream_close ( void )
public bool stream_eof ( void )
public bool stream_flush ( void )
public bool stream_lock ( int $operation )
public bool stream_metadata ( string $path , int $option , mixed $value )
public bool stream_open ( string $path , string $mode , int KaTeX parse error: Expected 'EOF', got '&' at position 26: … string &̲opened_path )
public string stream_read ( int $count )
public bool stream_seek ( int $offset , int $whence = SEEK_SET )
public bool stream_set_option ( int $option , int $arg1 , int $arg2 )
public array stream_stat ( void )
public int stream_tell ( void )
public bool stream_truncate ( int $new_size )
public int stream_write ( string $data )
public bool unlink ( string $path )
public array url_stat ( string $path , int $flags )
}
我们不打算实现其中的一个,因为我认为它应该有自己的教程。这里有很多工作需要完成。但是一旦这个工作完成,我们可以很容易地注册我们的流包装:

if (in_array(“highlight-names”, stream_get_wrappers())) {
stream_wrapper_unregister(“highlight-names”);
}

stream_wrapper_register(“highlight-names”, “HighlightNamesProtocol”);

$highlighted = file_get_contents(“highlight-names://story.txt”);
1
2
3
4
5
6
7
if (in_array(“highlight-names”, stream_get_wrappers())) {
stream_wrapper_unregister(“highlight-names”);
}

stream_wrapper_register(“highlight-names”, “HighlightNamesProtocol”);

$highlighted = file_get_contents(“highlight-names://story.txt”);
同样,也可以创建自定义流过滤器。该文档有一个示例过滤器类:

Filter {
public $filtername;
public $params
public int filter ( resource $in , resource KaTeX parse error: Expected 'EOF', got '&' at position 11: out , int &̲consumed ,
bool $closing )
public void onClose ( void )
public bool onCreate ( void )
}
1
2
3
4
5
6
7
8
Filter {
public $filtername;
public $params
public int filter ( resource $in , resource KaTeX parse error: Expected 'EOF', got '&' at position 11: out , int &̲consumed ,
bool $closing )
public void onClose ( void )
public bool onCreate ( void )
}
这可以很容易地注册:

h a n d l e = f o p e n ( &quot; s t o r y . t x t &quot; , &quot; w + &quot; ) ; s t r e a m f i l t e r a p p e n d ( handle = fopen(&quot;story.txt&quot;, &quot;w+&quot;); stream_filter_append( handle=fopen("story.txt","w+");streamfilterappend(handle, “highlight-names”, STREAM_FILTER_READ);
1
2
h a n d l e = f o p e n ( &quot; s t o r y . t x t &quot; , &quot; w + &quot; ) ; s t r e a m f i l t e r a p p e n d ( handle = fopen(&quot;story.txt&quot;, &quot;w+&quot;); stream_filter_append( handle=fopen("story.txt","w+");streamfilterappend(handle, “highlight-names”, STREAM_FILTER_READ);
突出显示名称需要匹配新的筛选器类的filtername属性。也可以在php://filter/highligh-names/resource=story.txt字符串中使用自定义过滤器。定义过滤器比定义协议要容易得多。因为协议需要处理目录操作,而过滤器只需处理每个数据块。

如果你有这个想法,我强烈建议你尝试创建自定义协议和过滤器。如果你可以将过滤器应用于stream_copy_to_streamoperations,那么即使在使用大容量文件时,你的应用程序也可以在没有内存的情况下使用。想象一下,编写一个调整大小的图像过滤器或加密的应用程序过滤器。

总结
虽然这不是我们经常遇到的问题,但在处理大文件时很容易搞砸。在异步应用程序中,当我们不注意小心使用内存的话,很容易导致整个服务器宕机。
本教程希望向你介绍一些新的想法(或者让你重新认识他们),以便你可以更多地考虑如何高效地读取和写入大型文件。当我们开始熟悉流程和生成器,并停止使用像file_get_contents这样的函数时,我们的应用程序中就会减少错误的类别,这看起来是很好。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
最近Google服务器遭遇了一次大规模的宕机事件。这次宕机导致全球范围内的用户无法访问Google的所有在线服务,包括Gmail、YouTube、Google搜索等。这对亿万用户和许多企业造成了严重的影响。 据了解,这次宕机是由于Google的服务器基础设施出现了严重的故障所致。服务器是承载和处理网站和应用程序的重要设备,一旦发生故障,就可能导致用户无法正常访问相关服务。在这次宕机事件中,Google的服务器基础设施发生了严重的故障,导致大量用户无法访问Google的各项服务。 宕机事件对用户和企业带来了很大的困扰。很多人依赖Google的各项服务来进行日常工作和学习,而宕机导致他们无法正常使用这些服务,从而影响了他们的工作和学习效率。同时,许多企业和网站也受到了影响,因为它们的在线业务很大程度上依赖于Google的服务。 对于Google来说,这次宕机事件无疑是一次重大的教训。作为全球最大的互联网公司之一,Google需要保证其服务器基础设施的可靠性和稳定性。此次宕机事件暴露了服务器基础设施方面的一些问题,Google需要重视并改进这些问题,以避免类似事件的再次发生。 总的来说,这次Google服务器宕机事件给用户和企业带来了不便和困扰。但是相信Google从中吸取教训,加强服务器基础设施的稳定性和可靠性,以提供更好的服务。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值