Skip to content

Issue #2079, Add cluster support for strict sessions and lazy write #2264

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions cluster.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,3 +193,12 @@ The save path for cluster based session storage takes the form of a PHP GET requ
* _distribute_: phpredis will randomly distribute session reads between masters and any attached slaves (load balancing).
* _auth (string, empty by default)_: The password used to authenticate with the server prior to sending commands.
* _stream (array)_: ssl/tls stream context options.

### redis.session.early_refresh
Under normal operation, the client will refresh the session's expiry ttl whenever the session is closed. However, we can save this additional round-trip by updating the ttl when the session is opened instead ( This means that sessions that have not been modified will not send further commands to the server ).

To enable, set the following INI variable:
```ini
redis.session.early_refresh = 1
```
Note: This is disabled by default since it may significantly reduce the session lifetime for long-running scripts. Redis server version 6.2+ required.
1 change: 1 addition & 0 deletions redis.c
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ PHP_INI_BEGIN()
PHP_INI_ENTRY("redis.session.lock_expire", "0", PHP_INI_ALL, NULL)
PHP_INI_ENTRY("redis.session.lock_retries", "100", PHP_INI_ALL, NULL)
PHP_INI_ENTRY("redis.session.lock_wait_time", "20000", PHP_INI_ALL, NULL)
PHP_INI_ENTRY("redis.session.early_refresh", "0", PHP_INI_ALL, NULL)
PHP_INI_END()

static const zend_module_dep redis_deps[] = {
Expand Down
176 changes: 172 additions & 4 deletions redis_session.c
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ ps_module ps_mod_redis = {
};

ps_module ps_mod_redis_cluster = {
PS_MOD(rediscluster)
PS_MOD_UPDATE_TIMESTAMP(rediscluster)
};

typedef struct {
Expand Down Expand Up @@ -982,6 +982,166 @@ PS_OPEN_FUNC(rediscluster) {
return FAILURE;
}

/* {{{ PS_CREATE_SID_FUNC
*/
PS_CREATE_SID_FUNC(rediscluster)
{
redisCluster *c = PS_GET_MOD_DATA();
clusterReply *reply;
char *cmd, *skey;
zend_string *sid;
int cmdlen, skeylen;
int retries = 3;
short slot;

if (!c) {
return php_session_create_id(NULL);
}

if (INI_INT("session.use_strict_mode") == 0) {
return php_session_create_id((void **) &c);
}

while (retries-- > 0) {
sid = php_session_create_id((void **) &c);

/* Create session key if it doesn't already exist */
skey = cluster_session_key(c, ZSTR_VAL(sid), ZSTR_LEN(sid), &skeylen, &slot);
cmdlen = redis_spprintf(NULL, NULL, &cmd, "SET", "ssssd", skey,
skeylen, "", 0, "NX", 2, "EX", 2, session_gc_maxlifetime());

efree(skey);

/* Attempt to kick off our command */
c->readonly = 0;
if (cluster_send_command(c,slot,cmd,cmdlen) < 0 || c->err) {
php_error_docref(NULL, E_NOTICE, "Redis connection not available");
efree(cmd);
zend_string_release(sid);
return php_session_create_id(NULL);;
}

efree(cmd);

/* Attempt to read reply */
reply = cluster_read_resp(c, 1);

if (!reply || c->err) {
php_error_docref(NULL, E_NOTICE, "Unable to read redis response");
} else if (reply->len > 0) {
cluster_free_reply(reply, 1);
break;
} else {
php_error_docref(NULL, E_NOTICE, "Redis sid collision on %s, retrying %d time(s)", sid->val, retries);
}

if (reply) {
cluster_free_reply(reply, 1);
}

zend_string_release(sid);
sid = NULL;
}

return sid;
}
/* }}} */

/* {{{ PS_VALIDATE_SID_FUNC
*/
PS_VALIDATE_SID_FUNC(rediscluster)
{
redisCluster *c = PS_GET_MOD_DATA();
clusterReply *reply;
char *cmd, *skey;
int cmdlen, skeylen;
int res = FAILURE;
short slot;

/* Check key is valid and whether it already exists */
if (php_session_valid_key(ZSTR_VAL(key)) == FAILURE) {
php_error_docref(NULL, E_NOTICE, "Invalid session key: %s", ZSTR_VAL(key));
return FAILURE;
}

skey = cluster_session_key(c, ZSTR_VAL(key), ZSTR_LEN(key), &skeylen, &slot);
cmdlen = redis_spprintf(NULL, NULL, &cmd, "EXISTS", "s", skey, skeylen);
efree(skey);

/* We send to master, to ensure consistency */
c->readonly = 0;
if (cluster_send_command(c,slot,cmd,cmdlen) < 0 || c->err) {
php_error_docref(NULL, E_NOTICE, "Redis connection not available");
efree(cmd);
return FAILURE;
}

efree(cmd);

/* Attempt to read reply */
reply = cluster_read_resp(c, 0);

if (!reply || c->err) {
php_error_docref(NULL, E_NOTICE, "Unable to read redis response");
res = FAILURE;
} else if (reply->integer == 1) {
res = SUCCESS;
}

/* Clean up */
if (reply) {
cluster_free_reply(reply, 1);
}

return res;
}
/* }}} */

/* {{{ PS_UPDATE_TIMESTAMP_FUNC
*/
PS_UPDATE_TIMESTAMP_FUNC(rediscluster) {
redisCluster *c = PS_GET_MOD_DATA();
clusterReply *reply;
char *cmd, *skey;
int cmdlen, skeylen;
short slot;

/* No need to update the session timestamp if we've already done so */
if (INI_INT("redis.session.early_refresh")) {
return SUCCESS;
}

/* Set up command and slot info */
skey = cluster_session_key(c, ZSTR_VAL(key), ZSTR_LEN(key), &skeylen, &slot);
cmdlen = redis_spprintf(NULL, NULL, &cmd, "EXPIRE", "sd", skey,
skeylen, session_gc_maxlifetime());
efree(skey);

/* Attempt to send EXPIRE command */
c->readonly = 0;
if (cluster_send_command(c,slot,cmd,cmdlen) < 0 || c->err) {
php_error_docref(NULL, E_NOTICE, "Redis unable to update session expiry");
efree(cmd);
return FAILURE;
}

/* Clean up our command */
efree(cmd);

/* Attempt to read reply */
reply = cluster_read_resp(c, 0);
if (!reply || c->err) {
if (reply) cluster_free_reply(reply, 1);
return FAILURE;
}

/* Clean up */
cluster_free_reply(reply, 1);

return SUCCESS;
}
/* }}} */

/* {{{ PS_READ_FUNC
*/
PS_READ_FUNC(rediscluster) {
Expand All @@ -994,11 +1154,19 @@ PS_READ_FUNC(rediscluster) {
/* Set up our command and slot information */
skey = cluster_session_key(c, ZSTR_VAL(key), ZSTR_LEN(key), &skeylen, &slot);

cmdlen = redis_spprintf(NULL, NULL, &cmd, "GET", "s", skey, skeylen);
/* Update the session ttl if early refresh is enabled */
if (INI_INT("redis.session.early_refresh")) {
cmdlen = redis_spprintf(NULL, NULL, &cmd, "GETEX", "ssd", skey,
skeylen, "EX", 2, session_gc_maxlifetime());
c->readonly = 0;
} else {
cmdlen = redis_spprintf(NULL, NULL, &cmd, "GET", "s", skey, skeylen);
c->readonly = 1;
}

efree(skey);

/* Attempt to kick off our command */
c->readonly = 1;
if (cluster_send_command(c,slot,cmd,cmdlen) < 0 || c->err) {
efree(cmd);
return FAILURE;
Expand Down Expand Up @@ -1126,4 +1294,4 @@ PS_GC_FUNC(rediscluster) {

#endif

/* vim: set tabstop=4 expandtab: */
/* vim: set tabstop=4 expandtab: */
3 changes: 3 additions & 0 deletions redis_session.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ PS_READ_FUNC(rediscluster);
PS_WRITE_FUNC(rediscluster);
PS_DESTROY_FUNC(rediscluster);
PS_GC_FUNC(rediscluster);
PS_CREATE_SID_FUNC(rediscluster);
PS_VALIDATE_SID_FUNC(rediscluster);
PS_UPDATE_TIMESTAMP_FUNC(rediscluster);

#endif
#endif