php 开发 谷歌扩展程序_PHP应用程序的水平扩展,第2部分

php 开发 谷歌扩展程序

In part 1, we discussed the horizontal scaling of the application layer – scaling out servers to handle the PHP code concurrently via load balancers and other means. We covered sharing of local data, touched on potential optimization, and explained the basics of load balancers.

第1部分中 ,我们讨论了应用程序层的水平扩展-扩展服务器以通过负载平衡器和其他方式同时处理PHP代码。 我们讨论了本地数据的共享,探讨了潜在的优化方法,并介绍了负载均衡器的基本知识。

The application layer, however, isn’t the only thing that needs scaling. With a bigger demand for our app, the demand for higher read/write operations on the database also surfaces. In this part, we’ll look at scaling the database, explain replication, and cover some common pitfalls you might want to avoid.

但是,应用程序层并不是唯一需要扩展的东西。 随着对我们应用程序的需求增加,对数据库更高读取/写入操作的需求也逐渐浮出水面。 在这一部分中,我们将研究扩展数据库,说明复制并介绍一些您可能要避免的常见陷阱。

优化 (Optimization)

As in Part 1, we need to mention optimization first. Indexing your database properly, making sure the tables consist of the least amount of important data and keeping the secondary information in others (users_basic + users_additional + users_alt_contacts, etc – known as database sharding – a complex topic warranting its own article for sure), doing small, atomic queries as opposed to large on-the-fly calculations – all those methods can help you speed up your databases and avoid bottlenecks. There’s one aspect that can help even more, though – query cache.

与第1部分一样,我们首先要提到优化。 正确地索引数据库,确保表包含最少数量的重要数据,并将次要信息保留在其他信息中( users_basic + users_additional + users_alt_contacts等,称为数据库分片 ,这是一个肯定有自己文章的复杂主题),小型的原子查询与大型的动态计算相反,所有这些方法都可以帮助您加速数据库并避免瓶颈。 不过,有一个方面可以提供更多帮助- 查询缓存

Database servers typically cache results and compiled SELECT queries that were last executed. This allows a client (our app) to receive the data from cache instead of having to wait for execution again. Such an approach saves CPU cycles, produces results faster, and frees the servers from doing unnecessary calculations. But the query cache is limited in size, and some data sets change more often than others. Using the cache on all of our data would be ludicrous, especially if some information changes faster than some other information. While the cache can be fine tuned, depending on the database vendor, there’s an approach that lends itself quite nicely to the query cache solution – contextual grouping of servers.

数据库服务器通常会缓存结果和最后执行的已编译SELECT查询。 这允许客户端(我们的应用程序)从缓存接收数据,而不必再次等待执行。 这种方法可以节省CPU周期,更快地产生结果,并使服务器不必进行不必要的计算。 但是查询缓存的大小有限,并且某些数据集的更改频率比其他数据集更高。 在我们所有的数据上使用缓存是很荒谬的,特别是如果某些信息的更改速度快于其他信息。 尽管可以对缓存进行微调,但取决于数据库供应商,有一种方法可以很好地适合于查询缓存解决方案-服务器的上下文分组。

When you group servers, you have groups of servers each dedicated to a specific part of your app. Say I’m making an online gambling website – I could have one group for in-game chats, one group for ongoing games, one group for user accounts. The user accounts group doesn’t take that many reads or writes, at least not as many as the chats or games functions do, so we can make it smaller – have 5 servers there. In the chats group, we can have 10 servers, because the real time communication of the millions of our users is very important for a high quality gambling experience. But there’s no aspect as important as the actual games – our bread and butter – so those get an exceptionally large group, maybe 20 servers.

对服务器进行分组时,将有服务器组,每个服务器组专用于应用程序的特定部分。 假设我正在建立一个在线赌博网站-我可以有一组用于游戏内聊天,一组可以用于正在进行的游戏,一组可以用于用户帐户。 用户帐户组的读取或写入次数不多,至少不像聊天或游戏功能那么多,因此我们可以使其更小-那里有5台服务器。 在聊天组中,我们可以拥有10台服务器,因为数百万用户的实时通信对于高质量的赌博体验非常重要。 但是没有哪一个方面比实际的游戏更重要-我们的面包和黄油-因此那些游戏可以组成一个非常庞大的团队,也许有20台服务器。

This makes sure you do all your user related reads and writes in one group, not touching the others. The games need to be fast, so the largest group is used to make sure they’re served fast (both read and write operations), while the user account page isn’t all that important to people who are already logged in and preparing to change things – we’ve already captured their attention and don’t need to impress them with super-fast responsiveness on the non-business-critical parts of our app.

这样可以确保您在一个组中进行所有与用户相关的读写操作,而不接触其他组。 游戏需要快速运行,因此使用最大的游戏组来确保快速提供服务(包括读取和写入操作),而用户帐户页面对于已经登录并准备进行游戏的用户而言并不那么重要改变事物–我们已经吸引了他们的注意力,无需在应用程序的非业务关键部分上以超快速的响应速度打动他们。

All groups share the same total data (see MSR below), so you can move servers from one group to another – say there’s a huge poker championship and you’ve got millions of users watching the game – the game group could benefit from 4-5 servers taken from the chat group to make sure it can handle the load. That said, there are built in solutions for clustered databases, and one such solution is Master Slave Replication.

所有组共享相同的总数据(请参阅下面的MSR),因此您可以将服务器从一组移动到另一组-假设有一个巨大的扑克锦标赛,并且您有数百万的用户正在观看游戏-该游戏组可以从4-中受益从聊天组中取出5台服务器,以确保它可以处理负载。 就是说,存在针对群集数据库的内置解决方案,而这样的解决方案之一就是“主从复制”。

主从复制 (Master-Slave Replication)

Master-Slave replication (MSR) is a very common feature in modern databases, often built-in. Search the documentation of your particular database vendor for information on whether or not it’s natively supported. MSR is the process of sending all database write operations from one server (the master) to one or more slaves. The slaves then replay the queries, and thus replicate the master’s data. Integrated with a web app, this is how it works, step by step:

主从复制(MSR)是现代数据库中非常常见的功能,通常是内置的。 在您的特定数据库供应商的文档中搜索有关其本机支持的信息。 MSR是将所有数据库写操作从一台服务器(主服务器)发送到一个或多个从属服务器的过程。 从站然后重播查询,从而复制主站的数据。 与网络应用程序集成后,它的工作方式将逐步进行:

  • a visitor performs a write operation on a database. For example, he changes some profile information.

    访问者在数据库上执行写操作。 例如,他更改了一些配置文件信息。
  • the application sends the query to the master database server, as usual

    应用程序照常将查询发送到主数据库服务器
  • the master executes this query and forwards it to all the slaves

    主服务器执行此查询并将其转发给所有从服务器
  • the slaves execute the query, and the same data is now in both master and slave machines

    从服务器执行查询,并且主服务器和从计算机中现在都具有相同的数据
  • further read operations are performed on the slaves

    在从站上执行进一步的读取操作

The final step is what’s important here – we perform read operations on the slaves. This by itself is division of labor between machines – with the Master free to do only writes (generally, there are far fewer writer than reads in a web app, and as such one Master is often enough), and an army of slaves available for fetching, no particular server is overburdened.

最后一步是重要的-我们在从站上执行读取操作。 这本身就是机器之间的分工– Master可以自由地只做写操作(通常,作家比Web应用程序中的阅读要少得多,因此Master通常就足够了),并且有一批奴隶可供提取时,没有特定的服务器负担过重。

MSR is usually activated by default in today’s installations of MariaDB and MySQL.

通常,在今天的MariaDB和MySQL安装中,默认情况下会激活MSR。

分开读写 (Separating reads and writes)

This is where a slight modification to your architecture is required, its complexity being reciprocal to the quality of your code and use of a framework or a good project structure. When needing to separate reads and writes, you’ll need to make separate connections. For writing, you need to connect to a “write DB” (the master), and for reads, you’ll need to connect to one of the slaves. This can, of course, be done manually. For writing, you would connect to a specifically defined master configuration:

这是需要对体系结构进行少量修改的地方,其复杂性与代码质量以及使用框架或良好的项目结构有关。 当需要分开读写时,您需要进行分开的连接。 要进行写入,您需要连接到“写入DB”(主数据库),而对于读取,则需要连接至一个从属数据库。 当然,这可以手动完成。 为了进行编写,您将连接到专门定义的主配置:

<?php
$config = $this->getService('configProvider');

$master = new PDO(
    'mysql:dbname='.$config->db->master->name.';host='.$config->db->master->host, 
    $config->db->master->user, 
    $config->db->master->password
);
$statement = $master->prepare('SOME QUERY FOR UPDATING');
//...

While for reading, a new connection would be required, through a different set of config values:

在读取时,将需要通过不同的一组配置值来建立新的连接:

<?php
$config = $this->getService('configProvider');

$slave = new PDO(
    'mysql:dbname='.$config->db->slave->name.';host='.$config->db->slave->host, 
    $config->db->slave->user, 
    $config->db->slave->password
);
$results = $slave->query('SOME QUERY FOR READING');
//...

If we have multiple slaves, and we know their host addresses, we can do some randomization:

如果我们有多个从属,并且知道它们的主机地址,则可以进行一些随机化:

<?php
$config = $this->getService('configProvider');

// Pick random slave from list of possible slaves
$slaveConfig = $config->db->slaves[array_rand($config->db->slaves)];

$slave = new PDO(
    'mysql:dbname='.$slaveConfig->name.';host='.$slaveConfig->host, 
    $slaveConfig->user, 
    $slaveConfig->password
);
$results = $slave->query('SOME QUERY FOR READING');
//...

Realizing this is awkward to do every time you need to do reads or writes doesn’t take long. A better solution would be to pre-init a master DB connection in a service container of some sort, and then just access that one for writes, without the need to re-establish connection every time you need writes. As for slaves, the same approach can apply – build the randomization function into a “Slave” DB adapter, and just call something like $this->getService('db')->slave, which automatically returns a randomly selected one. This way, you never have to worry about manually selecting them ever again, and as you add more slaves to your cluster, just include their host address into the configuration file.

每次需要进行读取或写入操作都不会花费很长时间,因此意识到这一点很尴尬。 更好的解决方案是在某种服务容器中预先初始化一个主数据库连接,然后仅访问该主数据库连接以进行写入,而无需在每次写入时都重新建立连接。 对于奴隶,可以采用相同的方法–将随机化功能构建到“奴隶”数据库适配器中,然后调用诸如$this->getService('db')->slave ,它会自动返回一个随机选择的东西。 这样,您不必担心再次手动选择它们,并且当您向群集中添加更多从属时,只需将它们的主机地址包括在配置文件中即可。

Want to take it up another notch? Have the slave-fetching service select only those slaves that aren’t taxed or down at the moment – make the slaves report their CPU/RAM usage somewhere repeatedly, and make sure the slave fetching class always selects the one that’s under the least amount of fire. This abstract selector class should also make sure it connects to another slave if the one it originally tried to connect to is down – you need the database reads to remain transparent to the end user, but you still want the admins to know there’s a problem with one of the slaves:

是否想再提高一个等级? 让从属获取服务仅选择那些当前未征税或关闭的从属–使从属在某处重复报告其CPU / RAM的使用情况,并确保从属获取类始终选择数量最少的从属。火。 如果最初尝试连接的从属服务器关闭,则该抽象选择器类还应确保其连接至另一个从属服务器–您需要读取数据库以保持对最终用户的透明性,但您仍希望管理员知道存在问题奴隶之一:

<?php

// ... some class, some connect() method

$config = $this->getService('configProvider');
$mailer = $this->getService('mailer');
$validSlaves = $config->db->slaves;
$bConnectionSuccessful = false;

while (!empty($validSlaves) && !$bConnectionSuccessful) {
    $randomSlaveKey = array_rand($validSlaves);
    $randomSlave = $validSlaves[$randomSlaveKey];
    try {
        $slave = new PDO(
            'mysql:dbname='.$randomSlave->name.';host='.$randomSlave->host, 
            $randomSlave->user, 
            $randomSlave->password
        );
        $bConnectionSuccessful = true;
    } catch (\PDOException $e) {
        unset($validSlaves[$randomSlaveKey]);
        $mailer->reportError( ... ); // Some kind of email sent to the admins, or the master log, etc
    } catch (\Exception $e) {
        unset($validSlaves[$randomSlaveKey]);
        $mailer->reportError( ... ); // Some kind of different email sent to the admins, or the master log, etc
    }
}

if ($bConnectionSuccessful) {
    return $slave;
} 

$mailer->reportError( ... ); // Report that the entire attempt to connect to any slave failed
throw new \Exception( ... ); // or report via an exception with details in the message

This is only pseudocode, but you get the gist – try to connect to a randomly selected slave, if it fails, remove that slave from the array of valid slaves, report the problem, and try again on another slave for as long as there are more to try.

这只是伪代码,但要领会到-尝试连接到随机选择的从站,如果它失败,请从有效从站数组中删除该从站,报告问题,并在有可能的情况下再次尝试另一个从站更多尝试。

读/写同步延迟 (Read/write sync delay)

One thing that might pose a data consistency threat is the sync delay between masters and slaves. On smaller writes, the replication to slaves will be near instant, and bigger queries will, naturally, need longer to execute – this delay also increases as the slave army grows, because the master has more query copies to disperse – some slaves will be ready sooner than others. However, no matter the quality of your cluster’s network, the speed of the individual machines, or the size of the queries, there is no setup in the world able to accurately do the following:

可能构成数据一致性威胁的一件事是主机和从机之间的同步延迟。 在较小的写操作中,对奴隶的复制将接近即时,并且较大的查询自然将需要更长的执行时间-随着奴隶军队的增长,这种延迟也会增加,因为主人要散布更多的查询副本-一些奴隶将准备就绪比别人早。 但是,无论集群网络的质量,单台计算机的速度还是查询的大小,世界上都没有能够准确执行以下操作的设置:

<?php

$db = $this->getService('db');
$master = $db->get('master');
$slave = $db->get('slave');

// value is currently 1
$master->exec('UPDATE `some_table` SET `value` += 1 WHERE `id` = 1'); // executes
$slave->query('SELECT `value` FROM `some_table` WHERE `id` = 1'); // value is still 1

These statements are executed too close to each other, and there is simply no way for the slave to get the update in time to accurately reflect it.

这些语句彼此之间的执行距离太近,从站根本无法及时获取更新以准确反映更新。

There are various workarounds for this, though none are failproof. This inconsistency is something you just have to accept. In most cases, data that was written doesn’t need to be immediately read. If it does, it’s best to just take an approximation – if you expect the value to increase by 1, then just read the original value from the slave, and add 1 to the result, then display it to the user. The master can still propagate the actual update in the background.

有各种各样的解决方法,尽管没有一种可以确保故障。 您必须接受这种不一致。 在大多数情况下,不需要立即读取已写入的数据。 如果是这样,最好只是近似一下-如果您希望该值增加1,则只需从从站读取原始值,并将结果加1,然后将其显示给用户即可。 主机仍可以在后台传播实际更新。

大师失败 (Master fail)

What if a master fails, though? Does the whole system grind to a halt? Well, it shouldn’t. Solutions exist for master failovers, and they usually solve the problem by turning a slave into a master if the master fails. Additional architecture changes are needed, however, to make this possible. The procedure is as follows:

但是,如果主服务器失败怎么办? 整个系统会停顿吗? 好吧,不应该。 存在用于主服务器故障转移的解决方案,并且通常在主服务器发生故障时通过将从服务器转变为主服务器来解决该问题。 但是,需要进行其他体系结构更改才能实现这一点。 步骤如下:

  • A master fails and can no longer accept writes

    主机失败,无法再接受写入
  • Our master fetching script recognizes this, reports an error, and selects a random available slave

    我们的主提取脚本会识别出此错误,报告错误,然后选择一个随机的可用从服务器
  • This slave is given the signal to become the master

    这个从机被告知成为主机
  • All other slaves are given the signal to switch to the new master

    所有其他从站均获得切换到新主站的信号
  • A small data loss is possible, if the master died mid-write and didn’t have time to propagate the data change to all slaves, or if the slave didn’t have time to execute the query before being turned into a master

    如果主服务器在写入过程中死亡,并且没有时间将数据更改传播到所有从服务器,或者从服务器在转换为主服务器之前没有时间执行查询,则可能会造成少量数据丢失。
  • When the master is brought back online, it should be destroyed and set up from scratch as the slave to the new master – doing anything else would be pointless, because catching up to the missed queries from during the downtime would be a fool’s errand. If the failed master machine is the most powerful machine and you really want it to continue being the master, additional safety steps need to be taken which will probably warrant a short downtime until the re-booted master catches up to everything.

    当主服务器重新联机时,应该将其销毁并重新设置为新主服务器的从属服务器-进行其他任何操作都是没有意义的,因为在停机期间追赶错过的查询将是一个愚蠢的事情。 如果发生故障的主机是功能最强大的计算机,而您确实希望它继续作为主机,则需要采取额外的安全步骤,这可能会导致停机时间很短,直到重新启动的主机赶上一切为止。

In MySQL and MariaDB in particular, you can make a slave switch its master with the CHANGE MASTER TO command, while promoting from slave to master is done via STOP SLAVE and RESET MASTER. For more information on master switching, please see the docs.

特别是在MySQL和MariaDB中,您可以使用CHANGE MASTER TO命令使从属服务器切换其主服务器,而通过STOP SLAVERESET MASTER来完成从属服务器到主服务器的升级。 有关主交换的更多信息,请参阅文档

结论 (Conclusion)

In Part 2, we covered database replication and clustering of databases. With both these parts now behind you, hopefully you’ve gathered enough initial knowledge to proceed on your own and build an excellent scalable environment. Do you have any suggestions you think are crucial in horizontal scaling? Would you like to see a more complex part 3? Have we missed some critical aspects? Let us know in the comments below!

在第2部分中,我们介绍了数据库复制和数据库集群。 现在,您已经掌握了这两个部分,希望您已经掌握了足够的初步知识,可以自行进行操作并构建出色的可扩展环境。 您有什么建议对水平缩放至关重要吗? 您是否希望看到更复杂的第3部分? 我们错过了一些关键方面吗? 在下面的评论中让我们知道!

翻译自: https://www.sitepoint.com/horizontal-scaling-php-apps-part-2/

php 开发 谷歌扩展程序

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值