Skip to content

Commit

Permalink
Merge pull request #25 from alexist/feat/24-INCR-with-initial-TTL
Browse files Browse the repository at this point in the history
feat(#24): INCR with initial expiration
  • Loading branch information
grrolland authored Nov 20, 2023
2 parents a63fd71 + 701dd8d commit 9ac632c
Show file tree
Hide file tree
Showing 8 changed files with 329 additions and 265 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
target/
.idea/
dependency-reduced-pom.xml
*.iml
83 changes: 52 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,22 @@
[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=io.github.grrolland%3Angx-distributed-shm&metric=coverage)](https://sonarcloud.io/dashboard/index/io.github.grrolland:ngx-distributed-shm)
[![Release](https://img.shields.io/github/release/grrolland/ngx-distributed-shm)](https://github.com/grrolland/ngx-distributed-shm/tags/)


# ngx-distributed-shm

This projet is memcached like server based on Hazelcast and Vertx. The goals of the project is to build an easy-to-use distributed memory storage with the nginx shared memory semantic for use with lua nginx plugin.
This projet is memcached like server based on Hazelcast and Vertx. The goals of the project is to build an easy-to-use
distributed memory storage with the nginx shared memory semantic for use with lua nginx plugin.

The semantic of the protocol is the same as the [lua.shared](https://github.com/openresty/lua-nginx-module#ngxshareddict) semantic.
The semantic of the protocol is the same as
the [lua.shared](https://github.com/openresty/lua-nginx-module#ngxshareddict) semantic.

## Status

Production Ready since 06/2018.

## Use cases

This project was successfully used to store rate limiting counter across a cluster of a Nginx based API Gateway in a French banking company.
This project was successfully used to store rate limiting counter across a cluster of a Nginx based API Gateway in a
French banking company.

This project was successfully used to distribute OpenID Connect Replying Party (based on zmartzone/lua-resty-openidc
) web session with the library bungle/lua-resty-session in a french banking company.
Expand Down Expand Up @@ -66,7 +68,8 @@ Simply get the distribution jar and copy it on your filesystem.

## How it works

When the storage startup, it creates an hazelcast instance and a vertx connector to communicate with it. When you start a second instance, it joins the first with the hazelcast protocol.
When the storage startup, it creates an hazelcast instance and a vertx connector to communicate with it. When you start
a second instance, it joins the first with the hazelcast protocol.

The protocol expose commands to interact with the distributed storage :

Expand All @@ -76,7 +79,8 @@ The protocol expose commands to interact with the distributed storage :
- DELETE : delete a key from the storage
- INCR : increment the value for a key

In a clustered deployment (2 or more instances), a client need to connect to only one instance to see all the storage. The goal is to provide a near storage associated with a nginx instance.
In a clustered deployment (2 or more instances), a client need to connect to only one instance to see all the storage.
The goal is to provide a near storage associated with a nginx instance.

## Startup

Expand Down Expand Up @@ -126,11 +130,13 @@ This command startup the storage on the 192.168.0.1 address :

### Configure the hazelcast IMDG map for replication

The hazelcast IMDG is configured with a configuration file which must be present in the classpath. The file must be named hazelcast.xml.
The hazelcast IMDG is configured with a configuration file which must be present in the classpath. The file must be
named hazelcast.xml.

This is an example of this file :

```xml

<hazelcast xsi:schemaLocation="http://www.hazelcast.com/schema/config hazelcast-config-3.9.xsd"
xmlns="http://www.hazelcast.com/schema/config"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
Expand All @@ -141,15 +147,15 @@ This is an example of this file :
<network>
<port auto-increment="false">5701</port>
<join>
<multicast enabled="false" />
<multicast enabled="false"/>
<tcp-ip enabled="true">
<interface>10.0.x.y</interface>
<member-list>
<member>10.0.x.y:5701</member>
<member>10.0.x.z:5701</member>
</member-list>
</tcp-ip>
<aws enabled="false" />
<aws enabled="false"/>
</join>
</network>
<map name="default">
Expand All @@ -168,7 +174,8 @@ This is an example of this file :
</hazelcast>
```

The reference documentation for this configuration is here : <https://docs.hazelcast.org/docs/3.12.1/manual/html-single/index.html#tcp-ip-element>
The reference documentation for this configuration is
here : <https://docs.hazelcast.org/docs/3.12.1/manual/html-single/index.html#tcp-ip-element>

This configuration works well for a two member cluster of the distributed shared memory.

Expand All @@ -183,7 +190,8 @@ The dist/conf directory contains an example logback.xml which control logging. T
<contextName>ngx-dshm</contextName>
<jmxConfigurator/>

<appender name="FILE-GLOBAL" class="ch.qos.logback.core.rolling.RollingFileAppender">
<appender name="FILE-GLOBAL"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${ngx-distributed-shm.log_dir}/ngx-dshm.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${ngx-distributed-shm.log_dir}/ngx-dshm-%d{yyyy-MM-dd}.log</fileNamePattern>
Expand All @@ -194,11 +202,11 @@ The dist/conf directory contains an example logback.xml which control logging. T
</encoder>
</appender>

<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="FILE-GLOBAL" />
<appender name="ASYNC"
class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="FILE-GLOBAL"/>
</appender>


<root level="info">
<appender-ref ref="ASYNC"/>
</root>
Expand Down Expand Up @@ -226,7 +234,8 @@ The **_ARG1_** is alway the **_key_**

### Anatomy of a key : region cache support

The distributed shared memory support region partitioning. The format of the key control the region where will be located the value.
The distributed shared memory support region partitioning. The format of the key control the region where will be
located the value.

This key **_key1_** will be located in the region **_region1_** :

Expand Down Expand Up @@ -299,7 +308,8 @@ GET key\r\n

**with data:** _yes_

Set a value of length \<length\> for the key \<key\> in the storage with the expiration time in second \<expiration\>. This operation is atomic.
Set a value of length \<length\> for the key \<key\> in the storage with the expiration time in second \<expiration\>.
This operation is atomic.

When \<expiration\> is 0, the key don't expire.

Expand Down Expand Up @@ -332,20 +342,25 @@ When the key does not exist the command does nothing.
DELETE key\r\n
```

**_INCR \<key\> \<value\> \<init\>_**
**_INCR \<key\> \<value\> \<init\> \<initial_expiration\>_**

**with data:** _no_

Increment the value of the key \<key\> with \<value\> if the key exists and represent an integer.

If the value is not an integer, this operation has no effect.

If the key don't exist or is expired, this operation create the key and init the value to \<value\>+\<init\>.
If the key exist, initial_expiration has no effect.

If the key don't exist or is expired, this operation create the key and init the value to \<value\>+\<init\>. In that
case, if initial_expiration is not provided or 0, the key will never expire

This operation is atomic.

```
INCR key -1 0\r\n
INCR key -1 0 60\r\n
```

**_FLUSHALL [region]_**
Expand Down Expand Up @@ -378,7 +393,8 @@ QUIT\r\n

## LUA libraries support

The lua libraries (lua/dshm.lua) is used to pilot the shared memory. The library should be installed in the resty directory of the openresty distribution.
The lua libraries (lua/dshm.lua) is used to pilot the shared memory. The library should be installed in the resty
directory of the openresty distribution.

This is an exemple to use it :

Expand All @@ -397,9 +413,11 @@ store:delete("key")

### Resty Session support

This module could be used to activate session replication with the excellent lua library Resty Session (<https://github.com/bungle/lua-resty-session>)
This module could be used to activate session replication with the excellent lua library Resty
Session (<https://github.com/bungle/lua-resty-session>)

To use it, copy the lua extension in your resty/session/storage directory and use this type of configuration in your nginx.conf :
To use it, copy the lua extension in your resty/session/storage directory and use this type of configuration in your
nginx.conf :

```nginx
set $session_storage dshm;
Expand Down Expand Up @@ -447,22 +465,25 @@ The session_storage parameter control the storage module to be used.
```shell
docker pull ghcr.io/grrolland/ngx-distributed-shm
```

## Kubernetes

1. See [kubernetes](./kubernetes) directory for sample artefacts.
2. Use [kustomize](https://github.com/kubernetes-sigs/kustomize) standalone or the one embedded in `kubectl` to generate kubernetes artefacts for a specific release:
2. Use [kustomize](https://github.com/kubernetes-sigs/kustomize) standalone or the one embedded in `kubectl` to generate
kubernetes artefacts for a specific release:

- Change files in `kubernetes/overlays/test/` according to your needs
- `configmap.yaml` contains a basic hazelcast configuration as yaml instead of xml for ease of reading
- Generate artefacts and inspect:
- Change files in `kubernetes/overlays/test/` according to your needs
- `configmap.yaml` contains a basic hazelcast configuration as yaml instead of xml for ease of reading
- Generate artefacts and inspect:

```bash
cd kubernetes
kubectl kustomize /overlays/test > kubernetes.yaml
```
```bash
cd kubernetes
kubectl kustomize /overlays/test > kubernetes.yaml
```

- Apply: `kubectl apply -f kubernetes.yaml`
- Apply: `kubectl apply -f kubernetes.yaml`

## Management Center

- Optionally, the Hazelcast cluster can be monitored via [Management Center](https://docs.hazelcast.org/docs/management-center/latest/manual/html/index.html)
- Optionally, the Hazelcast cluster can be monitored
via [Management Center](https://docs.hazelcast.org/docs/management-center/latest/manual/html/index.html)
22 changes: 11 additions & 11 deletions lua/dshm.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

local escape_uri = ngx.escape_uri
local unescape_uri = ngx.unescape_uri
local match = string.match
Expand Down Expand Up @@ -120,7 +119,7 @@ function _M.get(self, key)
return nil, "not initialized"
end

local bytes, err = sock:send("get " .. self.escape_key(key) .. "\r\n" )
local bytes, err = sock:send("get " .. self.escape_key(key) .. "\r\n")
if not bytes then
return nil, err
end
Expand Down Expand Up @@ -150,7 +149,6 @@ function _M.get(self, key)

end


function _M.delete(self, key)

ngx.log(ngx.DEBUG, "Delete : ", key)
Expand All @@ -160,7 +158,7 @@ function _M.delete(self, key)
return nil, "not initialized"
end

local bytes, err = sock:send("delete " .. self.escape_key(key) .. "\r\n" )
local bytes, err = sock:send("delete " .. self.escape_key(key) .. "\r\n")
if not bytes then
return nil, err
end
Expand All @@ -176,10 +174,9 @@ function _M.delete(self, key)

end

function _M.incr(self, key, value, init, init_ttl)

function _M.incr(self, key, value, init)

ngx.log(ngx.DEBUG, "Incr : ", key, ", Value : ", value, ", Init : ", init)
ngx.log(ngx.DEBUG, "Incr : ", key, ", Value : ", value, ", Init : ", init, ", Init_TTL", init_ttl)

local sock = self.sock
if not sock then
Expand All @@ -191,7 +188,13 @@ function _M.incr(self, key, value, init)
l_init = init
end

local bytes, err = sock:send("incr " .. self.escape_key(key) .. " " .. value .. " " .. l_init .. "\r\n")
local s_init_ttl = ""
if init_ttl then
s_init_ttl = " " .. init_ttl
end

local command = "incr " .. self.escape_key(key) .. " " .. value .. " " .. l_init .. s_init_ttl .. "\r\n"
local bytes, err = sock:send(command)
if not bytes then
return nil, err
end
Expand Down Expand Up @@ -261,7 +264,6 @@ function _M.set(self, key, value, exptime)

end


function _M.touch(self, key, exptime)

if not exptime or exptime == 0 then
Expand Down Expand Up @@ -295,7 +297,6 @@ function _M.touch(self, key, exptime)

end


function _M.quit(self)
local sock = self.sock
if not sock then
Expand Down Expand Up @@ -338,7 +339,6 @@ function _M.set_keepalive(self, ...)
return sock:setkeepalive(...)
end


function _M.get_reused_times(self)
local sock = self.sock
if not sock then
Expand Down
Loading

0 comments on commit 9ac632c

Please sign in to comment.