Documentation |
Documentation -> Tutorials -> Key-Value InterfaceThis page has been visited 21081 times. Table of Content (hide) 1. Key-Value InterfaceThe Key-Value interface added into OpenSIPS provides an easy way for the script writer and the module writer to transparently access different Key-Value type back-ends, very similar to the way the DB interface allows easy connection to different types of SQL databases. Modules that offer actual back-end connection will use the Key-Value interface in order to provide the actual functionality to the end-user. At the current stage in time ( Official 1.7 release is out, trunk holds the future 1.8 ), there are four modules that use the interface :
2. Core APIThe OpenSIPS core offers an API for operating with any memory caching system from the script. This API is composed out of the following functions:
Each of the above functions in the API receive as first parameter the ENGINE_ID of the targeted cache system, a plain-text string. The three modules previously listed that offer the actual implementation behave in the following way, in regards to providing the ENGINE_ID :
3. Use Cases and Examples3.1 Password caching for DB authenticationThe idea - how toThe idea is, after performing a DB authentication (authentication by fetching the password from DB) to store the password in memcache; the password is stored with a certain lifetime to ensure that from time to time the password is read from DB again. When a new authentication is needed, first we check if the password is available in memcache (previously stored and not yet expired); if so, we use the value from there to perform authentication without a DB hit. As we have to store in the same time the passwords of more than one users, we need to use different names for the attributes -> the attribute name will contain the user name, something like "passwd_username". The logic - diagram
Script exampleCurrent format of authentication script for REGISTER is: if (!www_authorize("", "subscriber")) { www_challenge("", "0"); exit; }; By adding memcache support, the script looks like: .... loadmodule "modules/db_mysql/db_mysql.so" loadmodule "modules/auth/auth.so" loadmodule "modules/auth_db/auth_db.so" loadmodule "modules/localcache/localcache.so" .... modparam("auth","username_spec","$avp(i:54)") modparam("auth","password_spec","$avp(i:55)") modparam("auth","calculate_ha1",1) modparam("auth_db", "calculate_ha1", yes) modparam("auth_db", "password_column", "password") modparam("auth_db", "db_url","mysql://opensips:opensipsrw@localhost/opensips_1_2") modparam("auth_db", "load_credentials", "$avp(i:55)=password") .... .... route[x]{ ..... # do we have the password cached ? if(cache_fetch("local","passwd_$tu",$avp(i:55))) { $avp(i:54) = $tU; xlog("SCRIPT: stored password is $avp(i:55)\n"); # perform auth from variables # $avp(i:54) contains the username # $avp(i:55) contains the password if (!pv_www_authorize("")) { # authentication failed -> do challenge www_challenge("", "0"); exit; }; } else { # perform DB authentication -> # password will be loaded from DB automatically if (!www_authorize("", "subscriber")) { # authentication failed -> do challenge www_challenge("", "0"); exit; }; # after DB authentication, the password is available # in $avp(i:55) because of the "load_credentials" # module parameter. xlog("SCRIPT: storing password <$avp(i:55)>\n"); # use a 20 minutes lifetime for the password; # after that, it will erased from cache and we do # db authentication again (refresh the passwd from DB) cache_store("local","passwd_$tu","$avp(i:55)",1200); } .... } References:
Performance improvementAssuming a 30 minute registration period and placing a call each 20 minutes, without any caching, there will be 5 authentication queries to DB per hour per user. This means, for 1000 users, per day : 1000 * 3 * 24 = 72000 queries per day for auth. If you set a 2 hours lifetime for cached data, you have one query per user at each 2 hours. So, for the same 1000 users, per day, you have : 1000 * 0.5 * 24 = 12000 queries per day for auth So, this gives you a reduction to with 83% (to 17%) of the numbers of queries! 3.2 Simple but distributed billingThe idea - how toOne can have multiple distributed OpenSIPS servers responsible for the same domain, for redundancy and load-balancing purposes, which can easily be accomplished via a simple mechanism like DNS Round-Robin. Because all of the OpenSIPS servers will try to access the user's credit information, the problem occurs when you try to bill the calls done via those distributed OpenSIPS servers, but also when it comes to rejecting certain calls because the user does not have enough credit available. The solution would be to use a Redis DB clusters. The key will hold the actual username and the value will be the available credit. Because of the atomic nature of cache_add and cache_sub operations, all the OpenSIPS servers can reliably update the credit once the call ends. An OpenSIPS server can also consistently get the credit available for a certain user, in order to decide if it allows initial INVITEs to pass through or not. Even more, because of the persistency of Redis Server, all the credit information will not be lost in case of failures. PrerequisitesEach machine where OpenSIPS runs should have a Redis Server instance running. All the Redis Servers should be linked into a Redis cluster. A good tutorial on how create a simple Redis Cluster is located here. Script exampleRejecting calls due to low credit and updating credit cost after each call end can be done in the following way : modparam("cachedb_redis","cachedb_url","redis://192.168.2.1:6379") .... route { ... # sequential requests if (has_totag()) { if (is_method("BYE")) { # substract from credit call_duration*cost cents $avp(cost) = $DLG_lifetime * $(dlg_val(cost){s.int}); cache_sub("redis","credit_$fU",$avp(cost),0); } ... } else { # initial INVITE if (is_method("INVITE")) { # get user credit cache_fetch("redis","credit_$fU",$avp(user_credit)); if ($avp(user_credit) <= 1) { xlog("User $fU does not have enough credit\n"); sl_send_reply("403","Forbidden - Not enough credit"); exit; } create_dialog(); # get call cost $dlg_val(cost)= "3"; } ... } ... } |