确定最近的镜像 PHP

发布于 2024-12-31 21:35:51 字数 464 浏览 4 评论 0原文

我维护一个包含大量可下载文件的网站。它目前托管在美国的一台服务器上,但我最近在德国购买了一台新服务器。我想将下载镜像到德国的服务器,并在第一台服务器(托管网站)上有一个 PHP 脚本,根据用户的位置检测要使用的文件镜像。例如,如果用户在加拿大,他们应该从我当前在美国的服务器下载文件。如果他们在法国,他们应该从德国获取文件,而不是跨越大西洋下载。那么,我怎样才能确定他们离哪个国家更近呢?

我了解 MaxMind GeoIP,并安装了它,但这只是给了我一个国家/地区,据我所知,无法自动确定给定国家/地区最接近我的两个镜像国家/地区中的哪一个。我想我可以做的是按大陆:让亚洲、欧洲、非洲和澳大利亚的用户从德国获取内容,让北美和南美的访问者从美国获取文件如果有人能想到更好的解决方案,我愿意接受建议。


好吧,我想我会继续我最初的想法,按各大洲进行检查。对于其他想要做此类事情的人来说,这将是一个很好的起点。当我在欧洲有多个镜子时,问题就会出现,但大陆的想法现在必须行得通。

I maintain a site with a bunch of downloadable files. It is currently hosted on a server in the U.S., but I have recently acquired a new server in Germany. I would like to mirror the downloads to the server in Germany, and have a PHP script on the first server (hosting the website) detect which file mirror to use based on the user's location. For instance, if the user is in Canada, they should download the file from my current server in the U.S. If they're in France, they should get the file from Germany, rather than downloading across the Atlantic. How, then, can I determine which country they are closer to?

I know about MaxMind GeoIP, and have it installed, but that just gives me a country, and AFAIK, there is no way to automatically determine which of my two mirror countries the given country is closest to. I suppose what I could do is go by continent: have users in Asia, Europe, Africa, and Australia get the content from Germany, and have visitors from North and South America get the file from the U.S. If anyone can think of a better solution, I'm open to suggestions.


Well, I guess I am going to go go with my original idea of checking by continents. For others looking to do this sort of thing, that will be a good place to start. The problem will come when I have multiple mirrors in Europe, but the continent idea will have to work for now.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

临走之时 2025-01-07 21:35:51

到目前为止,提议的解决方案似乎有大量的开发人员开销。如果这是我必须在自己的应用程序中解决的问题,我可能会选择重新发明轮子,从而节省几个小时的工作时间。

确定最近的镜像(使用邮政编码)

  1. 在阵列中维护可用镜像服务器的邮政编码列表。
  2. 确定用户代理的邮政编码(例如用户输入或 PHP 库)
  3. 计算两个邮政编码之间的距离(例如 PHP 库)
  4. 根据返回的距离继续进行镜像

选择 请记住,较近的距离不一定构成较近的距离更快的响应时间。然而,在您的场景中,假设两个镜像都已启动,一个国家/地区的镜像显然会比另一个国家/地区的镜像更快。继续阅读我认为更“稳健”的解决方案。

资源与资源链接

“特立独行者”方法

在我看来,特立独行者也被称为那些我们今天都使用的这些伟大的库和框架的创新者、问题解决者和发明者。有时会被错误地与“hackish”想法联系起来,但我们接受补充:)

  1. 在接受 $_GET 或 $_POST 请求的镜像服务器上创建您自己的 API 服务。

  2. 此 API 服务将获取给定的 IP 地址并对它进行 ping(),计算响应时间,然后取平均值,将其返回到请求接口(例如,客户端通过其连接的前端门户和/或服务器尝试确定最近的镜像)。平均响应最低的服务器应该是响应最快的服务器,尽管不一定是最接近的。哪个对你来说更重要?请参阅 Ping 站点并以 PHP 形式返回结果,了解有效的 ping()不依赖于在本地执行 shell 命令的函数(例如,与平台无关)。

  3. 最后一步,获取请求客户端的 IP 地址,并将其传递给在后台任一镜像服务器上运行的 API 服务。我们都知道如何获得知识产权,但并不像您想象的那么好。如果您是负载平衡的或位于代理之后,您可能需要首先检查是否有任何这些标头通过(HTTP_FORWARDED、HTTP_FORWARDED_FOR、HTTP_X_FORWARDED、HTTP_X_FORWARDED_FOR、HTTP_CLIENT_IP)。如果是这样,这可能是用户代理的真实 IP 地址。

此时(步骤 3),您可以比较每个镜像在 ping 用户代理时回复的平均响应时间。然后继续选择用户代理应从哪个镜像下载。您将创建的服务流程类似于以下内容:

  1. 用户代理访问门户
  2. 门户使用后台 AJAX/jQuery 请求(或传统的 POST 和重定向)将用户代理的 IP 地址转发到在两个镜像上单独运行的 API 服务。
  3. 在镜像上运行的 API 服务会对其接收的 IP 地址执行 ping 操作,并返回其配置为获取的响应总数的平均值。
  4. Portal 读取返回的平均值并进行比较。

希望有帮助,编码愉快!

Seems there is a lot of developer overhead in the proposed solutions thus far. If this was a problem I had to address in my own applications, I might save me a few hours of work by opting not to reinvent the wheel on this one.

Determining the Closest Mirror (Using Zip Codes)

  1. Maintain a list of postal codes in an array for those mirror servers available.
  2. Determine the postal code of the user agent (e.g. user input or PHP library)
  3. Calculate the distance between the two postal codes (e.g. PHP library)
  4. Proceed with mirror selection based on distance returned

Please keep in mind that a closer distance does not necessarily constitute a faster response time. In the context of your scenario, however, a mirror in one country will obviously be faster than a mirror in another, assuming both mirrors are up. Continue reading for what I consider to be a more "robust" solution.

Resources & Links

The "Maverick" Approach

In my opinion, Mavericks are also known as those innovators, problem solvers, and inventors of these great libraries and frameworks we all use today. Sometimes mistakenly associated with "hackish" ideas, but we embrace the complement :)

  1. Create your own API service on either one of your mirror servers that will accept either a $_GET or $_POST request.

  2. This API service will take an IP address it is given and ping() it, calculating the response times and then taking the average, returning it to the requesting interface (e.g. your frontend portal through which clients are connecting and/or the server trying to determine the closest mirror). The server that responds with the lowest average ought to be your quickest responding server, albeit not necessarily closest. Which is more important to you? See Ping site and return result in PHP for a working ping() function that does not rely on executing shell commands locally (e.g. platform independent).

  3. Last step, derive the IP address of the requesting client and pass it to your API service running on either mirror server in the background. And we all know how to derive the IP, but not as well as you think we might. If you're load balanced or behind a proxy, you may want to first check to see if any of these headers came through (HTTP_FORWARDED, HTTP_FORWARDED_FOR, HTTP_X_FORWARDED, HTTP_X_FORWARDED_FOR, HTTP_CLIENT_IP). If so, that's probably the real IP address of the user agent.

It is at this point (Step 3) where you would compare the averages of response times that each mirror replied with when they went to ping the user agent. Then proceed with selecting which mirror the user agent should download from. The service flow you will have created then resembles something like this:

  1. User agent visits portal
  2. Portal forwards user agent's IP address to API service running separately on both mirrors using a background AJAX/jQuery request (or traditional POST and redirect).
  3. API service running on mirrors pings the IP address it receives and returns an average of the total number of responses it is configured to fetch.
  4. Portal reads the returned averages and compares them.

Hope that helps and happy coding!

柒夜笙歌凉 2025-01-07 21:35:51

如果您只有两个镜像,请在浏览器中启动 AJAX 请求,从每个服务器下载 50K 文件。这个值足够小,不会对用户造成巨大的延迟,但又足够大,可以使计时器测量差异显着 - 当然,您应该稍微考虑一下这个数字。

然后,一旦您找到了“最佳时间”,就设置一个 JS cookie,并在需要下载时重定向到首选镜像。测量可以从后台的下载页面开始,因此用户可能不会注意到延迟(当他们选择所需的文件时)。

您甚至可以在每个 AJAX 操作中回复“服务器负载”,不仅根据响应时间而且根据当前负载选择最佳服务器。因此,即使最近的服务器位于德国,如果后者的负载明显高于第一个服务器,英国用户也会使用美国服务器。

If you have just two mirrors, kick off AJAX requests in your browser that download a 50K file from each server. This is small enough not to represent a huge delay for the user, but large enough to make timer measurement differences significant - though of course you should play with that figure a bit.

Then, once you've got a 'best time', set a JS cookie and redirect to the preferred mirror whenever a download is required. The measurement can be kicked off from a download page in the background, so the user probably won't notice the delay (whilst they are selecting the file they want).

You could even reply with a 'server load' in each AJAX op too, and select the best server not just on response time but on current load also. So, a UK user would use the US server, even though the closest server is in Germany, if the load on the latter is significantly higher than the first.

巷子口的你 2025-01-07 21:35:51

我不记得有哪个图书馆可以做到这一点。但不是建立一个系统,如果我有一个想法,也许可以帮助你。

使用此距离计算器计算两个 IP 之间的距离。或者找出两个IP地址(一台服务器)和(一台访客)的经纬度并计算距离。这是一个伪代码来做到这一点

distance = ( 3956 *2 * ASIN( SQRT( POWER( SIN( ( 34.1012181 - ABS( latitude ) ) * PI( ) /180 /2 ) , 2 ) + COS( 34.1012181 * PI( ) /180 ) * COS( ABS( latitude ) * PI( ) /180 ) * POWER( SIN( ( ABS( - 118.325739 ) - ABS( longitude ) ) * PI( ) /180 /2 ) , 2 ) ) ))

I dont remember any library that can do this. But instead of build a system, If I have an idea, that might be able to help you out.

Calculate the distance between two IP's using this distance calculator. Or find out the latitude and longitude of the two IP address (one server) and (one guest) and calculate the distance. Here is a pseudocode to do that

distance = ( 3956 *2 * ASIN( SQRT( POWER( SIN( ( 34.1012181 - ABS( latitude ) ) * PI( ) /180 /2 ) , 2 ) + COS( 34.1012181 * PI( ) /180 ) * COS( ABS( latitude ) * PI( ) /180 ) * POWER( SIN( ( ABS( - 118.325739 ) - ABS( longitude ) ) * PI( ) /180 /2 ) , 2 ) ) ))
暖心男生 2025-01-07 21:35:51

执行跟踪路由(将跟踪路由客户端配置为不解析主机名并设置较小的超时)。

根据跳数和跟踪路由客户端的位置(我想它与 PHP 脚本相同)在美国和德国之间进行选择。

地理距离与网络距离、网络速度或带宽成本无关。

作为跟踪路由的替代方案(因为它是一个黑客的小代码解决方案),
我建议您使用 $_SERVER["REMOTE_ADDR"] 并在 geo ip 数据库< /a> 获取国家/地区代码。如果国家代码不是美洲​​大陆的国家之一,为了避免穿越拥挤的互联网主干网,请回退到德国(此外,您可以将国家代码设置为来自欧洲)。

设置 geo ip 数据库后,我建议您将 IP 地址范围从点格式转换为整数格式,以提高查询速度和方便性。

根据我对上述 geo ip 数据库的经验,它很少会丢失,但这并不重要。

Do a traceroute (configure the traceroute client to not resolve hostnames and with a small timeout).

Based on the number of hops and the location of the traceroute client (I supose it's the same as the PHP script) select between U.S. and Germany.

Geographical distance has nothing to do with network distance and network speed, or bandwidth costs.

Alternatively to the traceroute (since its a hackish, small code solution),
I recommend you to use the $_SERVER["REMOTE_ADDR"] and look it up in a geo ip database to get the country code. If the country code is not one of the countries on the American continents, to avoid crossing a crowded internet backbone, fallback to Germany (additionaly you could condition the country code to be from Europe).

Once you setup the geo ip database, I recommend you convert the IP addresses in the ranges from dotted format to integer format for speed and ease of querying.

From my experience with the above geo ip database, it misses so rarely it doesn't matter.

貪欢 2025-01-07 21:35:51

正如您所说,使用像 geoip 这样的库并使用纬度和经度来比较镜子和用户之间的距离不是更容易吗?

我认为它不那么复杂并且很容易实现,适用于 N 个镜像,并且您不需要询问 Zip 或其他类型的数据来进行引用

its not more easy use some library like geoip as u said and use the latitude and longitude to compare the distance betwen the mirrors and the user ?

I think its less complicated and its much easy to implemen,works for N mirrors and u dont need to ask for Zip or another kind of data to make the references

遇到 2025-01-07 21:35:51

我参加聚会已经很晚了,但这并不是最简单的解决方案,只需 ping 摊位并看看哪个更快。

I'm pretty late to the party but wouldn't be the simplest solution to just ping booth and see which is faster.

没︽人懂的悲伤 2025-01-07 21:35:51

抱歉:聚会也太晚了。 :-)

可以访问 Maxmind GeoLite2 城市数据库 您可以获取镜子和访客的纬度和经度,并相应地重定向。

我创建了一个小脚本,告诉您它将根据离您最近的服务器重定向您的服务器。您只需执行 header("Location: .....); 重定向即可代替 echo 语句。

这是完整的示例:

<?php
/**
 * Credit for the function: https://stackoverflow.com/a/10054282
 * Calculates the great-circle distance between two points, with
 * the Vincenty formula.
 * @param float $latitudeFrom Latitude of start point in [deg decimal]
 * @param float $longitudeFrom Longitude of start point in [deg decimal]
 * @param float $latitudeTo Latitude of target point in [deg decimal]
 * @param float $longitudeTo Longitude of target point in [deg decimal]
 * @param float $earthRadius Mean earth radius in [m]
 * @return float Distance between points in [m] (same as earthRadius)
 */
function vincentyGreatCircleDistance(
  $latitudeFrom, $longitudeFrom, $latitudeTo, $longitudeTo, $earthRadius = 6371000)
{
  // convert from degrees to radians
  $latFrom = deg2rad($latitudeFrom);
  $lonFrom = deg2rad($longitudeFrom);
  $latTo = deg2rad($latitudeTo);
  $lonTo = deg2rad($longitudeTo);

  $lonDelta = $lonTo - $lonFrom;
  $a = pow(cos($latTo) * sin($lonDelta), 2) +
    pow(cos($latFrom) * sin($latTo) - sin($latFrom) * cos($latTo) * cos($lonDelta), 2);
  $b = sin($latFrom) * sin($latTo) + cos($latFrom) * cos($latTo) * cos($lonDelta);

  $angle = atan2(sqrt($a), $b);
  return $angle * $earthRadius;
}

$download_servers = array(      0 => array(     'hostname'  => "ftp.bu.edu",
                                                'longitude' => null,
                                                'latitude'  => null,
                                                'city'      => null,
                                                'distance'  => null),
                                1 => array(     'hostname'  => "www.softlayer.com",
                                                'longitude' => null,
                                                'latitude'  => null,
                                                'city'      => null,
                                                'distance'  => null),
                                2 => array(     'hostname'  => "download.nust.na",
                                                'longitude' => null,
                                                'latitude'  => null,
                                                'city'      => null,
                                                'distance'  => null),
                                3 => array(     'hostname'  => "mirror.isoc.org.il",
                                                'longitude' => null,
                                                'latitude'  => null,
                                                'city'      => null,
                                                'distance'  => null),
                                4 => array(     'hostname'  => "download.nus.edu.sg",
                                                'longitude' => null,
                                                'latitude'  => null,
                                                'city'      => null,
                                                'distance'  => null),
                                5 => array(     'hostname'  => "mirror.yandex.ru",
                                                'longitude' => null,
                                                'latitude'  => null,
                                                'city'      => null,
                                                'distance'  => null),
                                6 => array(     'hostname'  => "ftp.iij.ad.jp",
                                                'longitude' => null,
                                                'latitude'  => null,
                                                'city'      => null,
                                                'distance'  => null)
                          );

require_once( __DIR__ . "/maxmind/geoip2.phar");
use GeoIp2\Database\Reader;

// City DB
$reader = new Reader( __DIR__ . '/maxmind/GeoLite2-City.mmdb');
$record = $reader->city($_SERVER['REMOTE_ADDR']);

$i = 0;
foreach ($download_servers as $server) {
   //$record2 = $reader->city($server['ip']);
   $record2 = $reader->city(gethostbyname($server['hostname']));
   if (!isset($download_servers[$i]['longitude'])) {
      $download_servers[$i]['longitude'] = $record2->location->longitude;
      $download_servers[$i]['latitude'] = $record2->location->latitude;
   }
   $download_servers[$i]['city'] = $record2->city->name;
   $download_servers[$i]['distance'] = vincentyGreatCircleDistance(     $record->location->latitude, $record->location->longitude,
                                                                        $download_servers[$i]['latitude'], $download_servers[$i]['longitude']);
   $i++;
}

$dists = array_column($download_servers, 'distance');
$min = array_search(min($dists), $dists, true);

echo "Nearest download server is: " . $download_servers[$min]['hostname'] . " in " . $download_servers[$min]['city']  . "<br>";
echo "Distance to you (" . $record->city->name . ") is: " . $download_servers[$min]['distance'] / 1000 . " km";

Sorry: Also quite late to the party. :-)

With access to the Maxmind GeoLite2 City Database you can optain latitude and longitude of your mirrors and your visitors and redirect accordingly.

I've created a small script that tells you which server it would redirect you too based on the nearest server to you. Instead of the echo statements you would just do a header("Location: .....); redirect.

Here is the complete exmaple:

<?php
/**
 * Credit for the function: https://stackoverflow.com/a/10054282
 * Calculates the great-circle distance between two points, with
 * the Vincenty formula.
 * @param float $latitudeFrom Latitude of start point in [deg decimal]
 * @param float $longitudeFrom Longitude of start point in [deg decimal]
 * @param float $latitudeTo Latitude of target point in [deg decimal]
 * @param float $longitudeTo Longitude of target point in [deg decimal]
 * @param float $earthRadius Mean earth radius in [m]
 * @return float Distance between points in [m] (same as earthRadius)
 */
function vincentyGreatCircleDistance(
  $latitudeFrom, $longitudeFrom, $latitudeTo, $longitudeTo, $earthRadius = 6371000)
{
  // convert from degrees to radians
  $latFrom = deg2rad($latitudeFrom);
  $lonFrom = deg2rad($longitudeFrom);
  $latTo = deg2rad($latitudeTo);
  $lonTo = deg2rad($longitudeTo);

  $lonDelta = $lonTo - $lonFrom;
  $a = pow(cos($latTo) * sin($lonDelta), 2) +
    pow(cos($latFrom) * sin($latTo) - sin($latFrom) * cos($latTo) * cos($lonDelta), 2);
  $b = sin($latFrom) * sin($latTo) + cos($latFrom) * cos($latTo) * cos($lonDelta);

  $angle = atan2(sqrt($a), $b);
  return $angle * $earthRadius;
}

$download_servers = array(      0 => array(     'hostname'  => "ftp.bu.edu",
                                                'longitude' => null,
                                                'latitude'  => null,
                                                'city'      => null,
                                                'distance'  => null),
                                1 => array(     'hostname'  => "www.softlayer.com",
                                                'longitude' => null,
                                                'latitude'  => null,
                                                'city'      => null,
                                                'distance'  => null),
                                2 => array(     'hostname'  => "download.nust.na",
                                                'longitude' => null,
                                                'latitude'  => null,
                                                'city'      => null,
                                                'distance'  => null),
                                3 => array(     'hostname'  => "mirror.isoc.org.il",
                                                'longitude' => null,
                                                'latitude'  => null,
                                                'city'      => null,
                                                'distance'  => null),
                                4 => array(     'hostname'  => "download.nus.edu.sg",
                                                'longitude' => null,
                                                'latitude'  => null,
                                                'city'      => null,
                                                'distance'  => null),
                                5 => array(     'hostname'  => "mirror.yandex.ru",
                                                'longitude' => null,
                                                'latitude'  => null,
                                                'city'      => null,
                                                'distance'  => null),
                                6 => array(     'hostname'  => "ftp.iij.ad.jp",
                                                'longitude' => null,
                                                'latitude'  => null,
                                                'city'      => null,
                                                'distance'  => null)
                          );

require_once( __DIR__ . "/maxmind/geoip2.phar");
use GeoIp2\Database\Reader;

// City DB
$reader = new Reader( __DIR__ . '/maxmind/GeoLite2-City.mmdb');
$record = $reader->city($_SERVER['REMOTE_ADDR']);

$i = 0;
foreach ($download_servers as $server) {
   //$record2 = $reader->city($server['ip']);
   $record2 = $reader->city(gethostbyname($server['hostname']));
   if (!isset($download_servers[$i]['longitude'])) {
      $download_servers[$i]['longitude'] = $record2->location->longitude;
      $download_servers[$i]['latitude'] = $record2->location->latitude;
   }
   $download_servers[$i]['city'] = $record2->city->name;
   $download_servers[$i]['distance'] = vincentyGreatCircleDistance(     $record->location->latitude, $record->location->longitude,
                                                                        $download_servers[$i]['latitude'], $download_servers[$i]['longitude']);
   $i++;
}

$dists = array_column($download_servers, 'distance');
$min = array_search(min($dists), $dists, true);

echo "Nearest download server is: " . $download_servers[$min]['hostname'] . " in " . $download_servers[$min]['city']  . "<br>";
echo "Distance to you (" . $record->city->name . ") is: " . $download_servers[$min]['distance'] / 1000 . " km";
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文