diff --git a/.babelrc b/.babelrc
new file mode 100644
index 00000000..55754d07
--- /dev/null
+++ b/.babelrc
@@ -0,0 +1,3 @@
+{
+ "compact": false
+}
diff --git a/.gitattributes b/.gitattributes
index 07962a1f..eaae227f 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -22,6 +22,7 @@
*.less text
*.sql text
*.properties text
+*.md text
# unix style
*.sh text eol=lf
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
new file mode 100644
index 00000000..04010943
--- /dev/null
+++ b/.github/workflows/deploy.yml
@@ -0,0 +1,36 @@
+name: CI
+
+# 在master分支发生push事件时触发。
+on:
+ push:
+ branches:
+ - master
+
+env: # 设置环境变量
+ TZ: Asia/Shanghai # 时区(设置时区可使页面中的`最近更新时间`使用时区时间)
+
+jobs:
+ build: # 自定义名称
+ runs-on: ubuntu-latest # 运行在虚拟机环境ubuntu-latest
+
+ strategy:
+ matrix:
+ node-version: [16.x]
+
+ steps:
+ # 使用的动作。格式:userName/repoName。作用:检出仓库,获取源码。 官方actions库:https://github.com/actions
+ - name: Checkout
+ uses: actions/checkout@master
+
+ # 指定 nodejs 版本
+ - name: Use Nodejs ${{ matrix.node-version }}
+ uses: actions/setup-node@v1
+ with:
+ node-version: ${{ matrix.node-version }}
+
+ # 部署
+ - name: Deploy
+ env: # 设置环境变量
+ GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }}
+ GITEE_TOKEN: ${{ secrets.GITEE_TOKEN }}
+ run: npm install && npm run deploy
diff --git a/.gitignore b/.gitignore
index 83948575..7d98dac9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,7 +29,6 @@ hs_err_pid*
# maven plugin temp files
.flattened-pom.xml
-package-lock.json
# ------------------------------- javascript -------------------------------
@@ -37,10 +36,12 @@ package-lock.json
node_modules
# temp folders
-.temp
+build
dist
_book
_jsdoc
+.temp
+.deploy*/
# temp files
*.log
@@ -48,7 +49,11 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*
bundle*.js
+.DS_Store
+Thumbs.db
+db.json
book.pdf
+package-lock.json
# ------------------------------- intellij -------------------------------
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 7f7498fb..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,24 +0,0 @@
-# 持续集成 CI
-# @see https://docs.travis-ci.com/user/tutorial/
-
-language: node_js
-
-sudo: required
-
-node_js: stable
-
-branches:
- only:
- - master
-
-before_install:
- - export TZ=Asia/Shanghai
-
-script: bash ./scripts/deploy.sh
-
-notifications:
- email:
- recipients:
- - forbreak@163.com
- on_success: change
- on_failure: always
diff --git a/README.md b/README.md
index 0841d736..7d3dbdd3 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,27 @@
children = client.getChildren().forPath("/zkid");
+ if (CollectionUtil.isNotEmpty(children)) {
+ for (String child : children) {
+ client.delete().forPath("/zkid/" + child);
+ }
+ }
+ client.delete().forPath("/zkid");
+
+ // 关闭客户端
+ client.close();
+ }
+
+}
diff --git a/codes/java-distributed/java-distributed-id/src/main/java/io/github/dunwu/distributed/id/ZookeeperDistributedId2.java b/codes/java-distributed/java-distributed-id/src/main/java/io/github/dunwu/distributed/id/ZookeeperDistributedId2.java
new file mode 100644
index 00000000..69d5d543
--- /dev/null
+++ b/codes/java-distributed/java-distributed-id/src/main/java/io/github/dunwu/distributed/id/ZookeeperDistributedId2.java
@@ -0,0 +1,46 @@
+package io.github.dunwu.distributed.id;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.curator.RetryPolicy;
+import org.apache.curator.framework.CuratorFramework;
+import org.apache.curator.framework.CuratorFrameworkFactory;
+import org.apache.curator.framework.recipes.atomic.AtomicValue;
+import org.apache.curator.framework.recipes.atomic.DistributedAtomicLong;
+import org.apache.curator.retry.ExponentialBackoffRetry;
+
+/**
+ * ZK 分布式 ID
+ *
+ * 基于原子计数器生成 ID
+ *
+ * @author Zhang Peng
+ * @date 2024-12-20
+ */
+@Slf4j
+public class ZookeeperDistributedId2 {
+
+ public static void main(String[] args) throws Exception {
+
+ // 获取客户端
+ RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
+ CuratorFramework client = CuratorFrameworkFactory.newClient("127.0.0.1:2181", retryPolicy);
+ DistributedAtomicLong atomicLong = new DistributedAtomicLong(client, "/zkid", retryPolicy);
+
+ // 开启会话
+ client.start();
+
+ // 基于原子计数器生成 ID
+ AtomicValue id1 = atomicLong.increment();
+ log.info("id: {}", id1.postValue());
+
+ AtomicValue id2 = atomicLong.increment();
+ log.info("id: {}", id2.postValue());
+
+ // 清理节点
+ client.delete().forPath("/zkid");
+
+ // 关闭客户端
+ client.close();
+ }
+
+}
diff --git a/codes/java-distributed/java-distributed-id/src/main/resources/scripts/fixed_window_rate_limit.lua b/codes/java-distributed/java-distributed-id/src/main/resources/scripts/fixed_window_rate_limit.lua
new file mode 100644
index 00000000..e0c9ad00
--- /dev/null
+++ b/codes/java-distributed/java-distributed-id/src/main/resources/scripts/fixed_window_rate_limit.lua
@@ -0,0 +1,21 @@
+-- 缓存 Key
+local key = KEYS[1]
+-- 访问请求数
+local permits = tonumber(ARGV[1])
+-- 过期时间
+local seconds = tonumber(ARGV[2])
+-- 限流阈值
+local limit = tonumber(ARGV[3])
+
+-- 获取统计值
+local count = tonumber(redis.call('GET', key) or "0")
+
+if count + permits > limit then
+ -- 请求拒绝
+ return -1
+else
+ -- 请求通过
+ redis.call('INCRBY', key, permits)
+ redis.call('EXPIRE', key, seconds)
+ return count + permits
+end
\ No newline at end of file
diff --git a/codes/java-distributed/java-distributed-id/src/main/resources/scripts/token_bucket_rate_limit.lua b/codes/java-distributed/java-distributed-id/src/main/resources/scripts/token_bucket_rate_limit.lua
new file mode 100644
index 00000000..541d70c9
--- /dev/null
+++ b/codes/java-distributed/java-distributed-id/src/main/resources/scripts/token_bucket_rate_limit.lua
@@ -0,0 +1,39 @@
+local tokenKey = KEYS[1]
+local timeKey = KEYS[2]
+
+-- 申请令牌数
+local permits = tonumber(ARGV[1])
+-- QPS
+local qps = tonumber(ARGV[2])
+-- 桶的容量
+local capacity = tonumber(ARGV[3])
+-- 当前时间(单位:毫秒)
+local nowMillis = tonumber(ARGV[4])
+-- 填满令牌桶所需要的时间
+local fillTime = capacity / qps
+local ttl = math.min(capacity, math.floor(fillTime * 2))
+
+local currentTokenNum = tonumber(redis.call("GET", tokenKey))
+if currentTokenNum == nil then
+ currentTokenNum = capacity
+end
+
+local endTimeMillis = tonumber(redis.call("GET", timeKey))
+if endTimeMillis == nil then
+ endTimeMillis = 0
+end
+
+local gap = nowMillis - endTimeMillis
+local newTokenNum = math.max(0, gap * qps / 1000)
+local currentTokenNum = math.min(capacity, currentTokenNum + newTokenNum)
+
+if currentTokenNum < permits then
+ -- 请求拒绝
+ return -1
+else
+ -- 请求通过
+ local finalTokenNum = currentTokenNum - permits
+ redis.call("SETEX", tokenKey, ttl, finalTokenNum)
+ redis.call("SETEX", timeKey, ttl, nowMillis)
+ return finalTokenNum
+end
diff --git a/codes/java-distributed/java-load-balance/pom.xml b/codes/java-distributed/java-load-balance/pom.xml
index 5f14ae1a..b4e73f0f 100644
--- a/codes/java-distributed/java-load-balance/pom.xml
+++ b/codes/java-distributed/java-load-balance/pom.xml
@@ -3,11 +3,10 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
- io.github.dunwu.javatech
+ io.github.dunwu.distributed
java-load-balance
1.0.0
jar
- ${project.artifactId}
UTF-8
diff --git a/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/BaseLoadBalance.java b/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/BaseLoadBalance.java
similarity index 96%
rename from codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/BaseLoadBalance.java
rename to codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/BaseLoadBalance.java
index e4f03ab2..ebb317a8 100644
--- a/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/BaseLoadBalance.java
+++ b/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/BaseLoadBalance.java
@@ -1,4 +1,4 @@
-package io.github.dunwu.javatech;
+package io.github.dunwu.distributed;
import cn.hutool.core.collection.CollectionUtil;
diff --git a/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/ConsistentHashLoadBalance.java b/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/ConsistentHashLoadBalance.java
similarity index 99%
rename from codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/ConsistentHashLoadBalance.java
rename to codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/ConsistentHashLoadBalance.java
index 42394a12..c67558a2 100644
--- a/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/ConsistentHashLoadBalance.java
+++ b/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/ConsistentHashLoadBalance.java
@@ -1,4 +1,4 @@
-package io.github.dunwu.javatech;
+package io.github.dunwu.distributed;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
diff --git a/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/IpHashLoadBalance.java b/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/IpHashLoadBalance.java
similarity index 94%
rename from codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/IpHashLoadBalance.java
rename to codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/IpHashLoadBalance.java
index 00be0c93..3d71cbb7 100644
--- a/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/IpHashLoadBalance.java
+++ b/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/IpHashLoadBalance.java
@@ -1,4 +1,4 @@
-package io.github.dunwu.javatech;
+package io.github.dunwu.distributed;
import cn.hutool.core.util.HashUtil;
import cn.hutool.core.util.StrUtil;
diff --git a/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/LeastActiveLoadBalance.java b/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/LeastActiveLoadBalance.java
similarity index 98%
rename from codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/LeastActiveLoadBalance.java
rename to codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/LeastActiveLoadBalance.java
index 25dc6f13..23a7f03c 100644
--- a/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/LeastActiveLoadBalance.java
+++ b/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/LeastActiveLoadBalance.java
@@ -1,4 +1,4 @@
-package io.github.dunwu.javatech;
+package io.github.dunwu.distributed;
import java.util.List;
import java.util.Random;
diff --git a/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/LoadBalance.java b/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/LoadBalance.java
similarity index 86%
rename from codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/LoadBalance.java
rename to codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/LoadBalance.java
index 046734af..f2d0c561 100644
--- a/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/LoadBalance.java
+++ b/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/LoadBalance.java
@@ -1,4 +1,4 @@
-package io.github.dunwu.javatech;
+package io.github.dunwu.distributed;
import java.util.List;
diff --git a/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/LoadBalanceDemo.java b/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/LoadBalanceDemo.java
similarity index 97%
rename from codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/LoadBalanceDemo.java
rename to codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/LoadBalanceDemo.java
index 1077f48e..57dc61f1 100644
--- a/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/LoadBalanceDemo.java
+++ b/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/LoadBalanceDemo.java
@@ -1,6 +1,10 @@
-package io.github.dunwu.javatech;
+package io.github.dunwu.distributed;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.TreeMap;
/**
* 负载均衡算法测试例
diff --git a/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/Node.java b/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/Node.java
similarity index 97%
rename from codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/Node.java
rename to codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/Node.java
index ad0c8ed4..2fc9c712 100644
--- a/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/Node.java
+++ b/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/Node.java
@@ -1,4 +1,4 @@
-package io.github.dunwu.javatech;
+package io.github.dunwu.distributed;
import java.util.Objects;
diff --git a/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/RandomLoadBalance.java b/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/RandomLoadBalance.java
similarity index 94%
rename from codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/RandomLoadBalance.java
rename to codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/RandomLoadBalance.java
index 8f4b7d8d..5b775dd2 100644
--- a/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/RandomLoadBalance.java
+++ b/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/RandomLoadBalance.java
@@ -1,4 +1,4 @@
-package io.github.dunwu.javatech;
+package io.github.dunwu.distributed;
import java.util.List;
import java.util.Random;
diff --git a/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/RoundRobinLoadBalance.java b/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/RoundRobinLoadBalance.java
similarity index 94%
rename from codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/RoundRobinLoadBalance.java
rename to codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/RoundRobinLoadBalance.java
index d14b251d..c0858152 100644
--- a/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/RoundRobinLoadBalance.java
+++ b/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/RoundRobinLoadBalance.java
@@ -1,4 +1,4 @@
-package io.github.dunwu.javatech;
+package io.github.dunwu.distributed;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
diff --git a/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/StatisticsUtil.java b/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/StatisticsUtil.java
similarity index 95%
rename from codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/StatisticsUtil.java
rename to codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/StatisticsUtil.java
index 50eaa59a..cbb66d13 100644
--- a/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/StatisticsUtil.java
+++ b/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/StatisticsUtil.java
@@ -1,4 +1,4 @@
-package io.github.dunwu.javatech;
+package io.github.dunwu.distributed;
public class StatisticsUtil {
diff --git a/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/WeightRandomLoadBalance.java b/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/WeightRandomLoadBalance.java
similarity index 96%
rename from codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/WeightRandomLoadBalance.java
rename to codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/WeightRandomLoadBalance.java
index a3f1c853..c7135667 100644
--- a/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/WeightRandomLoadBalance.java
+++ b/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/WeightRandomLoadBalance.java
@@ -1,4 +1,4 @@
-package io.github.dunwu.javatech;
+package io.github.dunwu.distributed;
import java.util.List;
import java.util.Random;
diff --git a/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/WeightRoundRobinLoadBalance.java b/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/WeightRoundRobinLoadBalance.java
similarity index 99%
rename from codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/WeightRoundRobinLoadBalance.java
rename to codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/WeightRoundRobinLoadBalance.java
index 3180958d..3f71a573 100644
--- a/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/WeightRoundRobinLoadBalance.java
+++ b/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/WeightRoundRobinLoadBalance.java
@@ -1,4 +1,4 @@
-package io.github.dunwu.javatech;
+package io.github.dunwu.distributed;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
diff --git a/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/package-info.java b/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/package-info.java
similarity index 76%
rename from codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/package-info.java
rename to codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/package-info.java
index 8633bb5a..4d8b7a26 100644
--- a/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/package-info.java
+++ b/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/package-info.java
@@ -4,4 +4,4 @@
* @author Zhang Peng
* @since 2020-01-22
*/
-package io.github.dunwu.javatech;
+package io.github.dunwu.distributed;
diff --git a/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/support/CRCHashStrategy.java b/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/support/CRCHashStrategy.java
similarity index 98%
rename from codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/support/CRCHashStrategy.java
rename to codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/support/CRCHashStrategy.java
index 8622bb51..5c732a5b 100644
--- a/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/support/CRCHashStrategy.java
+++ b/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/support/CRCHashStrategy.java
@@ -1,4 +1,4 @@
-package io.github.dunwu.javatech.support;
+package io.github.dunwu.distributed.support;
import java.nio.charset.StandardCharsets;
diff --git a/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/support/FnvHashStrategy.java b/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/support/FnvHashStrategy.java
similarity index 92%
rename from codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/support/FnvHashStrategy.java
rename to codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/support/FnvHashStrategy.java
index 807f3c33..fc7cea13 100644
--- a/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/support/FnvHashStrategy.java
+++ b/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/support/FnvHashStrategy.java
@@ -1,4 +1,4 @@
-package io.github.dunwu.javatech.support;
+package io.github.dunwu.distributed.support;
public class FnvHashStrategy implements HashStrategy {
diff --git a/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/support/HashStrategy.java b/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/support/HashStrategy.java
similarity index 59%
rename from codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/support/HashStrategy.java
rename to codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/support/HashStrategy.java
index 2ad6deef..f574c86e 100644
--- a/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/support/HashStrategy.java
+++ b/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/support/HashStrategy.java
@@ -1,4 +1,4 @@
-package io.github.dunwu.javatech.support;
+package io.github.dunwu.distributed.support;
public interface HashStrategy {
diff --git a/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/support/JdkHashCodeStrategy.java b/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/support/JdkHashCodeStrategy.java
similarity index 77%
rename from codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/support/JdkHashCodeStrategy.java
rename to codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/support/JdkHashCodeStrategy.java
index f4a8389c..6541e2a4 100644
--- a/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/support/JdkHashCodeStrategy.java
+++ b/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/support/JdkHashCodeStrategy.java
@@ -1,4 +1,4 @@
-package io.github.dunwu.javatech.support;
+package io.github.dunwu.distributed.support;
public class JdkHashCodeStrategy implements HashStrategy {
diff --git a/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/support/KetamaHashStrategy.java b/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/support/KetamaHashStrategy.java
similarity index 96%
rename from codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/support/KetamaHashStrategy.java
rename to codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/support/KetamaHashStrategy.java
index 8e98ef1f..ef479aaf 100644
--- a/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/support/KetamaHashStrategy.java
+++ b/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/support/KetamaHashStrategy.java
@@ -1,4 +1,4 @@
-package io.github.dunwu.javatech.support;
+package io.github.dunwu.distributed.support;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
diff --git a/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/support/MurmurHashStrategy.java b/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/support/MurmurHashStrategy.java
similarity index 96%
rename from codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/support/MurmurHashStrategy.java
rename to codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/support/MurmurHashStrategy.java
index a100f3d3..82ee1620 100644
--- a/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/javatech/support/MurmurHashStrategy.java
+++ b/codes/java-distributed/java-load-balance/src/main/java/io/github/dunwu/distributed/support/MurmurHashStrategy.java
@@ -1,4 +1,4 @@
-package io.github.dunwu.javatech.support;
+package io.github.dunwu.distributed.support;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
diff --git a/codes/java-distributed/java-rate-limit/pom.xml b/codes/java-distributed/java-rate-limit/pom.xml
new file mode 100644
index 00000000..312d7f21
--- /dev/null
+++ b/codes/java-distributed/java-rate-limit/pom.xml
@@ -0,0 +1,41 @@
+
+
+ 4.0.0
+
+ io.github.dunwu.distributed
+ java-rate-limit
+ 1.0.0
+ jar
+
+
+ UTF-8
+ 1.8
+ ${java.version}
+ ${java.version}
+
+
+
+
+ redis.clients
+ jedis
+ 5.1.0
+
+
+ cn.hutool
+ hutool-all
+ 5.8.25
+
+
+ org.projectlombok
+ lombok
+ 1.18.30
+
+
+ ch.qos.logback
+ logback-classic
+ 1.2.3
+ true
+
+
+
diff --git a/codes/java-distributed/java-rate-limit/src/main/java/io/github/dunwu/distributed/ratelimit/FixedWindowRateLimiter.java b/codes/java-distributed/java-rate-limit/src/main/java/io/github/dunwu/distributed/ratelimit/FixedWindowRateLimiter.java
new file mode 100644
index 00000000..0af8d142
--- /dev/null
+++ b/codes/java-distributed/java-rate-limit/src/main/java/io/github/dunwu/distributed/ratelimit/FixedWindowRateLimiter.java
@@ -0,0 +1,59 @@
+package io.github.dunwu.distributed.ratelimit;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * 固定时间窗口限流算法
+ *
+ * @author Zhang Peng
+ * @date 2024-01-18
+ */
+public class FixedWindowRateLimiter implements RateLimiter {
+
+ /**
+ * 允许的最大请求数
+ */
+ private final long maxPermits;
+
+ /**
+ * 窗口期时长
+ */
+ private final long periodMillis;
+
+ /**
+ * 窗口期截止时间
+ */
+ private long lastPeriodMillis;
+
+ /**
+ * 请求计数
+ */
+ private AtomicLong count = new AtomicLong(0);
+
+ public FixedWindowRateLimiter(long qps) {
+ this(qps, 1000, TimeUnit.MILLISECONDS);
+ }
+
+ public FixedWindowRateLimiter(long maxPermits, long period, TimeUnit timeUnit) {
+ this.maxPermits = maxPermits;
+ this.periodMillis = timeUnit.toMillis(period);
+ this.lastPeriodMillis = System.currentTimeMillis() + this.periodMillis;
+ }
+
+ @Override
+ public synchronized boolean tryAcquire(int permits) {
+ long now = System.currentTimeMillis();
+ if (lastPeriodMillis <= now) {
+ this.lastPeriodMillis = now + this.periodMillis;
+ count = new AtomicLong(0);
+ }
+ if (count.get() + permits <= maxPermits) {
+ count.addAndGet(permits);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/codes/java-distributed/java-rate-limit/src/main/java/io/github/dunwu/distributed/ratelimit/LeakyBucketRateLimiter.java b/codes/java-distributed/java-rate-limit/src/main/java/io/github/dunwu/distributed/ratelimit/LeakyBucketRateLimiter.java
new file mode 100644
index 00000000..0d99a227
--- /dev/null
+++ b/codes/java-distributed/java-rate-limit/src/main/java/io/github/dunwu/distributed/ratelimit/LeakyBucketRateLimiter.java
@@ -0,0 +1,64 @@
+package io.github.dunwu.distributed.ratelimit;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * 漏桶限流算法
+ *
+ * @author Zhang Peng
+ * @date 2024-01-18
+ */
+public class LeakyBucketRateLimiter implements RateLimiter {
+
+ /**
+ * QPS
+ */
+ private final int qps;
+
+ /**
+ * 桶的容量
+ */
+ private final long capacity;
+
+ /**
+ * 计算的起始时间
+ */
+ private long beginTimeMillis;
+
+ /**
+ * 桶中当前的水量
+ */
+ private final AtomicLong waterNum = new AtomicLong(0);
+
+ public LeakyBucketRateLimiter(int qps, int capacity) {
+ this.qps = qps;
+ this.capacity = capacity;
+ }
+
+ @Override
+ public synchronized boolean tryAcquire(int permits) {
+
+ // 如果桶中没有水,直接放行
+ if (waterNum.get() == 0) {
+ beginTimeMillis = System.currentTimeMillis();
+ waterNum.addAndGet(permits);
+ return true;
+ }
+
+ // 计算水量
+ long leakedWaterNum = ((System.currentTimeMillis() - beginTimeMillis) / 1000) * qps;
+ long currentWaterNum = waterNum.get() - leakedWaterNum;
+ waterNum.set(Math.max(0, currentWaterNum));
+
+ // 重置时间
+ beginTimeMillis = System.currentTimeMillis();
+
+ if (waterNum.get() + permits < capacity) {
+ waterNum.addAndGet(permits);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/codes/java-distributed/java-rate-limit/src/main/java/io/github/dunwu/distributed/ratelimit/RateLimiter.java b/codes/java-distributed/java-rate-limit/src/main/java/io/github/dunwu/distributed/ratelimit/RateLimiter.java
new file mode 100644
index 00000000..4fbc9646
--- /dev/null
+++ b/codes/java-distributed/java-rate-limit/src/main/java/io/github/dunwu/distributed/ratelimit/RateLimiter.java
@@ -0,0 +1,13 @@
+package io.github.dunwu.distributed.ratelimit;
+
+/**
+ * 限流器
+ *
+ * @author Zhang Peng
+ * @date 2024-01-18
+ */
+public interface RateLimiter {
+
+ boolean tryAcquire(int permits);
+
+}
diff --git a/codes/java-distributed/java-rate-limit/src/main/java/io/github/dunwu/distributed/ratelimit/RateLimiterDemo.java b/codes/java-distributed/java-rate-limit/src/main/java/io/github/dunwu/distributed/ratelimit/RateLimiterDemo.java
new file mode 100644
index 00000000..e4a50641
--- /dev/null
+++ b/codes/java-distributed/java-rate-limit/src/main/java/io/github/dunwu/distributed/ratelimit/RateLimiterDemo.java
@@ -0,0 +1,95 @@
+package io.github.dunwu.distributed.ratelimit;
+
+import cn.hutool.core.thread.ThreadUtil;
+import cn.hutool.core.util.RandomUtil;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * 限流器示例
+ *
+ * @author Zhang Peng
+ * @date 2024-01-18
+ */
+@Slf4j
+public class RateLimiterDemo {
+
+ public static void main(String[] args) {
+
+ // ============================================================================
+
+ int qps = 20;
+
+ System.out.println("======================= 固定时间窗口限流算法 =======================");
+ FixedWindowRateLimiter fixedWindowRateLimiter = new FixedWindowRateLimiter(qps);
+ testRateLimit(fixedWindowRateLimiter, qps);
+
+ System.out.println("======================= 滑动时间窗口限流算法 =======================");
+ SlidingWindowRateLimiter slidingWindowRateLimiter = new SlidingWindowRateLimiter(qps, 10);
+ testRateLimit(slidingWindowRateLimiter, qps);
+
+ System.out.println("======================= 漏桶限流算法 =======================");
+ LeakyBucketRateLimiter leakyBucketRateLimiter = new LeakyBucketRateLimiter(qps, 100);
+ testRateLimit(leakyBucketRateLimiter, qps);
+
+ System.out.println("======================= 令牌桶限流算法 =======================");
+ TokenBucketRateLimiter tokenBucketRateLimiter = new TokenBucketRateLimiter(qps, 100);
+ testRateLimit(tokenBucketRateLimiter, qps);
+ }
+
+ private static void testRateLimit(RateLimiter rateLimiter, int qps) {
+
+ AtomicInteger okNum = new AtomicInteger(0);
+ AtomicInteger limitNum = new AtomicInteger(0);
+ ExecutorService executorService = ThreadUtil.newFixedExecutor(10, "限流测试", true);
+ long beginTime = System.currentTimeMillis();
+
+ int threadNum = 4;
+ final CountDownLatch latch = new CountDownLatch(threadNum);
+ for (int i = 0; i < threadNum; i++) {
+ executorService.submit(() -> {
+ try {
+ batchRequest(rateLimiter, okNum, limitNum, 1000);
+ } catch (Exception e) {
+ log.error("发生异常!", e);
+ } finally {
+ latch.countDown();
+ }
+ });
+ }
+
+ try {
+ latch.await(10, TimeUnit.SECONDS);
+ long endTime = System.currentTimeMillis();
+ long gap = endTime - beginTime;
+ log.info("限流 QPS: {} -> 实际结果:耗时 {} ms,{} 次请求成功,{} 次请求被限流,实际 QPS: {}",
+ qps, gap, okNum.get(), limitNum.get(), okNum.get() * 1000 / gap);
+ if (okNum.get() == qps) {
+ log.info("限流符合预期");
+ }
+ } catch (Exception e) {
+ log.error("发生异常!", e);
+ } finally {
+ executorService.shutdown();
+ }
+ }
+
+ private static void batchRequest(RateLimiter rateLimiter, AtomicInteger okNum, AtomicInteger limitNum, int num)
+ throws InterruptedException {
+ for (int j = 0; j < num; j++) {
+ if (rateLimiter.tryAcquire(1)) {
+ log.info("请求成功");
+ okNum.getAndIncrement();
+ } else {
+ log.info("请求限流");
+ limitNum.getAndIncrement();
+ }
+ TimeUnit.MILLISECONDS.sleep(RandomUtil.randomInt(0, 10));
+ }
+ }
+
+}
diff --git a/codes/java-distributed/java-rate-limit/src/main/java/io/github/dunwu/distributed/ratelimit/RedisFixedWindowRateLimiter.java b/codes/java-distributed/java-rate-limit/src/main/java/io/github/dunwu/distributed/ratelimit/RedisFixedWindowRateLimiter.java
new file mode 100644
index 00000000..ec5d77d9
--- /dev/null
+++ b/codes/java-distributed/java-rate-limit/src/main/java/io/github/dunwu/distributed/ratelimit/RedisFixedWindowRateLimiter.java
@@ -0,0 +1,100 @@
+package io.github.dunwu.distributed.ratelimit;
+
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.resource.ResourceUtil;
+import cn.hutool.core.util.RandomUtil;
+import cn.hutool.core.util.StrUtil;
+import redis.clients.jedis.Jedis;
+import redis.clients.jedis.exceptions.JedisConnectionException;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 基于 Redis + Lua 实现的固定时间窗口限流算法
+ *
+ * @author Zhang Peng
+ * @date 2024-01-23
+ */
+public class RedisFixedWindowRateLimiter implements RateLimiter {
+
+ private static final String REDIS_HOST = "localhost";
+
+ private static final int REDIS_PORT = 6379;
+
+ private static final Jedis JEDIS;
+
+ public static final String SCRIPT;
+
+ static {
+ // Jedis 有多种构造方法,这里选用最简单的一种情况
+ JEDIS = new Jedis(REDIS_HOST, REDIS_PORT);
+
+ // 触发 ping 命令
+ try {
+ JEDIS.ping();
+ System.out.println("jedis 连接成功");
+ } catch (JedisConnectionException e) {
+ e.printStackTrace();
+ }
+
+ SCRIPT = FileUtil.readString(ResourceUtil.getResource("scripts/fixed_window_rate_limit.lua"),
+ StandardCharsets.UTF_8);
+ }
+
+ private final long maxPermits;
+ private final long periodSeconds;
+ private final String key;
+
+ public RedisFixedWindowRateLimiter(long qps, String key) {
+ this(qps * 60, 60, TimeUnit.SECONDS, key);
+ }
+
+ public RedisFixedWindowRateLimiter(long maxPermits, long period, TimeUnit timeUnit, String key) {
+ this.maxPermits = maxPermits;
+ this.periodSeconds = timeUnit.toSeconds(period);
+ this.key = key;
+ }
+
+ @Override
+ public boolean tryAcquire(int permits) {
+ List keys = Collections.singletonList(key);
+ List args = CollectionUtil.newLinkedList(String.valueOf(permits), String.valueOf(periodSeconds),
+ String.valueOf(maxPermits));
+ Object eval = JEDIS.eval(SCRIPT, keys, args);
+ long value = (long) eval;
+ return value != -1;
+ }
+
+ public static void main(String[] args) throws InterruptedException {
+
+ int qps = 20;
+ RateLimiter jedisFixedWindowRateLimiter = new RedisFixedWindowRateLimiter(qps, "rate:limit:20240122210000");
+
+ // 模拟在一分钟内,不断收到请求,限流是否有效
+ int seconds = 60;
+ long okNum = 0L;
+ long total = 0L;
+ long beginTime = System.currentTimeMillis();
+ int num = RandomUtil.randomInt(qps, 100);
+ for (int second = 0; second < seconds; second++) {
+ for (int i = 0; i < num; i++) {
+ total++;
+ if (jedisFixedWindowRateLimiter.tryAcquire(1)) {
+ okNum++;
+ System.out.println("请求成功");
+ } else {
+ System.out.println("请求限流");
+ }
+ }
+ TimeUnit.SECONDS.sleep(1);
+ }
+ long endTime = System.currentTimeMillis();
+ long time = (endTime - beginTime) / 1000;
+ System.out.println(StrUtil.format("请求通过数:{},总请求数:{},实际 QPS:{}", okNum, total, okNum / time));
+ }
+
+}
diff --git a/codes/java-distributed/java-rate-limit/src/main/java/io/github/dunwu/distributed/ratelimit/RedisTokenBucketRateLimiter.java b/codes/java-distributed/java-rate-limit/src/main/java/io/github/dunwu/distributed/ratelimit/RedisTokenBucketRateLimiter.java
new file mode 100644
index 00000000..9dd219df
--- /dev/null
+++ b/codes/java-distributed/java-rate-limit/src/main/java/io/github/dunwu/distributed/ratelimit/RedisTokenBucketRateLimiter.java
@@ -0,0 +1,104 @@
+package io.github.dunwu.distributed.ratelimit;
+
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.resource.ResourceUtil;
+import cn.hutool.core.util.RandomUtil;
+import cn.hutool.core.util.StrUtil;
+import redis.clients.jedis.Jedis;
+import redis.clients.jedis.exceptions.JedisConnectionException;
+
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 基于 Redis + Lua 实现的令牌桶限流算法
+ *
+ * @author Zhang Peng
+ * @date 2024-01-23
+ */
+public class RedisTokenBucketRateLimiter implements RateLimiter {
+
+ private static final String REDIS_HOST = "localhost";
+
+ private static final int REDIS_PORT = 6379;
+
+ private static final Jedis JEDIS;
+
+ public static final String SCRIPT;
+
+ static {
+ // Jedis 有多种构造方法,这里选用最简单的一种情况
+ JEDIS = new Jedis(REDIS_HOST, REDIS_PORT);
+
+ // 触发 ping 命令
+ try {
+ JEDIS.ping();
+ System.out.println("jedis 连接成功");
+ } catch (JedisConnectionException e) {
+ e.printStackTrace();
+ }
+
+ SCRIPT = FileUtil.readString(ResourceUtil.getResource("scripts/token_bucket_rate_limit.lua"),
+ StandardCharsets.UTF_8);
+ }
+
+ private final long qps;
+ private final long capacity;
+ private final String tokenKey;
+ private final String timeKey;
+
+ public RedisTokenBucketRateLimiter(long qps, long capacity, String tokenKey, String timeKey) {
+ this.qps = qps;
+ this.capacity = capacity;
+ this.tokenKey = tokenKey;
+ this.timeKey = timeKey;
+ }
+
+ @Override
+ public boolean tryAcquire(int permits) {
+ long now = System.currentTimeMillis();
+ List keys = CollectionUtil.newLinkedList(tokenKey, timeKey);
+ List args = CollectionUtil.newLinkedList(String.valueOf(permits), String.valueOf(qps),
+ String.valueOf(capacity), String.valueOf(now));
+ Object eval = JEDIS.eval(SCRIPT, keys, args);
+ long value = (long) eval;
+ return value != -1;
+ }
+
+ public static void main(String[] args) throws InterruptedException {
+
+ int qps = 20;
+ int bucket = 100;
+ RedisTokenBucketRateLimiter redisTokenBucketRateLimiter =
+ new RedisTokenBucketRateLimiter(qps, bucket, "token:rate:limit", "token:rate:limit:time");
+
+ // 先将令牌桶预热令牌申请完,后续才能真实反映限流 QPS
+ redisTokenBucketRateLimiter.tryAcquire(bucket);
+ TimeUnit.SECONDS.sleep(1);
+
+ // 模拟在一分钟内,不断收到请求,限流是否有效
+ int seconds = 60;
+ long okNum = 0L;
+ long total = 0L;
+ long beginTime = System.currentTimeMillis();
+ for (int second = 0; second < seconds; second++) {
+ int num = RandomUtil.randomInt(qps, 100);
+ for (int i = 0; i < num; i++) {
+ total++;
+ if (redisTokenBucketRateLimiter.tryAcquire(1)) {
+ okNum++;
+ System.out.println("请求成功");
+ } else {
+ System.out.println("请求限流");
+ }
+ }
+ TimeUnit.SECONDS.sleep(1);
+ }
+ long endTime = System.currentTimeMillis();
+ long time = (endTime - beginTime) / 1000;
+ System.out.println(StrUtil.format("请求通过数:{},总请求数:{},实际 QPS:{}", okNum, total, okNum / time));
+ }
+
+}
diff --git a/codes/java-distributed/java-rate-limit/src/main/java/io/github/dunwu/distributed/ratelimit/SlidingWindowRateLimiter.java b/codes/java-distributed/java-rate-limit/src/main/java/io/github/dunwu/distributed/ratelimit/SlidingWindowRateLimiter.java
new file mode 100644
index 00000000..a93613a2
--- /dev/null
+++ b/codes/java-distributed/java-rate-limit/src/main/java/io/github/dunwu/distributed/ratelimit/SlidingWindowRateLimiter.java
@@ -0,0 +1,87 @@
+package io.github.dunwu.distributed.ratelimit;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * 滑动时间窗口限流算法
+ *
+ * @author Zhang Peng
+ * @date 2024-01-18
+ */
+public class SlidingWindowRateLimiter implements RateLimiter {
+
+ /**
+ * 允许的最大请求数
+ */
+ private final long maxPermits;
+
+ /**
+ * 窗口期时长
+ */
+ private final long periodMillis;
+
+ /**
+ * 分片窗口期时长
+ */
+ private final long shardPeriodMillis;
+
+ /**
+ * 窗口期截止时间
+ */
+ private long lastPeriodMillis;
+
+ /**
+ * 分片窗口数
+ */
+ private final int shardNum;
+
+ /**
+ * 请求总计数
+ */
+ private final AtomicLong totalCount = new AtomicLong(0);
+
+ /**
+ * 分片窗口计数列表
+ */
+ private final List countList = new LinkedList<>();
+
+ public SlidingWindowRateLimiter(long qps, int shardNum) {
+ this(qps, 1000, TimeUnit.MILLISECONDS, shardNum);
+ }
+
+ public SlidingWindowRateLimiter(long maxPermits, long period, TimeUnit timeUnit, int shardNum) {
+ this.maxPermits = maxPermits;
+ this.periodMillis = timeUnit.toMillis(period);
+ this.lastPeriodMillis = System.currentTimeMillis();
+ this.shardPeriodMillis = timeUnit.toMillis(period) / shardNum;
+ this.shardNum = shardNum;
+ for (int i = 0; i < shardNum; i++) {
+ countList.add(new AtomicLong(0));
+ }
+ }
+
+ @Override
+ public synchronized boolean tryAcquire(int permits) {
+ long now = System.currentTimeMillis();
+ if (now > lastPeriodMillis) {
+ for (int shardId = 0; shardId < shardNum; shardId++) {
+ long shardCount = countList.get(shardId).get();
+ totalCount.addAndGet(-shardCount);
+ countList.set(shardId, new AtomicLong(0));
+ lastPeriodMillis += shardPeriodMillis;
+ }
+ }
+ int shardId = (int) (now % periodMillis / shardPeriodMillis);
+ if (totalCount.get() + permits <= maxPermits) {
+ countList.get(shardId).addAndGet(permits);
+ totalCount.addAndGet(permits);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/codes/java-distributed/java-rate-limit/src/main/java/io/github/dunwu/distributed/ratelimit/TokenBucketRateLimiter.java b/codes/java-distributed/java-rate-limit/src/main/java/io/github/dunwu/distributed/ratelimit/TokenBucketRateLimiter.java
new file mode 100644
index 00000000..e03e4c7d
--- /dev/null
+++ b/codes/java-distributed/java-rate-limit/src/main/java/io/github/dunwu/distributed/ratelimit/TokenBucketRateLimiter.java
@@ -0,0 +1,59 @@
+package io.github.dunwu.distributed.ratelimit;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * 令牌桶限流算法
+ *
+ * @author Zhang Peng
+ * @date 2024-01-18
+ */
+public class TokenBucketRateLimiter implements RateLimiter {
+
+ /**
+ * QPS
+ */
+ private final long qps;
+
+ /**
+ * 桶的容量
+ */
+ private final long capacity;
+
+ /**
+ * 上一次令牌发放时间
+ */
+ private long endTimeMillis;
+
+ /**
+ * 桶中当前的令牌数量
+ */
+ private final AtomicLong tokenNum = new AtomicLong(0);
+
+ public TokenBucketRateLimiter(long qps, long capacity) {
+ this.qps = qps;
+ this.capacity = capacity;
+ this.endTimeMillis = System.currentTimeMillis();
+ }
+
+ @Override
+ public synchronized boolean tryAcquire(int permits) {
+
+ long now = System.currentTimeMillis();
+ long gap = now - endTimeMillis;
+
+ // 计算令牌数
+ long newTokenNum = (gap * qps / 1000);
+ long currentTokenNum = tokenNum.get() + newTokenNum;
+ tokenNum.set(Math.min(capacity, currentTokenNum));
+
+ if (tokenNum.get() < permits) {
+ return false;
+ } else {
+ tokenNum.addAndGet(-permits);
+ endTimeMillis = now;
+ return true;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/codes/java-distributed/java-rate-limit/src/main/resources/scripts/fixed_window_rate_limit.lua b/codes/java-distributed/java-rate-limit/src/main/resources/scripts/fixed_window_rate_limit.lua
new file mode 100644
index 00000000..e0c9ad00
--- /dev/null
+++ b/codes/java-distributed/java-rate-limit/src/main/resources/scripts/fixed_window_rate_limit.lua
@@ -0,0 +1,21 @@
+-- 缓存 Key
+local key = KEYS[1]
+-- 访问请求数
+local permits = tonumber(ARGV[1])
+-- 过期时间
+local seconds = tonumber(ARGV[2])
+-- 限流阈值
+local limit = tonumber(ARGV[3])
+
+-- 获取统计值
+local count = tonumber(redis.call('GET', key) or "0")
+
+if count + permits > limit then
+ -- 请求拒绝
+ return -1
+else
+ -- 请求通过
+ redis.call('INCRBY', key, permits)
+ redis.call('EXPIRE', key, seconds)
+ return count + permits
+end
\ No newline at end of file
diff --git a/codes/java-distributed/java-rate-limit/src/main/resources/scripts/token_bucket_rate_limit.lua b/codes/java-distributed/java-rate-limit/src/main/resources/scripts/token_bucket_rate_limit.lua
new file mode 100644
index 00000000..541d70c9
--- /dev/null
+++ b/codes/java-distributed/java-rate-limit/src/main/resources/scripts/token_bucket_rate_limit.lua
@@ -0,0 +1,39 @@
+local tokenKey = KEYS[1]
+local timeKey = KEYS[2]
+
+-- 申请令牌数
+local permits = tonumber(ARGV[1])
+-- QPS
+local qps = tonumber(ARGV[2])
+-- 桶的容量
+local capacity = tonumber(ARGV[3])
+-- 当前时间(单位:毫秒)
+local nowMillis = tonumber(ARGV[4])
+-- 填满令牌桶所需要的时间
+local fillTime = capacity / qps
+local ttl = math.min(capacity, math.floor(fillTime * 2))
+
+local currentTokenNum = tonumber(redis.call("GET", tokenKey))
+if currentTokenNum == nil then
+ currentTokenNum = capacity
+end
+
+local endTimeMillis = tonumber(redis.call("GET", timeKey))
+if endTimeMillis == nil then
+ endTimeMillis = 0
+end
+
+local gap = nowMillis - endTimeMillis
+local newTokenNum = math.max(0, gap * qps / 1000)
+local currentTokenNum = math.min(capacity, currentTokenNum + newTokenNum)
+
+if currentTokenNum < permits then
+ -- 请求拒绝
+ return -1
+else
+ -- 请求通过
+ local finalTokenNum = currentTokenNum - permits
+ redis.call("SETEX", tokenKey, ttl, finalTokenNum)
+ redis.call("SETEX", timeKey, ttl, nowMillis)
+ return finalTokenNum
+end
diff --git a/codes/java-distributed/java-task/pom.xml b/codes/java-distributed/java-task/pom.xml
new file mode 100644
index 00000000..8d7cc462
--- /dev/null
+++ b/codes/java-distributed/java-task/pom.xml
@@ -0,0 +1,40 @@
+
+
+ 4.0.0
+
+
+ io.github.dunwu.distributed
+ java-distributed
+ 1.0.0
+
+
+ io.github.dunwu.distributed
+ java-task
+ 1.0.0
+ jar
+
+
+ UTF-8
+ 1.8
+ ${java.version}
+ ${java.version}
+
+
+
+
+ cn.hutool
+ hutool-all
+
+
+ org.projectlombok
+ lombok
+
+
+ ch.qos.logback
+ logback-classic
+ 1.2.3
+ true
+
+
+
diff --git a/codes/java-distributed/java-task/src/main/java/io/github/dunwu/local/task/DelayQueueExample.java b/codes/java-distributed/java-task/src/main/java/io/github/dunwu/local/task/DelayQueueExample.java
new file mode 100644
index 00000000..d510eed1
--- /dev/null
+++ b/codes/java-distributed/java-task/src/main/java/io/github/dunwu/local/task/DelayQueueExample.java
@@ -0,0 +1,52 @@
+package io.github.dunwu.local.task;
+
+import cn.hutool.core.date.DateUtil;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Date;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.DelayQueue;
+import java.util.concurrent.Delayed;
+import java.util.concurrent.TimeUnit;
+
+@Slf4j
+public class DelayQueueExample {
+
+ public static void main(String[] args) throws InterruptedException {
+ BlockingQueue delayQueue = new DelayQueue<>();
+ long now = System.currentTimeMillis();
+ delayQueue.put(new SampleTask(now + 1000));
+ delayQueue.put(new SampleTask(now + 2000));
+ delayQueue.put(new SampleTask(now + 3000));
+ for (int i = 0; i < 3; i++) {
+ log.info("task 执行时间:{}", DateUtil.format(new Date(delayQueue.take().getTime()), "yyyy-MM-dd HH:mm:ss"));
+ }
+ }
+
+ static class SampleTask implements Delayed {
+
+ long time;
+
+ public SampleTask(long time) {
+ this.time = time;
+ }
+
+ public long getTime() {
+ return time;
+ }
+
+ @Override
+ public int compareTo(Delayed o) {
+ return Long.compare(this.getDelay(TimeUnit.MILLISECONDS), o.getDelay(TimeUnit.MILLISECONDS));
+ }
+
+ @Override
+ public long getDelay(TimeUnit unit) {
+ return unit.convert(time - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
+ }
+
+ }
+
+}
+
+
diff --git a/codes/java-distributed/java-task/src/main/java/io/github/dunwu/local/task/ScheduledExecutorServiceExample.java b/codes/java-distributed/java-task/src/main/java/io/github/dunwu/local/task/ScheduledExecutorServiceExample.java
new file mode 100644
index 00000000..78e8f5bd
--- /dev/null
+++ b/codes/java-distributed/java-task/src/main/java/io/github/dunwu/local/task/ScheduledExecutorServiceExample.java
@@ -0,0 +1,37 @@
+package io.github.dunwu.local.task;
+
+import cn.hutool.core.date.DateUtil;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Date;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+@Slf4j
+public class ScheduledExecutorServiceExample {
+
+ public static void main(String[] args) {
+ // 创建一个 ScheduledExecutorService 对象,它将使用一个线程池来执行任务
+ ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
+
+ // 创建一个 Runnable 对象,这个任务将在 2 秒后执行,并且每 1 秒重复执行一次
+ Runnable task = () -> {
+ log.info("task 执行时间:{}", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
+ };
+
+ // 安排任务在 2 秒后执行,并且每 1 秒重复执行一次
+ executor.scheduleAtFixedRate(task, 2, 1, TimeUnit.SECONDS);
+
+ // 主线程等待 10 秒后结束
+ try {
+ Thread.sleep(10000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ // 关闭 executor,这将停止所有正在执行的任务,并拒绝新任务的提交
+ executor.shutdown();
+ }
+
+}
diff --git a/codes/java-distributed/java-task/src/main/java/io/github/dunwu/local/task/TimerExample.java b/codes/java-distributed/java-task/src/main/java/io/github/dunwu/local/task/TimerExample.java
new file mode 100644
index 00000000..ce1d7756
--- /dev/null
+++ b/codes/java-distributed/java-task/src/main/java/io/github/dunwu/local/task/TimerExample.java
@@ -0,0 +1,39 @@
+package io.github.dunwu.local.task;
+
+import cn.hutool.core.date.DateUtil;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Date;
+import java.util.Timer;
+import java.util.TimerTask;
+
+@Slf4j
+public class TimerExample {
+
+ public static void main(String[] args) {
+ // 创建一个 Timer 对象
+ Timer timer = new Timer();
+
+ // 创建一个 TimerTask 对象,这个任务将在 2 秒后执行,并且每 1 秒重复执行一次
+ TimerTask task = new TimerTask() {
+ @Override
+ public void run() {
+ log.info("task 执行时间:{}", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
+ }
+ };
+
+ // 安排任务在 2 秒后执行,并且每 1 秒重复执行一次
+ timer.schedule(task, 2000, 1000);
+
+ // 主线程等待 10 秒后结束
+ try {
+ Thread.sleep(10000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ // 取消定时器和所有已安排的任务
+ timer.cancel();
+ }
+
+}
diff --git a/codes/java-distributed/pom.xml b/codes/java-distributed/pom.xml
index 35fd5860..aa88d15d 100644
--- a/codes/java-distributed/pom.xml
+++ b/codes/java-distributed/pom.xml
@@ -3,15 +3,52 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
- io.github.dunwu.javatech
+ io.github.dunwu.distributed
java-distributed
1.0.0
pom
- JAVA-DISTRIBUTED
- JAVA-DISTRIBUTED 示例源码
java-load-balance
+ java-rate-limit
+ java-distributed-id
+ java-task
+
+
+
+ org.apache.zookeeper
+ zookeeper
+ 3.9.2
+
+
+ org.apache.curator
+ curator-recipes
+ 4.3.0
+
+
+ redis.clients
+ jedis
+ 5.1.0
+
+
+ cn.hutool
+ hutool-all
+ 5.8.34
+
+
+ org.projectlombok
+ lombok
+ 1.18.30
+
+
+ ch.qos.logback
+ logback-classic
+ 1.4.12
+ true
+
+
+
+
diff --git a/codes/javatech/javatech-cache/pom.xml b/codes/javatech/javatech-cache/pom.xml
new file mode 100644
index 00000000..4503c60c
--- /dev/null
+++ b/codes/javatech/javatech-cache/pom.xml
@@ -0,0 +1,76 @@
+
+
+ 4.0.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.2.1.RELEASE
+
+
+ io.github.dunwu.javatech
+ javatech-cache
+ 1.0.0
+ jar
+ JAVATECH-缓存示例
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.springframework.boot
+ spring-boot-starter-data-redis
+
+
+ org.springframework.boot
+ spring-boot-starter-cache
+
+
+ net.sf.ehcache
+ ehcache
+
+
+ com.github.ben-manes.caffeine
+ caffeine
+
+
+ net.spy
+ spymemcached
+ 2.12.2
+
+
+ com.google.guava
+ guava
+ 29.0-jre
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+
+
+ org.projectlombok
+ lombok
+
+
+ mysql
+ mysql-connector-java
+
+
+ com.h2database
+ h2
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/codes/javatech/javatech-cache/src/main/java/io/github/dunwu/javatech/SpringBootDataCacheApplication.java b/codes/javatech/javatech-cache/src/main/java/io/github/dunwu/javatech/SpringBootDataCacheApplication.java
new file mode 100644
index 00000000..4a5220d3
--- /dev/null
+++ b/codes/javatech/javatech-cache/src/main/java/io/github/dunwu/javatech/SpringBootDataCacheApplication.java
@@ -0,0 +1,87 @@
+package io.github.dunwu.javatech;
+
+import io.github.dunwu.javatech.data.User;
+import io.github.dunwu.javatech.data.UserDao;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.jdbc.core.JdbcTemplate;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import javax.sql.DataSource;
+
+/**
+ * @author Zhang Peng
+ * @since 2019-10-14
+ */
+@EnableCaching
+@SpringBootApplication
+public class SpringBootDataCacheApplication implements CommandLineRunner {
+
+ private final Logger log = LoggerFactory.getLogger(this.getClass());
+
+ private final UserDao userDao;
+
+ public SpringBootDataCacheApplication(UserDao userDao) {
+ this.userDao = userDao;
+ }
+
+ public static void main(String[] args) {
+ SpringApplication.run(SpringBootDataCacheApplication.class, args);
+ }
+
+ @Override
+ public void run(String... args) throws Exception {
+
+ if (userDao != null) {
+ printDataSourceInfo(userDao.getJdbcTemplate());
+ log.info("连接数据源成功!");
+ } else {
+ log.error("连接数据源失败!");
+ return;
+ }
+
+ for (int i = 1; i <= 3; i++) {
+ User user = userDao.queryByName("张三");
+ log.info("第 {} 次查询 name = {}", i, user.toString());
+ }
+
+ for (int i = 1; i <= 3; i++) {
+ User user = userDao.queryByName("李四");
+ log.info("第 {} 次查询 name = {}", i, user.toString());
+ }
+
+ User result = userDao.queryByName("张三");
+ result.setAddress("深圳");
+ userDao.update(result);
+
+ for (int i = 1; i <= 3; i++) {
+ User user = userDao.queryByName("张三");
+ log.info("第 {} 次查询 name = {}", i, user.toString());
+ }
+ }
+
+ public void printDataSourceInfo(JdbcTemplate jdbcTemplate) throws SQLException {
+
+ DataSource dataSource = jdbcTemplate.getDataSource();
+
+ Connection connection;
+ if (dataSource != null) {
+ connection = dataSource.getConnection();
+ } else {
+ log.error("获取 DataSource 失败");
+ return;
+ }
+
+ if (connection != null) {
+ log.info("DB URL: {}", connection.getMetaData().getURL());
+ } else {
+ log.error("获取 Connection 失败");
+ }
+ }
+
+}
diff --git a/codes/javatech/javatech-cache/src/main/java/io/github/dunwu/javatech/cache/CaffeineDemo.java b/codes/javatech/javatech-cache/src/main/java/io/github/dunwu/javatech/cache/CaffeineDemo.java
new file mode 100644
index 00000000..484259a6
--- /dev/null
+++ b/codes/javatech/javatech-cache/src/main/java/io/github/dunwu/javatech/cache/CaffeineDemo.java
@@ -0,0 +1,23 @@
+package io.github.dunwu.javatech.cache;
+
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.Caffeine;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author Zhang Peng
+ * @since 2020-07-09
+ */
+public class CaffeineDemo {
+
+ public static void main(String[] args) {
+ Cache cache = Caffeine.newBuilder()
+ .expireAfterWrite(1, TimeUnit.SECONDS)
+ .expireAfterAccess(1, TimeUnit.SECONDS)
+ .maximumSize(10)
+ .build();
+ cache.put("hello", "hello");
+ }
+
+}
diff --git a/codes/javatech/javatech-cache/src/main/java/io/github/dunwu/javatech/cache/GuavaCacheDemo.java b/codes/javatech/javatech-cache/src/main/java/io/github/dunwu/javatech/cache/GuavaCacheDemo.java
new file mode 100644
index 00000000..1cdb585e
--- /dev/null
+++ b/codes/javatech/javatech-cache/src/main/java/io/github/dunwu/javatech/cache/GuavaCacheDemo.java
@@ -0,0 +1,55 @@
+package io.github.dunwu.javatech.cache;
+
+import com.google.common.cache.*;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author Zhang Peng
+ * @since 2020-07-09
+ */
+public class GuavaCacheDemo {
+
+ public static void main(String[] args) {
+ CacheLoader loader = new CacheLoader() {
+ @Override
+ public String load(String key) throws Exception {
+ Thread.sleep(1000);
+ if ("key".equals(key)) {
+ return null;
+ }
+ System.out.println(key + " is loaded from a cacheLoader!");
+ return key + "'s value";
+ }
+ };
+
+ RemovalListener removalListener = new RemovalListener() {
+ @Override
+ public void onRemoval(RemovalNotification removal) {
+ System.out.println("[" + removal.getKey() + ":" + removal.getValue() + "] is evicted!");
+ }
+ };
+
+ LoadingCache testCache = CacheBuilder.newBuilder()
+ .maximumSize(7)
+ .expireAfterWrite(10, TimeUnit.MINUTES)
+ .removalListener(removalListener)
+ .build(loader);
+
+ for (int i = 0; i < 10; i++) {
+ String key = "key" + i;
+ String value = "value" + i;
+ testCache.put(key, value);
+ System.out.println("[" + key + ":" + value + "] is put into cache!");
+ }
+
+ System.out.println(testCache.getIfPresent("key6"));
+
+ try {
+ System.out.println(testCache.get("key"));
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+}
diff --git a/codes/javatech/javatech-cache/src/main/java/io/github/dunwu/javatech/cache/LRUCache.java b/codes/javatech/javatech-cache/src/main/java/io/github/dunwu/javatech/cache/LRUCache.java
new file mode 100644
index 00000000..3d38eae3
--- /dev/null
+++ b/codes/javatech/javatech-cache/src/main/java/io/github/dunwu/javatech/cache/LRUCache.java
@@ -0,0 +1,60 @@
+package io.github.dunwu.javatech.cache;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * 通过继承 LinkedHashMap 来实现一个简单的 LRUHashMap
+ *
+ * 核心思想就是:LRU (最近最少使用)算法
+ *
+ * @author Zhang Peng
+ * @since 2020-01-18
+ */
+class LRUCache extends LinkedHashMap {
+
+ private final int max;
+ private Object lock;
+
+ public LRUCache(int max) {
+ //无需扩容
+ super((int) (max * 1.4f), 0.75f, true);
+ this.max = max;
+ this.lock = new Object();
+ }
+
+ /**
+ * 重写LinkedHashMap的removeEldestEntry方法即可 在Put的时候判断,如果为true,就会删除最老的
+ *
+ * @param eldest
+ * @return
+ */
+ @Override
+ protected boolean removeEldestEntry(Map.Entry eldest) {
+ return size() > max;
+ }
+
+ public Object getValue(Object key) {
+ synchronized (lock) {
+ return get(key);
+ }
+ }
+
+ public void putValue(Object key, Object value) {
+ synchronized (lock) {
+ put(key, value);
+ }
+ }
+
+ public boolean removeValue(Object key) {
+ synchronized (lock) {
+ return remove(key) != null;
+ }
+ }
+
+ public boolean removeAll() {
+ clear();
+ return true;
+ }
+
+}
diff --git a/codes/javatech/javatech-cache/src/main/java/io/github/dunwu/javatech/cache/MemcachedDemo.java b/codes/javatech/javatech-cache/src/main/java/io/github/dunwu/javatech/cache/MemcachedDemo.java
new file mode 100644
index 00000000..9399b12d
--- /dev/null
+++ b/codes/javatech/javatech-cache/src/main/java/io/github/dunwu/javatech/cache/MemcachedDemo.java
@@ -0,0 +1,294 @@
+package io.github.dunwu.javatech.cache;
+
+import net.spy.memcached.CASResponse;
+import net.spy.memcached.CASValue;
+import net.spy.memcached.MemcachedClient;
+
+import java.net.InetSocketAddress;
+import java.util.concurrent.Future;
+
+/**
+ * Memcached 客户端连接示例
+ *
+ * @author Zhang Peng
+ * @since 2020-07-10
+ */
+public class MemcachedDemo {
+
+ public static final String URL = "127.0.0.1";
+ public static final int PORT = 11211;
+
+ public static void main(String[] args) {
+ add();
+ remove();
+ append();
+ prepend();
+ cas();
+ get();
+ delete();
+ incrAndDecr();
+ }
+
+ public static void add() {
+ try {
+
+ // 连接本地的 Memcached 服务
+ MemcachedClient mcc = new MemcachedClient(new InetSocketAddress(URL, PORT));
+ System.out.println("Connection to server sucessful.");
+
+ // 添加数据
+ Future fo = mcc.set("MyKey", 900, "Free Education");
+
+ // 打印状态
+ System.out.println("set status:" + fo.get());
+
+ // 输出
+ System.out.println("MyKey value in cache - " + mcc.get("MyKey"));
+
+ // 添加
+ fo = mcc.add("MyKey", 900, "memcached");
+
+ // 打印状态
+ System.out.println("add status:" + fo.get());
+
+ // 添加新key
+ fo = mcc.add("codingground", 900, "All Free Compilers");
+
+ // 打印状态
+ System.out.println("add status:" + fo.get());
+
+ // 输出
+ System.out.println("codingground value in cache - " + mcc.get("codingground"));
+
+ // 关闭连接
+ mcc.shutdown();
+ } catch (Exception ex) {
+ System.out.println(ex.getMessage());
+ }
+ }
+
+ public static void remove() {
+
+ try {
+ //连接本地的 Memcached 服务
+ MemcachedClient mcc = new MemcachedClient(new InetSocketAddress(URL, PORT));
+ System.out.println("Connection to server sucessful.");
+
+ // 添加第一个 key=》value 对
+ Future fo = mcc.set("MyKey", 900, "Free Education");
+
+ // 输出执行 add 方法后的状态
+ System.out.println("add status:" + fo.get());
+
+ // 获取键对应的值
+ System.out.println("MyKey value in cache - " + mcc.get("MyKey"));
+
+ // 添加新的 key
+ fo = mcc.replace("MyKey", 900, "Largest Tutorials' Library");
+
+ // 输出执行 set 方法后的状态
+ System.out.println("replace status:" + fo.get());
+
+ // 获取键对应的值
+ System.out.println("MyKey value in cache - " + mcc.get("MyKey"));
+
+ // 关闭连接
+ mcc.shutdown();
+ } catch (Exception ex) {
+ System.out.println(ex.getMessage());
+ }
+ }
+
+ public static void append() {
+
+ try {
+
+ // 连接本地的 Memcached 服务
+ MemcachedClient mcc = new MemcachedClient(new InetSocketAddress(URL, PORT));
+ System.out.println("Connection to server sucessful.");
+
+ // 添加数据
+ Future fo = mcc.set("MyKey", 900, "Free Education");
+
+ // 输出执行 set 方法后的状态
+ System.out.println("set status:" + fo.get());
+
+ // 获取键对应的值
+ System.out.println("MyKey value in cache - " + mcc.get("MyKey"));
+
+ // 对存在的key进行数据添加操作
+ fo = mcc.append(900, "MyKey", " for All");
+
+ // 输出执行 set 方法后的状态
+ System.out.println("append status:" + fo.get());
+
+ // 获取键对应的值
+ System.out.println("MyKey value in cache - " + mcc.get("codingground"));
+
+ // 关闭连接
+ mcc.shutdown();
+ } catch (Exception ex) {
+ System.out.println(ex.getMessage());
+ }
+ }
+
+ public static void prepend() {
+
+ try {
+
+ // 连接本地的 Memcached 服务
+ MemcachedClient mcc = new MemcachedClient(new InetSocketAddress(URL, PORT));
+ System.out.println("Connection to server sucessful.");
+
+ // 添加数据
+ Future fo = mcc.set("MyKey", 900, "Education for All");
+
+ // 输出执行 set 方法后的状态
+ System.out.println("set status:" + fo.get());
+
+ // 获取键对应的值
+ System.out.println("MyKey value in cache - " + mcc.get("MyKey"));
+
+ // 对存在的key进行数据添加操作
+ fo = mcc.prepend(900, "MyKey", "Free ");
+
+ // 输出执行 set 方法后的状态
+ System.out.println("prepend status:" + fo.get());
+
+ // 获取键对应的值
+ System.out.println("MyKey value in cache - " + mcc.get("codingground"));
+
+ // 关闭连接
+ mcc.shutdown();
+ } catch (Exception ex) {
+ System.out.println(ex.getMessage());
+ }
+ }
+
+ public static void cas() {
+
+ try {
+
+ // 连接本地的 Memcached 服务
+ MemcachedClient mcc = new MemcachedClient(new InetSocketAddress(URL, PORT));
+ System.out.println("Connection to server sucessful.");
+
+ // 添加数据
+ Future fo = mcc.set("MyKey", 900, "Free Education");
+
+ // 输出执行 set 方法后的状态
+ System.out.println("set status:" + fo.get());
+
+ // 使用 get 方法获取数据
+ System.out.println("MyKey value in cache - " + mcc.get("MyKey"));
+
+ // 通过 gets 方法获取 CAS token(令牌)
+ CASValue casValue = mcc.gets("MyKey");
+
+ // 输出 CAS token(令牌) 值
+ System.out.println("CAS token - " + casValue);
+
+ // 尝试使用cas方法来更新数据
+ CASResponse casresp = mcc.cas("MyKey", casValue.getCas(), 900, "Largest Tutorials-Library");
+
+ // 输出 CAS 响应信息
+ System.out.println("CAS Response - " + casresp);
+
+ // 输出值
+ System.out.println("MyKey value in cache - " + mcc.get("MyKey"));
+
+ // 关闭连接
+ mcc.shutdown();
+ } catch (Exception ex) {
+ System.out.println(ex.getMessage());
+ }
+ }
+
+ public static void get() {
+
+ try {
+
+ // 连接本地的 Memcached 服务
+ MemcachedClient mcc = new MemcachedClient(new InetSocketAddress(URL, PORT));
+ System.out.println("Connection to server sucessful.");
+
+ // 添加数据
+ Future fo = mcc.set("MyKey", 900, "Free Education");
+
+ // 输出执行 set 方法后的状态
+ System.out.println("set status:" + fo.get());
+
+ // 使用 get 方法获取数据
+ System.out.println("MyKey value in cache - " + mcc.get("MyKey"));
+
+ // 关闭连接
+ mcc.shutdown();
+ } catch (Exception ex) {
+ System.out.println(ex.getMessage());
+ }
+ }
+
+ public static void delete() {
+
+ try {
+
+ // 连接本地的 Memcached 服务
+ MemcachedClient mcc = new MemcachedClient(new InetSocketAddress(URL, PORT));
+ System.out.println("Connection to server sucessful.");
+
+ // 添加数据
+ Future fo = mcc.set("MyKey", 900, "World's largest online tutorials library");
+
+ // 输出执行 set 方法后的状态
+ System.out.println("set status:" + fo.get());
+
+ // 获取键对应的值
+ System.out.println("MyKey value in cache - " + mcc.get("MyKey"));
+
+ // 对存在的key进行数据添加操作
+ fo = mcc.delete("MyKey");
+
+ // 输出执行 delete 方法后的状态
+ System.out.println("delete status:" + fo.get());
+
+ // 获取键对应的值
+ System.out.println("MyKey value in cache - " + mcc.get("codingground"));
+
+ // 关闭连接
+ mcc.shutdown();
+ } catch (Exception ex) {
+ System.out.println(ex.getMessage());
+ }
+ }
+
+ public static void incrAndDecr() {
+
+ try {
+
+ // 连接本地的 Memcached 服务
+ MemcachedClient mcc = new MemcachedClient(new InetSocketAddress(URL, PORT));
+ System.out.println("Connection to server sucessful.");
+
+ // 添加数字值
+ Future fo = mcc.set("number", 900, "1000");
+
+ // 输出执行 set 方法后的状态
+ System.out.println("set status:" + fo.get());
+
+ // 获取键对应的值
+ System.out.println("value in cache - " + mcc.get("number"));
+
+ // 自增并输出
+ System.out.println("value in cache after increment - " + mcc.incr("number", 111));
+
+ // 自减并输出
+ System.out.println("value in cache after decrement - " + mcc.decr("number", 112));
+
+ // 关闭连接
+ mcc.shutdown();
+ } catch (Exception ex) {
+ System.out.println(ex.getMessage());
+ }
+ }
+
+}
diff --git a/codes/javatech/javatech-cache/src/main/java/io/github/dunwu/javatech/data/User.java b/codes/javatech/javatech-cache/src/main/java/io/github/dunwu/javatech/data/User.java
new file mode 100644
index 00000000..758e3c9a
--- /dev/null
+++ b/codes/javatech/javatech-cache/src/main/java/io/github/dunwu/javatech/data/User.java
@@ -0,0 +1,38 @@
+package io.github.dunwu.javatech.data;
+
+import lombok.Data;
+import lombok.ToString;
+
+import java.io.Serializable;
+
+@Data
+@ToString
+public class User implements Serializable {
+
+ private static final long serialVersionUID = 4142994984277644695L;
+
+ private Long id;
+
+ private String name;
+
+ private Integer age;
+
+ private String address;
+
+ private String email;
+
+ public User() {}
+
+ public User(Long id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ public User(String name, Integer age, String address, String email) {
+ this.name = name;
+ this.age = age;
+ this.address = address;
+ this.email = email;
+ }
+
+}
diff --git a/codes/javatech/javatech-cache/src/main/java/io/github/dunwu/javatech/data/UserDao.java b/codes/javatech/javatech-cache/src/main/java/io/github/dunwu/javatech/data/UserDao.java
new file mode 100644
index 00000000..370147cd
--- /dev/null
+++ b/codes/javatech/javatech-cache/src/main/java/io/github/dunwu/javatech/data/UserDao.java
@@ -0,0 +1,33 @@
+package io.github.dunwu.javatech.data;
+
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.CachePut;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.jdbc.core.JdbcTemplate;
+
+import java.util.List;
+
+public interface UserDao {
+
+ void batchInsert(List users);
+
+ Integer count();
+
+ @CacheEvict(value = "dunwu:users", key = "#name")
+ int deleteByName(String name);
+
+ void insert(User user);
+
+ List list();
+
+ @Cacheable(value = "dunwu:users", key = "#name")
+ User queryByName(String name);
+
+ void recreateTable();
+
+ @CachePut(value = "dunwu:users", key = "#user.name")
+ User update(User user);
+
+ JdbcTemplate getJdbcTemplate();
+
+}
diff --git a/codes/javatech/javatech-cache/src/main/java/io/github/dunwu/javatech/data/UserDaoImpl.java b/codes/javatech/javatech-cache/src/main/java/io/github/dunwu/javatech/data/UserDaoImpl.java
new file mode 100644
index 00000000..a69ec012
--- /dev/null
+++ b/codes/javatech/javatech-cache/src/main/java/io/github/dunwu/javatech/data/UserDaoImpl.java
@@ -0,0 +1,105 @@
+package io.github.dunwu.javatech.data;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.BeanPropertyRowMapper;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Service
+public class UserDaoImpl implements UserDao {
+
+ private final Logger log = LoggerFactory.getLogger(this.getClass());
+
+ private final JdbcTemplate jdbcTemplate;
+
+ public UserDaoImpl(JdbcTemplate jdbcTemplate) {
+ this.jdbcTemplate = jdbcTemplate;
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void batchInsert(List users) {
+ String sql = "INSERT INTO user(name, age, address, email) VALUES(?, ?, ?, ?)";
+
+ List