我有一个执行一些负载平衡算法的存储过程的麻烦。服务远程调用这个过程,这个过程必须用良好的端口应答以转发消息。
问题是我想要一个很好的性能(500个上限或消息/秒)。因此,服务每秒向数据库请求500次,数据库是将消息转发给它的好端口。
注意:我确信我运行数据库服务器的虚拟机有足够的资源达到500个请求/秒。事实上,对于更简单的查询(Select, update…),我达到了这个数字。
下面是存储过程: -- Function: loadbalancing(integer)
-- DROP FUNCTION loadbalancing(integer);
CREATE OR REPLACE FUNCTION loadbalancing(num integer) RETURNS
integer AS $BODY$DECLARE
-- Declarations
port_selected integer;
total_call_attempts integer;
BEGIN
--
-- *** *** *** *** *** ***
-- *** Load Balancing Algorithm ***
-- *** *** *** *** *** ***
--
-- ***************
-- *** locking ***
-- ***************
perform number_of_call_attempt, destination_rate FROM load_distribution WHERE loadb_id = num FOR UPDATE;
-- *****************************
-- *** select the right port ***
-- *****************************
select port_number into port_selected from ( select *, row_number() over(order by destination_ratio asc,
destination_target_rate desc, port_number asc) as rn from
load_distribution where active = true and loadb_id = num ) t where
t.rn = 1;
-- ******************************************************
-- *** Update the number of call attempts at one port ***
-- ******************************************************
UPDATE load_distribution SET number_of_call_attempt = number_of_call_attempt
+ 1 WHERE port_number = port_selected AND loadb_id = num;
-- **************************************
-- *** Get the total of call attempts ***
-- **************************************
SELECT SUM(number_of_call_attempt) into total_call_attempts FROM
load_distribution WHERE loadb_id = num;
-- *******************************
-- *** Update destination rate ***
-- *******************************
UPDATE load_distribution SET destination_rate = (number_of_call_attempt / total_call_attempts)
WHERE loadb_id = num;
-- ********************************
-- *** Update destination ratio ***
-- ********************************
UPDATE load_distribution SET destination_ratio = (destination_rate / destination_target_rate) WHERE
loadb_id = num;
-- ***********************************************
-- *** Return a port to the requesting service ***
-- ***********************************************
RETURN port_selected;
END;$BODY$ LANGUAGE plpgsql VOLATILE COST 100;
ALTER FUNCTION loadbalancing(integer) OWNER TO postgres;
我遇到的问题是,这最多适用于70或100个请求/秒。当我开始达到200或更多的请求/秒,Postgres数据库发送一个超时异常。
你认为这个问题发生的原因是什么?至于我,我认为这是一个并发访问的问题。实际上,该过程是在其他列值的函数中选择端口号。每次调用/请求到达时,这些列的值都会更改。紧跟其后的请求必须使用已更改的值(干净状态),而不能使用脏状态的值或未更改的值(如果下一个请求在前一个请求更改值时或之前到来)。
因此,我想到的一个解决方案是使用SELECT FOR UPDATE进行显式锁定。不幸的是,我在我的算法中放入的那个似乎不起作用,或者它不是一个好的解决方案。我是锁和存储过程的新手。
那么,你认为解决这个问题的办法是什么?
我认为这是在RDBMS中尝试做的一件非常糟糕的事情。我会使用易失性存储,可能是非事务性的。这里最好的做法几乎肯定是使用现有的、建立良好的负载平衡工具,或者如果您必须自己动手,使用专为小原子数据块的极低延迟快速查询而设计的技术。
在pl/pgsql存储过程中,在多个查询中执行多个窗口和聚合传递是最糟糕的方法。特别是因为你基本上是序列化对行的访问。
您可能不需要这个分配是精确的,无错误的,并完全尊重分配的比例。相反,您可能需要它非常快,并且大致足够好。PL/PgSQL不是这项工作的正确工具,以至于几乎没有任何理由建议您改进它。这是行不通的。
除了锁定问题之外,这种快速更新过程还会产生大量的 MVCC膨胀,这反过来又会产生更多的真空清理负载。All for ACID保证您既不需要也不想要。
如果您确实必须使用这种方法,以下几点将会有所帮助:
- 调用进程的事务的
-
SET synchronous_commit = off
- 使用
UNLOGGED
表作为端口映射表 - 在一条
UPDATE
语句中设置destination_ratio
,destination_rate
和number_of_call_attempt
- 使用
ORDER BY ... LIMIT 1
在你的排序中抓取所需端口,而不是row_number()
…但实际上,整个方法注定要失败。它敲打存储,为行锁做写,为UPDATE
做写,每次都做聚合传递。尽管这可能都是在shared_buffers
中,但仍存在大量的流失。使用UNLOGGED
表和大约30的FILLFACTOR
表可能会有所帮助,并结合异步提交。一点。还是会比较慢。