Application of current limiting

In this article, application traffic limiting is to limit the total number of visits per unit time to THE PHP application server, not to the interface, not to individual users. The original approach is to record the total number of visits through Redis and eliminate the visits by expiration time (zero visits per unit time). The practice is simple, the effect is good, but the disadvantages are also obvious, a traffic judgment and setting, at least a network overhead (Redis Incr). The optimization idea is, can omit this Redis operation (directly remove the unlimited flow hahahaha). Limiting the total number of visits, distributed to each app, is their own metric. Yeah, so the application server records state locally, local judgment. How do you record status locally? Many, local files, APC cache. But when it comes to performance, local files are… Never mind. APC is good, the implementation will be a bit more complex, to consider the modify record conflict problem.

Redis version

Here is a brief version of Redis, the official idea, but there are bugs

$old_count = $redis->incr($key); if($old_count == 1) { expire($key, 1); // bug.Why? If it exits before expire, the $key will not expire, at least in the short term, and the service will become unusable when the expire limit is reached. If ($old_count > $limit) {if($old_count > $limit) {if($old_count > $limit) { return false; } else { return true; }Copy the code

Redis version can be setNx lock to implement more stable

My current limiting PHP extension

Said in the beginning, use APC to implement this feature on the Redis is possible, just have to many things more, forget it, tell me about the principle of APC cache, APC USES Shared memory to achieve interprocess communication, zha say, PHP – FPM is much process model, the same process can handle (not at the same time processing) PHP request for many times, in other words, Php-fpm worker processes are resident in memory, so php-fpm woker processes can cache data through memory sharing.

Then the use of memory sharing can be shared between multiple processes of the local server visits, you can save access records.

$solt = 0; $limit = 100; $rl = new M_ratelimit($solt, $limit); If ($rl->acquire()) {$rl->acquire(); return true; } else {echo "{$limit} upper limit "; return false; }Copy the code

Expand the source

// solt struct typedef struct {m_lck_t lock; size_t visit; size_t timeout; } rlimit; // module init if(MUTILS_G(ratelimit_enable)) { if(MUTILS_G(ratelimit_slot_nums) > 10) { php_error(E_ERROR, "Ratelimit slot nums(%d) bigger than 10", MUTILS_G(ratelimit_slot_nums)); return FAILURE; } size = MUTILS_G(ratelimit_slot_nums) * sizeof(rlimit); ah = &alloc_handler; // create a memory share if(! (ah->create_segments((void **)&rlimit_slots, size, &error))) { php_error(E_ERROR, "Shared memory allocator failed '%s': %s", error, strerror(errno)); return FAILURE; } for (i = 0; i < MUTILS_G(ratelimit_slot_nums); + + I) {/ / initializes the solt if (CREATE_LOCK (& (rlimit_slots [I] the lock), & error)) {php_error (E_ERROR, "% s", the error). ah->detach_segment((void **)&rlimit_slots, size); return FAILURE; } rlimit_slots[i].visit = 0; rlimit_slots[i].timeout = 0; }} // M_ratelimit::acquire if(LOCK(&(rlimit_slots[Z_LVAL_P(slot)].lock), &error)) {error = error? error: "rate limit LOCK error"; php_error(E_ERROR, "%s", error); RETURN_FALSE; If (TV < rlimit_slots[Z_LVAL_P(slot)].timeout) {rlimit_slots[Z_LVAL_P(slot)]. Visit += 1; if( rlimit_slots[Z_LVAL_P(slot)].visit <= Z_LVAL_P(limit)) { goto allow; } else { goto deny; } } else { rlimit_slots[Z_LVAL_P(slot)].visit = 1; rlimit_slots[Z_LVAL_P(slot)].timeout = tv + 1; if( rlimit_slots[Z_LVAL_P(slot)].visit <= Z_LVAL_P(limit)) { goto allow; } else { goto deny; } } allow: UNLOCK(&(rlimit_slots[Z_LVAL_P(slot)].lock), &error); RETURN_TRUE;Copy the code

Expand the source