깃헙링크 : https://github.com/ehgks0000/redis-lock-example.git
redis cluster 모드로 3 마스터, 3 슬레이브
express 1
실행방법 : docker-compose up --build
import express, { NextFunction, Request, Response } from "express";
import Client from "ioredis";
import Redlock, { ResourceLockedError, ExecutionError } from "redlock";
const main = () => {
const app = express();
// Check this, https://stackoverflow.com/questions/64374113/replyerror-moved-error-after-connecting-to-redis-cluster-aws
const cluster = new Client.Cluster([
{ host: "redis-node1", port: 6300 },
{ host: "redis-node2", port: 6301 },
{ host: "redis-node3", port: 6302 },
{ host: "redis-node4", port: 6303 },
{ host: "redis-node5", port: 6304 },
{ host: "redis-node6", port: 6305 },
]);
cluster.once("connect", () => {
console.log("connected");
});
cluster.on("reconnecting", () => {
console.log("connected");
});
cluster.on("error", (e) => {
console.log("error: ", e);
});
const redlock = new Redlock([cluster], {
driftFactor: 0.01, // multiplied by lock ttl to determine drift time
retryCount: 10,
retryDelay: 2000, // time in ms
retryJitter: 200, // time in ms
automaticExtensionThreshold: 500, // time in ms
});
redlock.on("error", (e) => {
if (e instanceof ResourceLockedError) {
console.log("ResourceLockedError :", e);
return;
}
if (e instanceof ExecutionError) {
console.log("ExecutionError :", e);
return;
}
});
app.get("/ping", async (req, res) => {
console.log("pong");
res.json({ msg: "pong"});
});
app.get("/set", async (req, res) => {
try {
console.log("test set");
const a = await cluster.get("a");
console.log("a :", a);
if(!a){
await cluster.set("a", "testA");
}
const counter = await cluster.get("bbb");
if(!counter){
await cluster.set("bbb", 100);
}
res.json({ msg: "Test", a, counter });
} catch (error) {
res.json({ msg: "Test", error });
}
});
app.get("/count", async (req, res) => {
const counter = await cluster.get("bbb");
console.log("a :", counter);
if (Number(counter) <= 0) {
res.json({ msg: "0 됨." });
return;
}
const rv = await cluster.decrby("bbb", 1);
res.json({ msg: "Test", counter, rv });
});
app.get("/lock", async (req, res) => {
console.log("in lock");
try {
let lock = await redlock.acquire(["aASDF"], 5000);
try {
const counter = await cluster.get("bbb");
if (Number(counter) <= 0) {
console.log("재고소진.");
res.json({ msg: "0 됨." });
return;
}
// Something do to db
const rv = await cluster.decrby("bbb", 1);
console.log("rv :", rv);
res.json({ msg: "Test", counter, rv });
} catch (error) {
console.log("error :", error);
} finally {
await lock.release();
console.log("unlock");
}
} catch (error) {
res.json({ msg: " lock error", error });
}
});
app.use((err: unknown, req: Request, res: Response, next: NextFunction) => {
res.status(400).json({ msg: err });
});
const PORT = 3000;
const HOST = "0.0.0.0";
app.listen(PORT, HOST, () => {
console.log(`conccuruncy distributed redis example`);
});
};
main();
docker exec -it redis-node1 redis-cli -c -h 173.17.0.2 -p 6300 // 마스터 1
set a "testA"
docker exec -it redis-node4 redis-cli -c -h 173.17.0.5 -p 6303 // 슬레이브 1
get a
# 3 nGrinder
docker pull ngrinder/controller
docker run -d -v ~/ngrinder-controller:/opt/ngrinder-controller --name controller -p 80:80 -p 16001:16001 -p 12000-12009:12000-12009 ngrinder/controller
//
docker pull ngrinder/agent
docker run -d -v ~/ngrinder-agent:/opt/ngrinder-agent --name agent ngrinder/agent 192.168.0.37:80
// 기존 docker network에 연결 시켜주기
// 네트워크 확인, docker network ls
docker network connect redis-lock-example_redis_cluster controller
docker network connect redis-lock-example_redis_cluster agent
localhost:80 접속
user: admin
password: admin
import static net.grinder.script.Grinder.grinder
import static org.junit.Assert.*
import static org.hamcrest.Matchers.*
import net.grinder.script.GTest
import net.grinder.script.Grinder
import net.grinder.scriptengine.groovy.junit.GrinderRunner
import net.grinder.scriptengine.groovy.junit.annotation.BeforeProcess
import net.grinder.scriptengine.groovy.junit.annotation.BeforeThread
// import static net.grinder.util.GrinderUtils.* // You can use this if you're using nGrinder after 3.2.3
import org.junit.Before
import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith
import org.ngrinder.http.HTTPRequest
import org.ngrinder.http.HTTPRequestControl
import org.ngrinder.http.HTTPResponse
import org.ngrinder.http.cookie.Cookie
import org.ngrinder.http.cookie.CookieManager
/**
* A simple example using the HTTP plugin that shows the retrieval of a single page via HTTP.
*
* This script is automatically generated by ngrinder.
*
* @author admin
*/
@RunWith(GrinderRunner)
class TestRunner {
public static GTest test
public static HTTPRequest request
public static Map<String, String> headers = [:]
public static Map<String, Object> params = [:]
public static List<Cookie> cookies = []
@BeforeProcess
public static void beforeProcess() {
HTTPRequestControl.setConnectionTimeout(300000)
test = new GTest(1, "192.168.0.37")
request = new HTTPRequest()
grinder.logger.info("before process.")
}
@BeforeThread
public void beforeThread() {
test.record(this, "test")
grinder.statistics.delayReports = true
grinder.logger.info("before thread.")
}
@Before
public void before() {
request.setHeaders(headers)
CookieManager.addCookies(cookies)
grinder.logger.info("before. init headers and cookies")
}
@Test
public void test() {
HTTPResponse response = request.GET("http://192.168.0.37:3001/lock")
if (response.statusCode == 301 || response.statusCode == 302) {
grinder.logger.warn("Warning. The response may not be correct. The response code was {}.", response.statusCode)
} else {
assertThat(response.statusCode, is(200))
}
}
}
http://redisgate.kr/redis/command/sadd.php // sAdd
https://redis.io/commands/sadd/
http://redisgate.kr/redis/command/append.php // append
https://redis.io/commands/append/
https://www.npmjs.com/package/redlock
https://www.npmjs.com/package/redis-lock
https://github.com/errorception/redis-lock
https://blog.dennisokeeffe.com/blog/2021-10-04-locking-redis-transactions-in-nodejs