Compare commits
356 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1e1c3ebbf | ||
|
|
c8f440e24c | ||
|
|
6c5f4e0705 | ||
|
|
73e6b04026 | ||
|
|
0b09de3378 | ||
|
|
69b1355cfe | ||
|
|
459f8a8215 | ||
|
|
a58b9a1fda | ||
|
|
6cdcceda86 | ||
|
|
1cf0f13f7d | ||
|
|
b1b450bb50 | ||
|
|
a7ad2cea8f | ||
|
|
c9dac96366 | ||
|
|
5b9ffe1225 | ||
|
|
b965080bb0 | ||
|
|
ed49d74874 | ||
|
|
91f817d2d2 | ||
|
|
96cb816200 | ||
|
|
bf28505d91 | ||
|
|
ad558cc0e8 | ||
|
|
0065543e96 | ||
|
|
56219d5082 | ||
|
|
ffac717219 | ||
|
|
8794bf5676 | ||
|
|
7130154bc8 | ||
|
|
2b01162ab4 | ||
|
|
3ce30e36bb | ||
|
|
3e183ac824 | ||
|
|
e1d9e72df0 | ||
|
|
bbb71db0d0 | ||
|
|
7310b601d6 | ||
|
|
7220c53ced | ||
|
|
234887cc1d | ||
|
|
fedc4262a0 | ||
|
|
267ac99d65 | ||
|
|
140d3bbf59 | ||
|
|
ae82310b92 | ||
|
|
484b56492b | ||
|
|
167bb2df29 | ||
|
|
a4d58ffc40 | ||
|
|
64a186b0e7 | ||
|
|
e0bed33cc5 | ||
|
|
5e7ea9a884 | ||
|
|
4bdd248f99 | ||
|
|
0789b3d0d5 | ||
|
|
27c61ca0d4 | ||
|
|
7141add68e | ||
|
|
7e11ee98a9 | ||
|
|
cdb6134a3b | ||
|
|
3a9fec5196 | ||
|
|
0396cae524 | ||
|
|
04c503d644 | ||
|
|
f526665633 | ||
|
|
fe069762bb | ||
|
|
62f8ed63be | ||
|
|
4fb1e414be | ||
|
|
4cc6a4ff31 | ||
|
|
6026d8c3bd | ||
|
|
dc9cff541e | ||
|
|
b1151479e0 | ||
|
|
7bb808c08d | ||
|
|
200718d7dc | ||
|
|
d75afa9850 | ||
|
|
8d72e5fdd1 | ||
|
|
1fe15d4e3c | ||
|
|
e87ea9d802 | ||
|
|
b15b5cfb8f | ||
|
|
c9eb577c06 | ||
|
|
ece596d7b9 | ||
|
|
58b6d7848a | ||
|
|
c4bb92433d | ||
|
|
f6e5201cc3 | ||
|
|
b645c76a07 | ||
|
|
27c56119ae | ||
|
|
e034c31333 | ||
|
|
94ada46abc | ||
|
|
4148681bb8 | ||
|
|
e5c5234be8 | ||
|
|
750aafdea1 | ||
|
|
4d3c214d7d | ||
|
|
a5950cc91b | ||
|
|
4d88629707 | ||
|
|
98441ccd3a | ||
|
|
2a44283016 | ||
|
|
db9463bdb1 | ||
|
|
d2e9c8c10f | ||
|
|
b4995868c9 | ||
|
|
10319ab48f | ||
|
|
6146a829ed | ||
|
|
947ef4eb78 | ||
|
|
4cfd48f0c4 | ||
|
|
f1d0984031 | ||
|
|
f119690ab7 | ||
|
|
a77134a601 | ||
|
|
707ed03f1e | ||
|
|
07080b13ee | ||
|
|
f78f9136d0 | ||
|
|
ee4a011a62 | ||
|
|
23f5503a08 | ||
|
|
32a3b2db2e | ||
|
|
f425b0faf6 | ||
|
|
d9cf043cfe | ||
|
|
03e8394bff | ||
|
|
22b4a4afbc | ||
|
|
5a8ee34360 | ||
|
|
1c275e8cfb | ||
|
|
5d138238de | ||
|
|
206f6c8a5d | ||
|
|
8f794be0c1 | ||
|
|
eaf8f06b87 | ||
|
|
5d5312e897 | ||
|
|
08798b6078 | ||
|
|
bf08170f6d | ||
|
|
6d8be979db | ||
|
|
94aefacba4 | ||
|
|
62b971453a | ||
|
|
9dc08d3256 | ||
|
|
dceb314a32 | ||
|
|
3028922835 | ||
|
|
a0a5ba8263 | ||
|
|
99bde764d5 | ||
|
|
c2274379f6 | ||
|
|
204967f05a | ||
|
|
8cd25e4dc2 | ||
|
|
68d4b1898d | ||
|
|
a37f984871 | ||
|
|
4c143310b5 | ||
|
|
6c2d488c37 | ||
|
|
d84b844e53 | ||
|
|
095c381ae5 | ||
|
|
7d11b3c63b | ||
|
|
45ba4fe5f1 | ||
|
|
ac341da05b | ||
|
|
12b9c37095 | ||
|
|
125b25ea27 | ||
|
|
6b1d595d58 | ||
|
|
f26b80e9c1 | ||
|
|
6e1bb43a9e | ||
|
|
35e7ce1435 | ||
|
|
5eb247b999 | ||
|
|
c94e93859f | ||
|
|
d694319191 | ||
|
|
84e61f7765 | ||
|
|
196f0612dc | ||
|
|
4cd5e12686 | ||
|
|
aca128c19d | ||
|
|
5052d20fbd | ||
|
|
74790bea4a | ||
|
|
e922c12611 | ||
|
|
cc632d557b | ||
|
|
a7dd101dbf | ||
|
|
1e1cd5a643 | ||
|
|
6888ccc350 | ||
|
|
8d9a4b6d4f | ||
|
|
470b33e90f | ||
|
|
74a559b1a0 | ||
|
|
9db86b011a | ||
|
|
c94a51f47b | ||
|
|
2a9ec05d45 | ||
|
|
c9811d78a9 | ||
|
|
65435ab32d | ||
|
|
614c7a4687 | ||
|
|
ef541a2d8f | ||
|
|
9141a1434e | ||
|
|
17af07cce0 | ||
|
|
cfa57fac66 | ||
|
|
47523eaa73 | ||
|
|
27a24c6a8a | ||
|
|
9bc2b1a651 | ||
|
|
4f24b7f39c | ||
|
|
4607a1f4e7 | ||
|
|
0f2068b161 | ||
|
|
c039691a71 | ||
|
|
930ee44065 | ||
|
|
8a9aac7d72 | ||
|
|
e50bbb962d | ||
|
|
9ff936d0c1 | ||
|
|
f53727b09c | ||
|
|
525ce1f923 | ||
|
|
16e7cd800c | ||
|
|
3f34bfc0b0 | ||
|
|
548cd1002b | ||
|
|
3423865868 | ||
|
|
037bc8e0de | ||
|
|
e03292de28 | ||
|
|
ee2565905e | ||
|
|
05881b457d | ||
|
|
b116effc6c | ||
|
|
536efeeb9c | ||
|
|
e8638e4bec | ||
|
|
c9db722129 | ||
|
|
90de472bd5 | ||
|
|
50c6c60abf | ||
|
|
cc10372fe1 | ||
|
|
05c98a0656 | ||
|
|
1a790fe391 | ||
|
|
7dbd73cb59 | ||
|
|
4dfa571547 | ||
|
|
9f77f62308 | ||
|
|
facea1ed96 | ||
|
|
e367814db3 | ||
|
|
3a15408c98 | ||
|
|
c504b37118 | ||
|
|
74708dc02f | ||
|
|
0c097498bb | ||
|
|
981c063eff | ||
|
|
5e35c50113 | ||
|
|
e6c2869ff2 | ||
|
|
358bec2e9b | ||
|
|
1cd644f2eb | ||
|
|
f783e5c331 | ||
|
|
c39b1c794f | ||
|
|
2633d43897 | ||
|
|
88dca006c4 | ||
|
|
98feb26b79 | ||
|
|
ac6683e79d | ||
|
|
99d24afbcd | ||
|
|
ba19a9f4c4 | ||
|
|
7fea67a2b5 | ||
|
|
ecd2e6955e | ||
|
|
09d60a3047 | ||
|
|
e24f390412 | ||
|
|
eeacec1a4e | ||
|
|
30cd6373c5 | ||
|
|
87a6ab0559 | ||
|
|
59f27215d3 | ||
|
|
768384dcf0 | ||
|
|
3b52ac0fd2 | ||
|
|
41343b2264 | ||
|
|
d084059f04 | ||
|
|
9253c44ba5 | ||
|
|
ddec0bf2e0 | ||
|
|
aeba1805af | ||
|
|
ecff37e080 | ||
|
|
d31dac75be | ||
|
|
4571c84102 | ||
|
|
6a9f59bee0 | ||
|
|
f1951869f1 | ||
|
|
cfd4195c0f | ||
|
|
d793472b42 | ||
|
|
1e56247b9c | ||
|
|
c34a38857a | ||
|
|
57fa7036dc | ||
|
|
b8a3ac750f | ||
|
|
9d6692db0c | ||
|
|
ad94327226 | ||
|
|
aee1ff9609 | ||
|
|
822e967874 | ||
|
|
2acf890b8e | ||
|
|
3909695b44 | ||
|
|
d0f420a945 | ||
|
|
7618338f38 | ||
|
|
9b2a704e7f | ||
|
|
630c1ec63b | ||
|
|
5b93b28690 | ||
|
|
3aa68b5ffc | ||
|
|
adb0069c59 | ||
|
|
211da66226 | ||
|
|
81911c4073 | ||
|
|
6ff3230bab | ||
|
|
f01eae3590 | ||
|
|
47e8761209 | ||
|
|
8449fe7c0b | ||
|
|
7f3e6ddc65 | ||
|
|
6ca8b6837c | ||
|
|
a9969430a3 | ||
|
|
7c5c06191d | ||
|
|
afc533c3e4 | ||
|
|
71c891ae14 | ||
|
|
e8570bfd09 | ||
|
|
534d64673f | ||
|
|
6bff5c978b | ||
|
|
38a214878a | ||
|
|
149e04d0e4 | ||
|
|
5733d466ca | ||
|
|
a95e2e3259 | ||
|
|
a80a89edf5 | ||
|
|
b8f7d4110f | ||
|
|
00e76a6a09 | ||
|
|
79fa9d88a1 | ||
|
|
c460421279 | ||
|
|
69e4dd6cfe | ||
|
|
b73f0ae2c9 | ||
|
|
2af577380e | ||
|
|
89bfdc478f | ||
|
|
b6e8221ac1 | ||
|
|
ea6d3d7107 | ||
|
|
7d8bdfcd45 | ||
|
|
70fe1b5d2b | ||
|
|
a7caf0260a | ||
|
|
d547793eee | ||
|
|
d92f27c44b | ||
|
|
8561ff3e2d | ||
|
|
0736b20d33 | ||
|
|
263acb775b | ||
|
|
7a1fff8504 | ||
|
|
5c5adf690f | ||
|
|
1eca5099df | ||
|
|
5f2ad8b096 | ||
|
|
4405cfd405 | ||
|
|
2f6aa0c14f | ||
|
|
bb50ecd682 | ||
|
|
ae1454f9bb | ||
|
|
3781b1920a | ||
|
|
125ad9c606 | ||
|
|
e516300dc7 | ||
|
|
6c1d24c3e5 | ||
|
|
6f230b30e0 | ||
|
|
8a0318b4f3 | ||
|
|
d9fddcb001 | ||
|
|
9113c4c1b3 | ||
|
|
f0762fe1b9 | ||
|
|
9054b8ec05 | ||
|
|
5ad25e34c6 | ||
|
|
d4cca10301 | ||
|
|
8597884ec5 | ||
|
|
9f469f5b77 | ||
|
|
5bc4f5b359 | ||
|
|
494ff5b5bb | ||
|
|
bac0060a74 | ||
|
|
062ca1cfcd | ||
|
|
8a4373e984 | ||
|
|
99670e46a5 | ||
|
|
64642c6680 | ||
|
|
f7a7b50eda | ||
|
|
f5265f1832 | ||
|
|
2d6f7522fc | ||
|
|
cfb15e764d | ||
|
|
406d8de999 | ||
|
|
2ca79953b9 | ||
|
|
d067cd8437 | ||
|
|
e4937685bc | ||
|
|
6ac2343aa7 | ||
|
|
1d80bc640d | ||
|
|
b19c431b89 | ||
|
|
a1e1b5ef98 | ||
|
|
e93275ac5c | ||
|
|
a6059ab070 | ||
|
|
5b0f94f317 | ||
|
|
0b4ac55aa6 | ||
|
|
9080c78b13 | ||
|
|
aa7d67e387 | ||
|
|
405b3615fe | ||
|
|
7534af09ed | ||
|
|
cd3bf0cad4 | ||
|
|
41ebdfb7d2 | ||
|
|
fa7d4963cb | ||
|
|
627d9721b3 | ||
|
|
8c3cd53dc3 | ||
|
|
5995be8489 | ||
|
|
eabaa84252 | ||
|
|
40e137e69e | ||
|
|
6f51fe52f8 | ||
|
|
dd4071e7dc | ||
|
|
7e5b3254eb | ||
|
|
ad1947379d |
@@ -71,4 +71,6 @@ linters:
|
||||
- forcetypeassert
|
||||
- whitespace
|
||||
- noctx
|
||||
- rowserrcheck
|
||||
- rowserrcheck
|
||||
- tagliatelle
|
||||
- protogetter
|
||||
@@ -1,9 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
./build.sh linux amd64
|
||||
./build.sh linux 386
|
||||
#./build.sh linux 386
|
||||
./build.sh linux arm64
|
||||
./build.sh linux mips64
|
||||
./build.sh linux mips64le
|
||||
./build.sh darwin amd64
|
||||
./build.sh darwin arm64
|
||||
#./build.sh linux mips64
|
||||
#./build.sh linux mips64le
|
||||
#./build.sh darwin amd64
|
||||
#./build.sh darwin arm64
|
||||
@@ -6,8 +6,11 @@ function build() {
|
||||
VERSION=$(lookup-version "$ROOT"/../internal/const/const.go)
|
||||
DIST=$ROOT/"../dist/${NAME}"
|
||||
MUSL_DIR="/usr/local/opt/musl-cross/bin"
|
||||
GCC_X86_64_DIR="/usr/local/Cellar/x86_64-unknown-linux-gnu/10.3.0/bin"
|
||||
GCC_ARM64_DIR="/usr/local/Cellar/aarch64-unknown-linux-gnu/10.3.0/bin"
|
||||
SRCDIR=$(realpath "$ROOT/..")
|
||||
|
||||
# for macOS users: precompiled gcc can be downloaded from https://github.com/messense/homebrew-macos-cross-toolchains
|
||||
GCC_X86_64_DIR="/usr/local/gcc/x86_64-unknown-linux-gnu/bin"
|
||||
GCC_ARM64_DIR="/usr/local/gcc/aarch64-unknown-linux-gnu/bin"
|
||||
|
||||
OS=${1}
|
||||
ARCH=${2}
|
||||
@@ -57,7 +60,10 @@ function build() {
|
||||
# we support TOA on linux only
|
||||
if [ "$OS" == "linux" ] && [ -f "${ROOT}/edge-toa/edge-toa-${ARCH}" ]
|
||||
then
|
||||
mkdir "$DIST/edge-toa"
|
||||
if [ ! -d "$DIST/edge-toa" ]
|
||||
then
|
||||
mkdir "$DIST/edge-toa"
|
||||
fi
|
||||
cp "${ROOT}/edge-toa/edge-toa-${ARCH}" "$DIST/edge-toa/edge-toa"
|
||||
fi
|
||||
|
||||
@@ -65,6 +71,8 @@ function build() {
|
||||
|
||||
CC_PATH=""
|
||||
CXX_PATH=""
|
||||
CGO_LDFLAGS=""
|
||||
CGO_CFLAGS=""
|
||||
BUILD_TAG=$TAG
|
||||
if [[ `uname -a` == *"Darwin"* && "${OS}" == "linux" ]]; then
|
||||
if [ "${ARCH}" == "amd64" ]; then
|
||||
@@ -74,7 +82,7 @@ function build() {
|
||||
CC_PATH="x86_64-unknown-linux-gnu-gcc"
|
||||
CXX_PATH="x86_64-unknown-linux-gnu-g++"
|
||||
if [ "$TAG" = "plus" ]; then
|
||||
BUILD_TAG="plus,script"
|
||||
BUILD_TAG="plus,script,packet"
|
||||
fi
|
||||
else
|
||||
CC_PATH="x86_64-linux-musl-gcc"
|
||||
@@ -92,7 +100,7 @@ function build() {
|
||||
CC_PATH="aarch64-unknown-linux-gnu-gcc"
|
||||
CXX_PATH="aarch64-unknown-linux-gnu-g++"
|
||||
if [ "$TAG" = "plus" ]; then
|
||||
BUILD_TAG="plus,script"
|
||||
BUILD_TAG="plus,script,packet"
|
||||
fi
|
||||
else
|
||||
CC_PATH="aarch64-linux-musl-gcc"
|
||||
@@ -112,13 +120,31 @@ function build() {
|
||||
CXX_PATH="mips64el-linux-musl-g++"
|
||||
fi
|
||||
fi
|
||||
|
||||
# libpcap
|
||||
if [ "$OS" == "linux" ] && [[ "$ARCH" == "amd64" || "$ARCH" == "arm64" ]] && [ "$TAG" == "plus" ]; then
|
||||
CGO_LDFLAGS="-L${SRCDIR}/libs/libpcap/${ARCH} -lpcap -L${SRCDIR}/libs/libbrotli/${ARCH} -lbrotlienc -lbrotlidec -lbrotlicommon"
|
||||
CGO_CFLAGS="-I${SRCDIR}/libs/libpcap/src/libpcap -I${SRCDIR}/libs/libpcap/src/libpcap/pcap -I${SRCDIR}/libs/libbrotli/src/brotli/c/include"
|
||||
fi
|
||||
|
||||
if [ ! -z $CC_PATH ]; then
|
||||
env CC=$MUSL_DIR/$CC_PATH CXX=$MUSL_DIR/$CXX_PATH GOOS="${OS}" GOARCH="${ARCH}" CGO_ENABLED=1 go build -trimpath -tags $BUILD_TAG -o "$DIST"/bin/${NAME} -ldflags "-linkmode external -extldflags -static -s -w" "$ROOT"/../cmd/edge-node/main.go
|
||||
env CC=$MUSL_DIR/$CC_PATH \
|
||||
CXX=$MUSL_DIR/$CXX_PATH GOOS="${OS}" \
|
||||
GOARCH="${ARCH}" CGO_ENABLED=1 \
|
||||
CGO_LDFLAGS="${CGO_LDFLAGS}" \
|
||||
CGO_CFLAGS="${CGO_CFLAGS}" \
|
||||
go build -trimpath -tags $BUILD_TAG -o "$DIST"/bin/${NAME} -ldflags "-linkmode external -extldflags -static -s -w" "$ROOT"/../cmd/edge-node/main.go
|
||||
else
|
||||
if [[ `uname` == *"Linux"* ]] && [ "$OS" == "linux" ] && [[ "$ARCH" == "amd64" || "$ARCH" == "arm64" ]] && [ "$TAG" == "plus" ]; then
|
||||
BUILD_TAG="plus,script"
|
||||
BUILD_TAG="plus,script,packet"
|
||||
fi
|
||||
env GOOS="${OS}" GOARCH="${ARCH}" CGO_ENABLED=1 go build -trimpath -tags $BUILD_TAG -o "$DIST"/bin/${NAME} -ldflags="-s -w" "$ROOT"/../cmd/edge-node/main.go
|
||||
|
||||
env GOOS="${OS}" GOARCH="${ARCH}" CGO_ENABLED=1 CGO_LDFLAGS="${CGO_LDFLAGS}" CGO_CFLAGS="${CGO_CFLAGS}" go build -trimpath -tags $BUILD_TAG -o "$DIST"/bin/${NAME} -ldflags="-s -w" "$ROOT"/../cmd/edge-node/main.go
|
||||
fi
|
||||
|
||||
if [ ! -f "${DIST}/bin/${NAME}" ]; then
|
||||
echo "build failed!"
|
||||
exit
|
||||
fi
|
||||
|
||||
# delete hidden files
|
||||
|
||||
@@ -6,4 +6,9 @@ if [ -z "$TAG" ]; then
|
||||
TAG="community"
|
||||
fi
|
||||
|
||||
go test -v ../... -tags=${TAG}
|
||||
# stop node
|
||||
go run -tags=${TAG} ../cmd/edge-node/main.go stop
|
||||
|
||||
# reference: https://pkg.go.dev/cmd/go/internal/test
|
||||
go clean -testcache
|
||||
go test -timeout 60s -tags="${TAG}" -cover ../...
|
||||
@@ -16,12 +16,14 @@ import (
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"github.com/iwind/gosock/pkg/gosock"
|
||||
"gopkg.in/yaml.v3"
|
||||
"net"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -29,8 +31,8 @@ func main() {
|
||||
var app = apps.NewAppCmd().
|
||||
Version(teaconst.Version).
|
||||
Product(teaconst.ProductName).
|
||||
Usage(teaconst.ProcessName + " [-v|start|stop|restart|status|quit|test|reload|service|daemon|pprof|accesslog|uninstall]").
|
||||
Usage(teaconst.ProcessName + " [trackers|goman|conns|gc|bandwidth|disk]").
|
||||
Usage(teaconst.ProcessName + " [-v|start|stop|restart|status|quit|test|reload|service|daemon|config|pprof|accesslog|uninstall]").
|
||||
Usage(teaconst.ProcessName + " [trackers|goman|conns|gc|bandwidth|disk|cache.garbage]").
|
||||
Usage(teaconst.ProcessName + " [ip.drop|ip.reject|ip.remove|ip.close] IP")
|
||||
|
||||
app.On("start:before", func() {
|
||||
@@ -227,11 +229,18 @@ func main() {
|
||||
})
|
||||
app.On("gc", func() {
|
||||
var sock = gosock.NewTmpSock(teaconst.ProcessName)
|
||||
_, err := sock.Send(&gosock.Command{Code: "gc"})
|
||||
reply, err := sock.Send(&gosock.Command{Code: "gc"})
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]" + err.Error())
|
||||
} else {
|
||||
fmt.Println("ok")
|
||||
if reply == nil {
|
||||
fmt.Println("ok")
|
||||
} else {
|
||||
var paramMap = maps.NewMap(reply.Params)
|
||||
var pauseMS = paramMap.GetFloat64("pauseMS")
|
||||
var costMS = paramMap.GetFloat64("costMS")
|
||||
fmt.Printf("ok, cost: %.4fms, pause: %.4fms", costMS, pauseMS)
|
||||
}
|
||||
}
|
||||
})
|
||||
app.On("ip.drop", func() {
|
||||
@@ -464,6 +473,93 @@ func main() {
|
||||
fmt.Println("Usage: edge-node disk [speed]")
|
||||
}
|
||||
})
|
||||
app.On("cache.garbage", func() {
|
||||
fmt.Println("scanning ...")
|
||||
|
||||
var shouldDelete bool
|
||||
for _, arg := range os.Args {
|
||||
if strings.TrimLeft(arg, "-") == "delete" {
|
||||
shouldDelete = true
|
||||
}
|
||||
}
|
||||
|
||||
var progressSock = gosock.NewTmpSock(teaconst.CacheGarbageSockName)
|
||||
progressSock.OnCommand(func(cmd *gosock.Command) {
|
||||
var params = maps.NewMap(cmd.Params)
|
||||
if cmd.Code == "progress" {
|
||||
fmt.Printf("%.2f%% %d\n", params.GetFloat64("progress")*100, params.GetInt("count"))
|
||||
_ = cmd.ReplyOk()
|
||||
}
|
||||
})
|
||||
go func() {
|
||||
_ = progressSock.Listen()
|
||||
}()
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
var sock = gosock.NewTmpSock(teaconst.ProcessName)
|
||||
reply, err := sock.Send(&gosock.Command{
|
||||
Code: "cache.garbage",
|
||||
Params: map[string]any{"delete": shouldDelete},
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]" + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var params = maps.NewMap(reply.Params)
|
||||
if params.GetBool("isOk") {
|
||||
var count = params.GetInt("count")
|
||||
fmt.Println("found", count, "bad caches")
|
||||
|
||||
if count > 0 {
|
||||
fmt.Println("======")
|
||||
var sampleFiles = params.GetSlice("sampleFiles")
|
||||
for _, file := range sampleFiles {
|
||||
fmt.Println(types.String(file))
|
||||
}
|
||||
if count > len(sampleFiles) {
|
||||
fmt.Println("... more files")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fmt.Println("[ERROR]" + params.GetString("error"))
|
||||
}
|
||||
})
|
||||
app.On("config", func() {
|
||||
var configString = os.Args[len(os.Args)-1]
|
||||
if configString == "config" {
|
||||
fmt.Println("Usage: edge-node config '\nrpc.endpoints: [\"...\"]\nnodeId: \"...\"\nsecret: \"...\"\n'")
|
||||
return
|
||||
}
|
||||
|
||||
var config = &configs.APIConfig{}
|
||||
err := yaml.Unmarshal([]byte(configString), config)
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]decode config failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = config.Init()
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]validate config failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// marshal again
|
||||
configYAML, err := yaml.Marshal(config)
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]encode config failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = os.WriteFile(Tea.Root + "/configs/api_node.yaml", configYAML, 0666)
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]write config failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("success")
|
||||
})
|
||||
app.Run(func() {
|
||||
var node = nodes.NewNode()
|
||||
node.Start()
|
||||
|
||||
94
go.mod
94
go.mod
@@ -1,82 +1,98 @@
|
||||
module github.com/TeaOSLab/EdgeNode
|
||||
|
||||
go 1.18
|
||||
go 1.21
|
||||
|
||||
replace (
|
||||
github.com/TeaOSLab/EdgeCommon => ../EdgeCommon
|
||||
github.com/dchest/captcha => github.com/iwind/captcha v0.0.0-20231130092438-ae985686ed84
|
||||
github.com/fsnotify/fsnotify => github.com/iwind/fsnotify v1.5.2-0.20220817040843-193be2051ff4
|
||||
github.com/google/nftables => github.com/iwind/nftables v0.0.0-20230419014751-9f023a644ad4
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/TeaOSLab/EdgeCommon v0.0.0-00010101000000-000000000000
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.7+incompatible
|
||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
|
||||
github.com/andybalholm/brotli v1.0.5
|
||||
github.com/aws/aws-sdk-go v1.44.279
|
||||
github.com/baidubce/bce-sdk-go v0.9.170
|
||||
github.com/biessek/golang-ico v0.0.0-20180326222316-d348d9ea4670
|
||||
github.com/cespare/xxhash v1.1.0
|
||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
||||
github.com/fsnotify/fsnotify v1.6.0
|
||||
github.com/cespare/xxhash/v2 v2.2.0
|
||||
github.com/cockroachdb/pebble v1.1.0
|
||||
github.com/dchest/captcha v0.0.0-00010101000000-000000000000
|
||||
github.com/fsnotify/fsnotify v1.7.0
|
||||
github.com/go-redis/redis/v8 v8.11.5
|
||||
github.com/golang/protobuf v1.5.3
|
||||
github.com/google/nftables v0.1.0
|
||||
github.com/google/gopacket v1.1.19
|
||||
github.com/google/nftables v0.2.0
|
||||
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.23.4+incompatible
|
||||
github.com/iwind/TeaGo v0.0.0-20230630104525-161f0b32996d
|
||||
github.com/iwind/TeaGo v0.0.0-20240411075713-6c1fc9aca7b6
|
||||
github.com/iwind/gofcgi v0.0.0-20210528023741-a92711d45f11
|
||||
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4
|
||||
github.com/iwind/gowebp v0.0.0-20230615040911-5013dbb9d508
|
||||
github.com/klauspost/compress v1.16.5
|
||||
github.com/mattn/go-sqlite3 v1.14.9
|
||||
github.com/mdlayher/netlink v1.7.1
|
||||
github.com/iwind/gowebp v0.0.0-20240109104518-489f3429f5c5
|
||||
github.com/klauspost/compress v1.17.8
|
||||
github.com/mattn/go-sqlite3 v1.14.17
|
||||
github.com/mdlayher/netlink v1.7.2
|
||||
github.com/miekg/dns v1.1.43
|
||||
github.com/mssola/useragent v1.0.0
|
||||
github.com/pires/go-proxyproto v0.6.1
|
||||
github.com/qiniu/go-sdk/v7 v7.16.0
|
||||
github.com/quic-go/quic-go v0.36.1
|
||||
github.com/quic-go/quic-go v0.42.0
|
||||
github.com/shirou/gopsutil/v3 v3.22.2
|
||||
github.com/tdewolff/minify/v2 v2.20.19
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.41
|
||||
golang.org/x/image v0.7.0
|
||||
golang.org/x/net v0.12.0
|
||||
golang.org/x/sys v0.10.0
|
||||
google.golang.org/grpc v1.55.0
|
||||
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f
|
||||
golang.org/x/image v0.15.0
|
||||
golang.org/x/net v0.24.0
|
||||
golang.org/x/sys v0.19.0
|
||||
google.golang.org/grpc v1.62.1
|
||||
google.golang.org/protobuf v1.33.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/chai2010/webp v1.1.1 // indirect
|
||||
github.com/DataDog/zstd v1.5.5 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/clbanning/mxj v1.8.4 // indirect
|
||||
github.com/cockroachdb/errors v1.11.1 // indirect
|
||||
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect
|
||||
github.com/cockroachdb/redact v1.1.5 // indirect
|
||||
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/getsentry/sentry-go v0.27.0 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/golang/mock v1.6.0 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/go-querystring v1.0.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8 // indirect
|
||||
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/josharian/native v1.0.0 // indirect
|
||||
github.com/josharian/native v1.1.0 // indirect
|
||||
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/mdlayher/socket v0.4.0 // indirect
|
||||
github.com/mdlayher/socket v0.5.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.3 // indirect
|
||||
github.com/mozillazg/go-httpheader v0.2.1 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.11.0 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.16.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/prometheus/client_golang v1.19.0 // indirect
|
||||
github.com/prometheus/client_model v0.6.0 // indirect
|
||||
github.com/prometheus/common v0.51.0 // indirect
|
||||
github.com/prometheus/procfs v0.13.0 // indirect
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/quic-go/qtls-go1-19 v0.3.2 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.2.2 // indirect
|
||||
github.com/tdewolff/minify/v2 v2.12.7 // indirect
|
||||
github.com/tdewolff/parse/v2 v2.6.6 // indirect
|
||||
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
||||
github.com/tdewolff/parse/v2 v2.7.12 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.9 // indirect
|
||||
github.com/tklauser/numcpus v0.3.0 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
golang.org/x/crypto v0.11.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect
|
||||
golang.org/x/mod v0.12.0 // indirect
|
||||
golang.org/x/sync v0.3.0 // indirect
|
||||
golang.org/x/text v0.11.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/tools v0.11.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
go.uber.org/mock v0.4.0 // indirect
|
||||
golang.org/x/crypto v0.22.0 // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/tools v0.20.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect
|
||||
)
|
||||
|
||||
247
go.sum
247
go.sum
@@ -1,34 +1,46 @@
|
||||
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ=
|
||||
github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
|
||||
github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.7+incompatible h1:KpbJFXwhVeuxNtBJ74MCGbIoaBok2uZvkD7QXp2+Wis=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.7+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible h1:8psS8a+wKfiLt1iVDX79F7Y6wUM49Lcha2FMXt4UM8g=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/aws/aws-sdk-go v1.44.279 h1:g23dxnYjIiPlQo0gIKNR0zVPsSvo1bj5frWln+5sfhk=
|
||||
github.com/aws/aws-sdk-go v1.44.279/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
|
||||
github.com/baidubce/bce-sdk-go v0.9.170 h1:vAr7COuhu6SEf+8f77DVRji45x7TVZtY5kbu9sX7q8g=
|
||||
github.com/baidubce/bce-sdk-go v0.9.170/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/biessek/golang-ico v0.0.0-20180326222316-d348d9ea4670 h1:FQPKKjDhzG0T4ew6dm6MGrXb4PRAi8ZmTuYuxcF62BM=
|
||||
github.com/biessek/golang-ico v0.0.0-20180326222316-d348d9ea4670/go.mod h1:iRWAFbKXMMkVQyxZ1PfGlkBr1TjATx1zy2MRprV7A3Q=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chai2010/webp v1.1.1 h1:jTRmEccAJ4MGrhFOrPMpNGIJ/eybIgwKpcACsrTEapk=
|
||||
github.com/chai2010/webp v1.1.1/go.mod h1:0XVwvZWdjjdxpUEIf7b9g9VkHFnInUSYujwqTLEuldU=
|
||||
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
|
||||
github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I=
|
||||
github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
|
||||
github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4=
|
||||
github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU=
|
||||
github.com/cockroachdb/errors v1.11.1 h1:xSEW75zKaKCWzR3OfxXUxgrk/NtT4G1MiOv5lWZazG8=
|
||||
github.com/cockroachdb/errors v1.11.1/go.mod h1:8MUxA3Gi6b25tYlFEBGLf+D8aISL+M4MIpiWMSNRfxw=
|
||||
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE=
|
||||
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs=
|
||||
github.com/cockroachdb/pebble v1.1.0 h1:pcFh8CdCIt2kmEpK0OIatq67Ln9uGDYY3d5XnE0LJG4=
|
||||
github.com/cockroachdb/pebble v1.1.0/go.mod h1:sEHm5NOXxyiAoKWhoFxT8xMgd/f3RA6qUqQ1BXKrh2E=
|
||||
github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30=
|
||||
github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg=
|
||||
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo=
|
||||
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f h1:q/DpyjJjZs94bziQ7YkBmIlpqbVP7yw179rnzoNVX1M=
|
||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f/go.mod h1:QGrK8vMWWHQYQ3QU9bw9Y9OPNfxccGzfb41qjvVeXtY=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/djherbis/atime v1.1.0/go.mod h1:28OF6Y8s3NQWwacXc5eZTsEsiMzp7LF8MbXE+XJPdBE=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps=
|
||||
github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
|
||||
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
||||
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
@@ -41,50 +53,58 @@ github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC
|
||||
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8 h1:n6vlPhxsA+BW/XsS5+uqi7GyzaLa5MH7qlSLBZtRdiA=
|
||||
github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA=
|
||||
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
||||
github.com/google/nftables v0.2.0 h1:PbJwaBmbVLzpeldoeUKGkE2RjstrjPKMl6oLrfEJ6/8=
|
||||
github.com/google/nftables v0.2.0/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4=
|
||||
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 h1:y3N7Bm7Y9/CtpiVkw/ZWj6lSlDF3F74SfKwfTCer72Q=
|
||||
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.23.4+incompatible h1:XRAk4HBDLCYEdPLWtKf5iZhOi7lfx17aY0oSO9+mcg8=
|
||||
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.23.4+incompatible/go.mod h1:l7VUhRbTKCzdOacdT4oWCwATKyvZqUOlOqr0Ous3k4s=
|
||||
github.com/iwind/TeaGo v0.0.0-20230630104525-161f0b32996d h1:XnTIj781NdSipts60fVqbgZorVAKVSRaA6nqVNfBQ1g=
|
||||
github.com/iwind/TeaGo v0.0.0-20230630104525-161f0b32996d/go.mod h1:fi/Pq+/5m2HZoseM+39dMF57ANXRt6w4PkGu3NXPc5s=
|
||||
github.com/iwind/TeaGo v0.0.0-20240411075713-6c1fc9aca7b6 h1:dS3pTxrLlDQxdoxSUcHkHnr3LHpsBIXv8v2/xw65RN8=
|
||||
github.com/iwind/TeaGo v0.0.0-20240411075713-6c1fc9aca7b6/go.mod h1:SfqVbWyIPdVflyA6lMgicZzsoGS8pyeLiTRe8/CIpGI=
|
||||
github.com/iwind/captcha v0.0.0-20231130092438-ae985686ed84 h1:/RtK8t22a/YFkBWiEwxS+JWcDmxAKsu+r+p00c36K0Q=
|
||||
github.com/iwind/captcha v0.0.0-20231130092438-ae985686ed84/go.mod h1:7zoElIawLp7GUMLcj54K9kbw+jEyvz2K0FDdRRYhvWo=
|
||||
github.com/iwind/fsnotify v1.5.2-0.20220817040843-193be2051ff4 h1:PKtXlgNHJhdwl5ozio7KRV3n0SckMw+8ZC2NCpRSv8U=
|
||||
github.com/iwind/fsnotify v1.5.2-0.20220817040843-193be2051ff4/go.mod h1:DmAukmDY25inGlriLn0B2jidmvaDR1REOcPXwvzvDPI=
|
||||
github.com/iwind/gofcgi v0.0.0-20210528023741-a92711d45f11 h1:DaQjoWZhLNxjhIXedVg4/vFEtHkZhK4IjIwsWdyzBLg=
|
||||
github.com/iwind/gofcgi v0.0.0-20210528023741-a92711d45f11/go.mod h1:JtbX20untAjUVjZs1ZBtq80f5rJWvwtQNRL6EnuYRnY=
|
||||
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4 h1:VWGsCqTzObdlbf7UUE3oceIpcEKi4C/YBUszQXk118A=
|
||||
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4/go.mod h1:H5Q7SXwbx3a97ecJkaS2sD77gspzE7HFUafBO0peEyA=
|
||||
github.com/iwind/gowebp v0.0.0-20230615040911-5013dbb9d508 h1:fjKiHAyPQmdwuw1DQ2BI1JTbhUWAtI3Kr9wIZQBdRgQ=
|
||||
github.com/iwind/gowebp v0.0.0-20230615040911-5013dbb9d508/go.mod h1:Re7TEhwL+ygnxFg52fC0PWy01ULAIZp2QR0q5WwEOQA=
|
||||
github.com/iwind/nftables v0.0.0-20230419014751-9f023a644ad4 h1:RPAH9Sj9l/20zH5zU5/iJGszfwPq6eLjoiC/n/asulA=
|
||||
github.com/iwind/nftables v0.0.0-20230419014751-9f023a644ad4/go.mod h1:7OLL+86wZKfBnAJxNxmdcZ0ebbgdp/A28fcagx9oJqA=
|
||||
github.com/iwind/gowebp v0.0.0-20240109104518-489f3429f5c5 h1:tA0HEDQJ/FM847wc7kVpSgkTfMF1LervEmd2UZQr3Po=
|
||||
github.com/iwind/gowebp v0.0.0-20240109104518-489f3429f5c5/go.mod h1:AYyXDhbbD7q9N6rJff2jrE7pGupaiyvtv3YeyIAQLXk=
|
||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk=
|
||||
github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
||||
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e h1:LvL4XsI70QxOGHed6yhQtAU34Kx3Qq2wwBzGFKY8zKk=
|
||||
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
|
||||
github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI=
|
||||
github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
|
||||
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
@@ -92,13 +112,12 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs=
|
||||
github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA=
|
||||
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mdlayher/netlink v1.7.1 h1:FdUaT/e33HjEXagwELR8R3/KL1Fq5x3G5jgHLp/BTmg=
|
||||
github.com/mdlayher/netlink v1.7.1/go.mod h1:nKO5CSjE/DJjVhk/TNp6vCE1ktVxEA8VEh8drhZzxsQ=
|
||||
github.com/mdlayher/socket v0.4.0 h1:280wsy40IC9M9q1uPGcLBwXpcTQDtoGwVt+BNoITxIw=
|
||||
github.com/mdlayher/socket v0.4.0/go.mod h1:xxFqz5GRCUN3UEOm9CZqEJsAbe1C8OwSK46NlmWuVoc=
|
||||
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
||||
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
|
||||
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
|
||||
github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI=
|
||||
github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI=
|
||||
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
|
||||
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
|
||||
github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs=
|
||||
@@ -108,50 +127,60 @@ github.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISe
|
||||
github.com/mssola/useragent v1.0.0 h1:WRlDpXyxHDNfvZaPEut5Biveq86Ze4o4EMffyMxmH5o=
|
||||
github.com/mssola/useragent v1.0.0/go.mod h1:hz9Cqz4RXusgg1EdI4Al0INR62kP7aPSRNHnpU+b85Y=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU=
|
||||
github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM=
|
||||
github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/ginkgo/v2 v2.16.0 h1:7q1w9frJDzninhXxjZd+Y/x54XNjG/UlRLIYPZafsPM=
|
||||
github.com/onsi/ginkgo/v2 v2.16.0/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs=
|
||||
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
|
||||
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
|
||||
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
|
||||
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
|
||||
github.com/pires/go-proxyproto v0.6.1 h1:EBupykFmo22SDjv4fQVQd2J9NOoLPmyZA/15ldOGkPw=
|
||||
github.com/pires/go-proxyproto v0.6.1/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
|
||||
github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
|
||||
github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos=
|
||||
github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8=
|
||||
github.com/prometheus/common v0.51.0 h1:vT5R9NAlW4V6k8Wruk7ikrHaHRsrPbduM/cKTOdQM/k=
|
||||
github.com/prometheus/common v0.51.0/go.mod h1:wHFBCEVWVmHMUpg7pYcOm2QUR/ocQdYSJVQJKnHc3xQ=
|
||||
github.com/prometheus/procfs v0.13.0 h1:GqzLlQyfsPbaEHaQkO7tbDlriv/4o5Hudv6OXHGKX7o=
|
||||
github.com/prometheus/procfs v0.13.0/go.mod h1:cd4PFCR54QLnGKPaKGA6l+cfuNXtht43ZKY6tow0Y1g=
|
||||
github.com/qiniu/dyn v1.3.0/go.mod h1:E8oERcm8TtwJiZvkQPbcAh0RL8jO1G0VXJMW3FAWdkk=
|
||||
github.com/qiniu/go-sdk/v7 v7.16.0 h1:Jt4YOMLuaDfgb/KdVg0O1fYLpv5MDkYe/zV+Ri7gWRs=
|
||||
github.com/qiniu/go-sdk/v7 v7.16.0/go.mod h1:nqoYCNo53ZlGA521RvRethvxUDvXKt4gtYXOwye868w=
|
||||
github.com/qiniu/x v1.10.5/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs=
|
||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||
github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U=
|
||||
github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
|
||||
github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E=
|
||||
github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
|
||||
github.com/quic-go/quic-go v0.36.1 h1:WsG73nVtnDy1TiACxFxhQ3TqaW+DipmqzLEtNlAwZyY=
|
||||
github.com/quic-go/quic-go v0.36.1/go.mod h1:zPetvwDlILVxt15n3hr3Gf/I3mDf7LpLKPhR4Ez0AZQ=
|
||||
github.com/quic-go/quic-go v0.42.0 h1:uSfdap0eveIl8KXnipv9K7nlwZ5IqLlYOpJ58u5utpM=
|
||||
github.com/quic-go/quic-go v0.42.0/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/shirou/gopsutil/v3 v3.22.2 h1:wCrArWFkHYIdDxx/FSfF5RB4dpJYW6t7rcp3+zL8uks=
|
||||
github.com/shirou/gopsutil/v3 v3.22.2/go.mod h1:WapW1AOOPlHyXr+yOyw3uYx36enocrtSoSBy0L5vUHY=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/tdewolff/minify/v2 v2.12.7 h1:pBzz2tAfz5VghOXiQIsSta6srhmTeinQPjRDHWoumCA=
|
||||
github.com/tdewolff/minify/v2 v2.12.7/go.mod h1:ZRKTheiOGyLSK8hOZWWv+YoJAECzDivNgAlVYDHp/Ws=
|
||||
github.com/tdewolff/parse/v2 v2.6.6 h1:Yld+0CrKUJaCV78DL1G2nk3C9lKrxyRTux5aaK/AkDo=
|
||||
github.com/tdewolff/parse/v2 v2.6.6/go.mod h1:woz0cgbLwFdtbjJu8PIKxhW05KplTFQkOdX78o+Jgrs=
|
||||
github.com/tdewolff/test v1.0.7/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
||||
github.com/tdewolff/test v1.0.9 h1:SswqJCmeN4B+9gEAi/5uqT0qpi1y2/2O47V/1hhGZT0=
|
||||
github.com/tdewolff/test v1.0.9/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/tdewolff/minify/v2 v2.20.19 h1:tX0SR0LUrIqGoLjXnkIzRSIbKJ7PaNnSENLD4CyH6Xo=
|
||||
github.com/tdewolff/minify/v2 v2.20.19/go.mod h1:ulkFoeAVWMLEyjuDz1ZIWOA31g5aWOawCFRp9R/MudM=
|
||||
github.com/tdewolff/parse/v2 v2.7.12 h1:tgavkHc2ZDEQVKy1oWxwIyh5bP4F5fEh/JmBwPP/3LQ=
|
||||
github.com/tdewolff/parse/v2 v2.7.12/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA=
|
||||
github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
||||
github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739 h1:IkjBCtQOOjIn03u/dMQK9g+Iw9ewps4mCl1nB8Sscbo=
|
||||
github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.194/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/kms v1.0.194/go.mod h1:yrBKWhChnDqNz1xuXdSbWXG56XawEq0G5j1lg4VwBD4=
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.41 h1:iU0Li/Np78H4SBna0ECQoF3mpgi6ImLXU+doGzPFXGc=
|
||||
@@ -161,104 +190,106 @@ github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ
|
||||
github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ=
|
||||
github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8=
|
||||
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc h1:R83G5ikgLMxrBvLh22JhdfI8K6YXEPHx5P03Uu3DRs4=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
|
||||
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
||||
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
|
||||
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
|
||||
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||
golang.org/x/image v0.7.0 h1:gzS29xtG1J5ybQlv0PuyfE3nmc6R4qB73m6LUUmvFuw=
|
||||
golang.org/x/image v0.7.0/go.mod h1:nd/q4ef1AKKYl/4kft7g+6UyGbdiqWqTP1ZAbRoV7Rg=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY=
|
||||
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
|
||||
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
|
||||
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
|
||||
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
|
||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8=
|
||||
golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8=
|
||||
golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY=
|
||||
golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc h1:XSJ8Vk1SWuNr8S18z1NZSziL0CPIXLCCMDOEFtHBOFc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
|
||||
google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
|
||||
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
||||
google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk=
|
||||
google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
||||
@@ -3,7 +3,6 @@ package apps
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/sizes"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/files"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
@@ -41,7 +40,7 @@ func (this *LogWriter) Init() {
|
||||
this.c = make(chan string, 1024)
|
||||
|
||||
// 异步写入文件
|
||||
var maxFileSize = 128 * sizes.M // 文件最大尺寸,超出此尺寸则清空
|
||||
var maxFileSize int64 = 128 << 20 // 文件最大尺寸,超出此尺寸则清空
|
||||
if fp != nil {
|
||||
goman.New(func() {
|
||||
var totalSize int64 = 0
|
||||
|
||||
@@ -6,13 +6,14 @@ import "errors"
|
||||
|
||||
// 常用的几个错误
|
||||
var (
|
||||
ErrNotFound = errors.New("cache not found")
|
||||
ErrFileIsWriting = errors.New("the cache file is updating")
|
||||
ErrInvalidRange = errors.New("invalid range")
|
||||
ErrEntityTooLarge = errors.New("entity too large")
|
||||
ErrWritingUnavailable = errors.New("writing unavailable")
|
||||
ErrWritingQueueFull = errors.New("writing queue full")
|
||||
ErrServerIsBusy = errors.New("server is busy")
|
||||
ErrNotFound = errors.New("cache not found")
|
||||
ErrFileIsWriting = errors.New("the cache file is updating")
|
||||
ErrInvalidRange = errors.New("invalid range")
|
||||
ErrEntityTooLarge = errors.New("entity too large")
|
||||
ErrWritingUnavailable = errors.New("writing unavailable")
|
||||
ErrWritingQueueFull = errors.New("writing queue full")
|
||||
ErrServerIsBusy = errors.New("server is busy")
|
||||
ErrUnexpectedContentLength = errors.New("unexpected content length")
|
||||
)
|
||||
|
||||
// CapacityError 容量错误
|
||||
@@ -34,16 +35,23 @@ func CanIgnoreErr(err error) bool {
|
||||
if err == nil {
|
||||
return true
|
||||
}
|
||||
if err == ErrFileIsWriting ||
|
||||
err == ErrEntityTooLarge ||
|
||||
err == ErrWritingUnavailable ||
|
||||
err == ErrWritingQueueFull ||
|
||||
err == ErrServerIsBusy {
|
||||
if errors.Is(err, ErrFileIsWriting) ||
|
||||
errors.Is(err, ErrEntityTooLarge) ||
|
||||
errors.Is(err, ErrWritingUnavailable) ||
|
||||
errors.Is(err, ErrWritingQueueFull) ||
|
||||
errors.Is(err, ErrServerIsBusy) {
|
||||
return true
|
||||
}
|
||||
_, ok := err.(*CapacityError)
|
||||
if ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
var capacityErr *CapacityError
|
||||
return errors.As(err, &capacityErr)
|
||||
}
|
||||
|
||||
func IsCapacityError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var capacityErr *CapacityError
|
||||
return errors.As(err, &capacityErr)
|
||||
}
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package caches
|
||||
package caches_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||
"github.com/iwind/TeaGo/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCanIgnoreErr(t *testing.T) {
|
||||
a := assert.NewAssertion(t)
|
||||
var a = assert.NewAssertion(t)
|
||||
|
||||
a.IsTrue(CanIgnoreErr(ErrFileIsWriting))
|
||||
a.IsTrue(CanIgnoreErr(NewCapacityError("over capcity")))
|
||||
a.IsFalse(CanIgnoreErr(ErrNotFound))
|
||||
a.IsTrue(caches.CanIgnoreErr(caches.ErrFileIsWriting))
|
||||
a.IsTrue(caches.CanIgnoreErr(fmt.Errorf("error: %w", caches.ErrFileIsWriting)))
|
||||
a.IsTrue(errors.Is(fmt.Errorf("error: %w", caches.ErrFileIsWriting), caches.ErrFileIsWriting))
|
||||
a.IsTrue(errors.Is(caches.ErrFileIsWriting, caches.ErrFileIsWriting))
|
||||
a.IsTrue(caches.CanIgnoreErr(caches.NewCapacityError("over capacity")))
|
||||
a.IsTrue(caches.CanIgnoreErr(fmt.Errorf("error: %w", caches.NewCapacityError("over capacity"))))
|
||||
a.IsFalse(caches.CanIgnoreErr(caches.ErrNotFound))
|
||||
a.IsFalse(caches.CanIgnoreErr(errors.New("test error")))
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package caches
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ItemType = int
|
||||
@@ -16,27 +15,25 @@ const (
|
||||
// 计算当前周
|
||||
// 不要用YW,因为需要计算两周是否临近
|
||||
func currentWeek() int32 {
|
||||
return int32(time.Now().Unix() / 86400)
|
||||
return int32(fasttime.Now().Unix() / 86400)
|
||||
}
|
||||
|
||||
type Item struct {
|
||||
Type ItemType `json:"type"`
|
||||
Key string `json:"key"`
|
||||
ExpiredAt int64 `json:"expiredAt"`
|
||||
StaleAt int64 `json:"staleAt"`
|
||||
HeaderSize int64 `json:"headerSize"`
|
||||
BodySize int64 `json:"bodySize"`
|
||||
MetaSize int64 `json:"metaSize"`
|
||||
Host string `json:"host"` // 主机名
|
||||
ServerId int64 `json:"serverId"` // 服务ID
|
||||
|
||||
Week1Hits int64 `json:"week1Hits"`
|
||||
Week2Hits int64 `json:"week2Hits"`
|
||||
Week int32 `json:"week"`
|
||||
Type ItemType `json:"-"`
|
||||
Key string `json:"1,omitempty"`
|
||||
ExpiresAt int64 `json:"2,omitempty"`
|
||||
StaleAt int64 `json:"3,omitempty"`
|
||||
HeaderSize int64 `json:"-"`
|
||||
BodySize int64 `json:"4,omitempty"`
|
||||
MetaSize int64 `json:"-"`
|
||||
Host string `json:"-"` // 主机名
|
||||
ServerId int64 `json:"5,omitempty"` // 服务ID
|
||||
Week int32 `json:"-"`
|
||||
CreatedAt int64 `json:"6,omitempty"`
|
||||
}
|
||||
|
||||
func (this *Item) IsExpired() bool {
|
||||
return this.ExpiredAt < fasttime.Now().Unix()
|
||||
return this.ExpiresAt < fasttime.Now().Unix()
|
||||
}
|
||||
|
||||
func (this *Item) TotalSize() int64 {
|
||||
@@ -47,20 +44,6 @@ func (this *Item) Size() int64 {
|
||||
return this.HeaderSize + this.BodySize
|
||||
}
|
||||
|
||||
func (this *Item) IncreaseHit(week int32) {
|
||||
if this.Week == week {
|
||||
this.Week2Hits++
|
||||
} else {
|
||||
if week-this.Week == 1 {
|
||||
this.Week1Hits = this.Week2Hits
|
||||
} else {
|
||||
this.Week1Hits = 0
|
||||
}
|
||||
this.Week2Hits = 1
|
||||
this.Week = week
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Item) RequestURI() string {
|
||||
var schemeIndex = strings.Index(this.Key, "://")
|
||||
if schemeIndex <= 0 {
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package caches
|
||||
package caches_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
@@ -11,17 +15,31 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestItem_IncreaseHit(t *testing.T) {
|
||||
var week = currentWeek()
|
||||
func TestItem_Marshal(t *testing.T) {
|
||||
{
|
||||
var item = &caches.Item{}
|
||||
data, err := json.Marshal(item)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(string(data))
|
||||
}
|
||||
|
||||
var item = &Item{}
|
||||
//item.Week = 2704
|
||||
item.Week2Hits = 100
|
||||
item.IncreaseHit(week)
|
||||
t.Log("week:", item.Week, "week1:", item.Week1Hits, "week2:", item.Week2Hits)
|
||||
|
||||
item.IncreaseHit(week)
|
||||
t.Log("week:", item.Week, "week1:", item.Week1Hits, "week2:", item.Week2Hits)
|
||||
{
|
||||
var item = &caches.Item{
|
||||
Type: caches.ItemTypeFile,
|
||||
Key: "https://example.com/index.html",
|
||||
ExpiresAt: fasttime.Now().Unix(),
|
||||
HeaderSize: 1 << 10,
|
||||
BodySize: 1 << 20,
|
||||
MetaSize: 256,
|
||||
}
|
||||
data, err := json.Marshal(item)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(string(data))
|
||||
}
|
||||
}
|
||||
|
||||
func TestItems_Memory(t *testing.T) {
|
||||
@@ -29,9 +47,13 @@ func TestItems_Memory(t *testing.T) {
|
||||
runtime.ReadMemStats(stat)
|
||||
var memory1 = stat.HeapInuse
|
||||
|
||||
var items = []*Item{}
|
||||
for i := 0; i < 10_000_000; i++ {
|
||||
items = append(items, &Item{
|
||||
var items = []*caches.Item{}
|
||||
var count = 100
|
||||
if testutils.IsSingleTesting() {
|
||||
count = 10_000_000
|
||||
}
|
||||
for i := 0; i < count; i++ {
|
||||
items = append(items, &caches.Item{
|
||||
Key: types.String(i),
|
||||
})
|
||||
}
|
||||
@@ -41,18 +63,13 @@ func TestItems_Memory(t *testing.T) {
|
||||
|
||||
t.Log(memory1, memory2, (memory2-memory1)/1024/1024, "M")
|
||||
|
||||
var weekItems = make(map[string]*Item, 10_000_000)
|
||||
|
||||
for _, item := range items {
|
||||
weekItems[item.Key] = item
|
||||
}
|
||||
|
||||
runtime.ReadMemStats(stat)
|
||||
var memory3 = stat.HeapInuse
|
||||
t.Log(memory2, memory3, (memory3-memory2)/1024/1024, "M")
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
t.Log(len(items), len(weekItems))
|
||||
if testutils.IsSingleTesting() {
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func TestItems_Memory2(t *testing.T) {
|
||||
@@ -61,7 +78,12 @@ func TestItems_Memory2(t *testing.T) {
|
||||
var memory1 = stat.HeapInuse
|
||||
|
||||
var items = map[int32]map[string]zero.Zero{}
|
||||
for i := 0; i < 10_000_000; i++ {
|
||||
var count = 100
|
||||
if testutils.IsSingleTesting() {
|
||||
count = 10_000_000
|
||||
}
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
var week = int32((time.Now().Unix() - int64(86400*rands.Int(0, 300))) / (86400 * 7))
|
||||
m, ok := items[week]
|
||||
if !ok {
|
||||
@@ -76,7 +98,9 @@ func TestItems_Memory2(t *testing.T) {
|
||||
|
||||
t.Log(memory1, memory2, (memory2-memory1)/1024/1024, "M")
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
if testutils.IsSingleTesting() {
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
for w, i := range items {
|
||||
t.Log(w, len(i))
|
||||
}
|
||||
@@ -88,7 +112,7 @@ func TestItem_RequestURI(t *testing.T) {
|
||||
"https://goedge.cn:8080/hello/world",
|
||||
"https://goedge.cn/hello/world?v=1&t=123",
|
||||
} {
|
||||
var item = &Item{Key: u}
|
||||
var item = &caches.Item{Key: u}
|
||||
t.Log(u, "=>", item.RequestURI())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,38 +6,33 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/dbs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
memutils "github.com/TeaOSLab/EdgeNode/internal/utils/mem"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type FileListDB struct {
|
||||
type SQLiteFileListDB struct {
|
||||
dbPath string
|
||||
|
||||
readDB *dbs.DB
|
||||
writeDB *dbs.DB
|
||||
|
||||
writeBatch *dbs.Batch
|
||||
|
||||
hashMap *FileListHashMap
|
||||
hashMap *SQLiteFileListHashMap
|
||||
|
||||
itemsTableName string
|
||||
hitsTableName string
|
||||
|
||||
isClosed bool
|
||||
isReady bool
|
||||
isClosed bool // 是否已关闭
|
||||
isReady bool // 是否已完成初始化
|
||||
hashMapIsLoaded bool // Hash是否已加载
|
||||
|
||||
// cacheItems
|
||||
existsByHashStmt *dbs.Stmt // 根据hash检查是否存在
|
||||
@@ -52,37 +47,31 @@ type FileListDB struct {
|
||||
deleteByHashStmt *dbs.Stmt // 根据hash删除数据
|
||||
deleteByHashSQL string
|
||||
|
||||
statStmt *dbs.Stmt // 统计
|
||||
purgeStmt *dbs.Stmt // 清理
|
||||
deleteAllStmt *dbs.Stmt // 删除所有数据
|
||||
listOlderItemsStmt *dbs.Stmt // 读取较早存储的缓存
|
||||
updateAccessWeekSQL string // 修改访问日期
|
||||
|
||||
// hits
|
||||
insertHitSQL string // 写入数据
|
||||
increaseHitSQL string // 增加点击量
|
||||
deleteHitByHashSQL string // 根据hash删除数据
|
||||
statStmt *dbs.Stmt // 统计
|
||||
purgeStmt *dbs.Stmt // 清理
|
||||
deleteAllStmt *dbs.Stmt // 删除所有数据
|
||||
listOlderItemsStmt *dbs.Stmt // 读取较早存储的缓存
|
||||
}
|
||||
|
||||
func NewFileListDB() *FileListDB {
|
||||
return &FileListDB{
|
||||
hashMap: NewFileListHashMap(),
|
||||
func NewSQLiteFileListDB() *SQLiteFileListDB {
|
||||
return &SQLiteFileListDB{
|
||||
hashMap: NewSQLiteFileListHashMap(),
|
||||
}
|
||||
}
|
||||
|
||||
func (this *FileListDB) Open(dbPath string) error {
|
||||
func (this *SQLiteFileListDB) Open(dbPath string) error {
|
||||
this.dbPath = dbPath
|
||||
|
||||
// 动态调整Cache值
|
||||
var cacheSize = 32000
|
||||
var memoryGB = utils.SystemMemoryGB()
|
||||
if memoryGB >= 8 {
|
||||
cacheSize += 32000 * memoryGB / 8
|
||||
var cacheSize = 512
|
||||
var memoryGB = memutils.SystemMemoryGB()
|
||||
if memoryGB >= 1 {
|
||||
cacheSize = 256 * memoryGB
|
||||
}
|
||||
|
||||
// write db
|
||||
// 这里不能加 EXCLUSIVE 锁,不然异步事务可能会失败
|
||||
writeDB, err := dbs.OpenWriter("file:" + dbPath + "?cache=private&mode=rwc&_journal_mode=WAL&_sync=OFF&_cache_size=" + types.String(cacheSize) + "&_secure_delete=FAST")
|
||||
writeDB, err := dbs.OpenWriter("file:" + dbPath + "?cache=private&mode=rwc&_journal_mode=WAL&_sync=" + dbs.SyncMode + "&_cache_size=" + types.String(cacheSize) + "&_secure_delete=FAST")
|
||||
if err != nil {
|
||||
return fmt.Errorf("open write database failed: %w", err)
|
||||
}
|
||||
@@ -109,17 +98,7 @@ func (this *FileListDB) Open(dbPath string) error {
|
||||
}
|
||||
}
|
||||
|
||||
this.writeBatch = dbs.NewBatch(writeDB, 4)
|
||||
this.writeBatch.OnFail(func(err error) {
|
||||
remotelogs.Warn("LIST_FILE_DB", "run batch failed: "+err.Error()+" ("+filepath.Base(this.dbPath)+")")
|
||||
})
|
||||
|
||||
goman.New(func() {
|
||||
this.writeBatch.Exec()
|
||||
})
|
||||
|
||||
if teaconst.EnableDBStat {
|
||||
this.writeBatch.EnableStat(true)
|
||||
this.writeDB.EnableStat(true)
|
||||
}
|
||||
|
||||
@@ -140,9 +119,8 @@ func (this *FileListDB) Open(dbPath string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *FileListDB) Init() error {
|
||||
func (this *SQLiteFileListDB) Init() error {
|
||||
this.itemsTableName = "cacheItems"
|
||||
this.hitsTableName = "hits"
|
||||
|
||||
// 创建
|
||||
var err = this.initTables(1)
|
||||
@@ -156,7 +134,7 @@ func (this *FileListDB) Init() error {
|
||||
return err
|
||||
}
|
||||
|
||||
this.insertSQL = `INSERT INTO "` + this.itemsTableName + `" ("hash", "key", "headerSize", "bodySize", "metaSize", "expiredAt", "staleAt", "host", "serverId", "createdAt", "accessWeek") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
this.insertSQL = `INSERT INTO "` + this.itemsTableName + `" ("hash", "key", "headerSize", "bodySize", "metaSize", "expiredAt", "staleAt", "host", "serverId", "createdAt") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
this.insertStmt, err = this.writeDB.Prepare(this.insertSQL)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -167,7 +145,7 @@ func (this *FileListDB) Init() error {
|
||||
return err
|
||||
}
|
||||
|
||||
this.selectHashListStmt, err = this.readDB.Prepare(`SELECT "id", "hash" FROM "` + this.itemsTableName + `" WHERE id>:id ORDER BY id ASC LIMIT 2000`)
|
||||
this.selectHashListStmt, err = this.readDB.Prepare(`SELECT "id", "hash" FROM "` + this.itemsTableName + `" WHERE id>? ORDER BY id ASC LIMIT 2000`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -193,51 +171,24 @@ func (this *FileListDB) Init() error {
|
||||
return err
|
||||
}
|
||||
|
||||
this.listOlderItemsStmt, err = this.readDB.Prepare(`SELECT "hash" FROM "` + this.itemsTableName + `" ORDER BY "accessWeek" ASC, "id" ASC LIMIT ?`)
|
||||
this.listOlderItemsStmt, err = this.readDB.Prepare(`SELECT "hash" FROM "` + this.itemsTableName + `" ORDER BY "id" ASC LIMIT ?`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
this.updateAccessWeekSQL = `UPDATE "` + this.itemsTableName + `" SET "accessWeek"=? WHERE "hash"=?`
|
||||
|
||||
this.insertHitSQL = `INSERT INTO "` + this.hitsTableName + `" ("hash", "week2Hits", "week") VALUES (?, 1, ?)`
|
||||
|
||||
this.increaseHitSQL = `INSERT INTO "` + this.hitsTableName + `" ("hash", "week2Hits", "week") VALUES (?, 1, ?) ON CONFLICT("hash") DO UPDATE SET "week1Hits"=IIF("week"=?, "week1Hits", "week2Hits"), "week2Hits"=IIF("week"=?, "week2Hits"+1, 1), "week"=?`
|
||||
|
||||
this.deleteHitByHashSQL = `DELETE FROM "` + this.hitsTableName + `" WHERE "hash"=?`
|
||||
|
||||
this.isReady = true
|
||||
|
||||
// 加载HashMap
|
||||
go func() {
|
||||
err := this.hashMap.Load(this)
|
||||
if err != nil {
|
||||
remotelogs.Error("LIST_FILE_DB", "load hash map failed: "+err.Error()+"(file: "+this.dbPath+")")
|
||||
|
||||
// 自动修复错误
|
||||
// TODO 将来希望能尽可能恢复以往数据库中的内容
|
||||
if strings.Contains(err.Error(), "database is closed") || strings.Contains(err.Error(), "database disk image is malformed") {
|
||||
_ = this.Close()
|
||||
this.deleteDB()
|
||||
remotelogs.Println("LIST_FILE_DB", "recreating the database (file:"+this.dbPath+") ...")
|
||||
err = this.Open(this.dbPath)
|
||||
if err != nil {
|
||||
remotelogs.Error("LIST_FILE_DB", "recreate the database failed: "+err.Error()+" (file:"+this.dbPath+")")
|
||||
} else {
|
||||
_ = this.Init()
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
go this.loadHashMap()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *FileListDB) IsReady() bool {
|
||||
func (this *SQLiteFileListDB) IsReady() bool {
|
||||
return this.isReady
|
||||
}
|
||||
|
||||
func (this *FileListDB) Total() (int64, error) {
|
||||
func (this *SQLiteFileListDB) Total() (int64, error) {
|
||||
// 读取总数量
|
||||
var row = this.readDB.QueryRow(`SELECT COUNT(*) FROM "` + this.itemsTableName + `"`)
|
||||
if row.Err() != nil {
|
||||
@@ -248,14 +199,14 @@ func (this *FileListDB) Total() (int64, error) {
|
||||
return total, err
|
||||
}
|
||||
|
||||
func (this *FileListDB) AddSync(hash string, item *Item) error {
|
||||
func (this *SQLiteFileListDB) AddSync(hash string, item *Item) error {
|
||||
this.hashMap.Add(hash)
|
||||
|
||||
if item.StaleAt == 0 {
|
||||
item.StaleAt = item.ExpiredAt
|
||||
item.StaleAt = item.ExpiresAt
|
||||
}
|
||||
|
||||
_, err := this.insertStmt.Exec(hash, item.Key, item.HeaderSize, item.BodySize, item.MetaSize, item.ExpiredAt, item.StaleAt, item.Host, item.ServerId, fasttime.Now().Unix(), timeutil.Format("YW"))
|
||||
_, err := this.insertStmt.Exec(hash, item.Key, item.HeaderSize, item.BodySize, item.MetaSize, item.ExpiresAt, item.StaleAt, item.Host, item.ServerId, fasttime.Now().Unix())
|
||||
if err != nil {
|
||||
return this.WrapError(err)
|
||||
}
|
||||
@@ -263,7 +214,7 @@ func (this *FileListDB) AddSync(hash string, item *Item) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *FileListDB) DeleteSync(hash string) error {
|
||||
func (this *SQLiteFileListDB) DeleteSync(hash string) error {
|
||||
this.hashMap.Delete(hash)
|
||||
|
||||
_, err := this.deleteByHashStmt.Exec(hash)
|
||||
@@ -273,7 +224,7 @@ func (this *FileListDB) DeleteSync(hash string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *FileListDB) ListExpiredItems(count int) (hashList []string, err error) {
|
||||
func (this *SQLiteFileListDB) ListExpiredItems(count int) (hashList []string, err error) {
|
||||
if !this.isReady {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -301,7 +252,7 @@ func (this *FileListDB) ListExpiredItems(count int) (hashList []string, err erro
|
||||
return hashList, nil
|
||||
}
|
||||
|
||||
func (this *FileListDB) ListLFUItems(count int) (hashList []string, err error) {
|
||||
func (this *SQLiteFileListDB) ListLFUItems(count int) (hashList []string, err error) {
|
||||
if !this.isReady {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -329,7 +280,7 @@ func (this *FileListDB) ListLFUItems(count int) (hashList []string, err error) {
|
||||
return hashList, nil
|
||||
}
|
||||
|
||||
func (this *FileListDB) ListHashes(lastId int64) (hashList []string, maxId int64, err error) {
|
||||
func (this *SQLiteFileListDB) ListHashes(lastId int64) (hashList []string, maxId int64, err error) {
|
||||
rows, err := this.selectHashListStmt.Query(lastId)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
@@ -350,27 +301,19 @@ func (this *FileListDB) ListHashes(lastId int64) (hashList []string, maxId int64
|
||||
return
|
||||
}
|
||||
|
||||
func (this *FileListDB) IncreaseHitAsync(hash string) error {
|
||||
var week = timeutil.Format("YW")
|
||||
this.writeBatch.Add(this.increaseHitSQL, hash, week, week, week, week)
|
||||
this.writeBatch.Add(this.updateAccessWeekSQL, week, hash)
|
||||
func (this *SQLiteFileListDB) IncreaseHitAsync(hash string) error {
|
||||
// do nothing
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *FileListDB) DeleteHitAsync(hash string) error {
|
||||
this.writeBatch.Add(this.deleteHitByHashSQL, hash)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *FileListDB) CleanPrefix(prefix string) error {
|
||||
func (this *SQLiteFileListDB) CleanPrefix(prefix string) error {
|
||||
if !this.isReady {
|
||||
return nil
|
||||
}
|
||||
var count = int64(10000)
|
||||
var staleLife = 600 // TODO 需要可以设置
|
||||
var unixTime = fasttime.Now().Unix() // 只删除当前的,不删除新的
|
||||
for {
|
||||
result, err := this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET expiredAt=0,staleAt=? WHERE id IN (SELECT id FROM "`+this.itemsTableName+`" WHERE expiredAt>0 AND createdAt<=? AND INSTR("key", ?)=1 LIMIT `+types.String(count)+`)`, unixTime+int64(staleLife), unixTime, prefix)
|
||||
result, err := this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET expiredAt=0,staleAt=? WHERE id IN (SELECT id FROM "`+this.itemsTableName+`" WHERE expiredAt>0 AND createdAt<=? AND INSTR("key", ?)=1 LIMIT `+types.String(count)+`)`, unixTime+DefaultStaleCacheSeconds, unixTime, prefix)
|
||||
if err != nil {
|
||||
return this.WrapError(err)
|
||||
}
|
||||
@@ -384,7 +327,7 @@ func (this *FileListDB) CleanPrefix(prefix string) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (this *FileListDB) CleanMatchKey(key string) error {
|
||||
func (this *SQLiteFileListDB) CleanMatchKey(key string) error {
|
||||
if !this.isReady {
|
||||
return nil
|
||||
}
|
||||
@@ -414,15 +357,14 @@ func (this *FileListDB) CleanMatchKey(key string) error {
|
||||
queryKey = strings.Replace(queryKey, "*", "%", 1)
|
||||
|
||||
// TODO 检查大批量数据下的操作性能
|
||||
var staleLife = 600 // TODO 需要可以设置
|
||||
var unixTime = fasttime.Now().Unix() // 只删除当前的,不删除新的
|
||||
|
||||
_, err = this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET "expiredAt"=0, "staleAt"=? WHERE "host" GLOB ? AND "host" NOT GLOB ? AND "key" LIKE ? ESCAPE '\'`, unixTime+int64(staleLife), host, "*."+host, queryKey)
|
||||
_, err = this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET "expiredAt"=0, "staleAt"=? WHERE "host" GLOB ? AND "host" NOT GLOB ? AND "key" LIKE ? ESCAPE '\'`, unixTime+DefaultStaleCacheSeconds, host, "*."+host, queryKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET "expiredAt"=0, "staleAt"=? WHERE "host" GLOB ? AND "host" NOT GLOB ? AND "key" LIKE ? ESCAPE '\'`, unixTime+int64(staleLife), host, "*."+host, queryKey+SuffixAll+"%")
|
||||
_, err = this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET "expiredAt"=0, "staleAt"=? WHERE "host" GLOB ? AND "host" NOT GLOB ? AND "key" LIKE ? ESCAPE '\'`, unixTime+DefaultStaleCacheSeconds, host, "*."+host, queryKey+SuffixAll+"%")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -430,7 +372,7 @@ func (this *FileListDB) CleanMatchKey(key string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *FileListDB) CleanMatchPrefix(prefix string) error {
|
||||
func (this *SQLiteFileListDB) CleanMatchPrefix(prefix string) error {
|
||||
if !this.isReady {
|
||||
return nil
|
||||
}
|
||||
@@ -456,14 +398,13 @@ func (this *FileListDB) CleanMatchPrefix(prefix string) error {
|
||||
queryPrefix += "%"
|
||||
|
||||
// TODO 检查大批量数据下的操作性能
|
||||
var staleLife = 600 // TODO 需要可以设置
|
||||
var unixTime = fasttime.Now().Unix() // 只删除当前的,不删除新的
|
||||
|
||||
_, err = this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET "expiredAt"=0, "staleAt"=? WHERE "host" GLOB ? AND "host" NOT GLOB ? AND "key" LIKE ? ESCAPE '\'`, unixTime+int64(staleLife), host, "*."+host, queryPrefix)
|
||||
_, err = this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET "expiredAt"=0, "staleAt"=? WHERE "host" GLOB ? AND "host" NOT GLOB ? AND "key" LIKE ? ESCAPE '\'`, unixTime+DefaultStaleCacheSeconds, host, "*."+host, queryPrefix)
|
||||
return err
|
||||
}
|
||||
|
||||
func (this *FileListDB) CleanAll() error {
|
||||
func (this *SQLiteFileListDB) CleanAll() error {
|
||||
if !this.isReady {
|
||||
return nil
|
||||
}
|
||||
@@ -478,7 +419,7 @@ func (this *FileListDB) CleanAll() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *FileListDB) Close() error {
|
||||
func (this *SQLiteFileListDB) Close() error {
|
||||
if this.isClosed {
|
||||
return nil
|
||||
}
|
||||
@@ -536,15 +477,19 @@ func (this *FileListDB) Close() error {
|
||||
return errors.New("close database failed: " + strings.Join(errStrings, ", "))
|
||||
}
|
||||
|
||||
func (this *FileListDB) WrapError(err error) error {
|
||||
func (this *SQLiteFileListDB) WrapError(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("%w (file: %s)", err, this.dbPath)
|
||||
}
|
||||
|
||||
func (this *SQLiteFileListDB) HashMapIsLoaded() bool {
|
||||
return this.hashMapIsLoaded
|
||||
}
|
||||
|
||||
// 初始化
|
||||
func (this *FileListDB) initTables(times int) error {
|
||||
func (this *SQLiteFileListDB) initTables(times int) error {
|
||||
{
|
||||
// expiredAt - 过期时间,用来判断有无过期
|
||||
// staleAt - 过时缓存最大时间,用来清理缓存
|
||||
@@ -561,8 +506,7 @@ func (this *FileListDB) initTables(times int) error {
|
||||
"staleAt" integer DEFAULT 0,
|
||||
"createdAt" integer DEFAULT 0,
|
||||
"host" varchar(128),
|
||||
"serverId" integer,
|
||||
"accessWeek" varchar(6)
|
||||
"serverId" integer
|
||||
);
|
||||
|
||||
DROP INDEX IF EXISTS "createdAt";
|
||||
@@ -578,8 +522,6 @@ CREATE INDEX IF NOT EXISTS "hash"
|
||||
ON "` + this.itemsTableName + `" (
|
||||
"hash" ASC
|
||||
);
|
||||
|
||||
ALTER TABLE "cacheItems" ADD "accessWeek" varchar(6);
|
||||
`)
|
||||
|
||||
if err != nil {
|
||||
@@ -603,38 +545,15 @@ ALTER TABLE "cacheItems" ADD "accessWeek" varchar(6);
|
||||
}
|
||||
}
|
||||
|
||||
// 删除hits表
|
||||
{
|
||||
_, err := this.writeDB.Exec(`CREATE TABLE IF NOT EXISTS "` + this.hitsTableName + `" (
|
||||
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"hash" varchar(32),
|
||||
"week1Hits" integer DEFAULT 0,
|
||||
"week2Hits" integer DEFAULT 0,
|
||||
"week" varchar(6)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "hits_hash"
|
||||
ON "` + this.hitsTableName + `" (
|
||||
"hash" ASC
|
||||
);
|
||||
`)
|
||||
if err != nil {
|
||||
// 尝试删除重建
|
||||
if times < 3 {
|
||||
_, dropErr := this.writeDB.Exec(`DROP TABLE "` + this.hitsTableName + `"`)
|
||||
if dropErr == nil {
|
||||
return this.initTables(times + 1)
|
||||
}
|
||||
return this.WrapError(err)
|
||||
}
|
||||
|
||||
return this.WrapError(err)
|
||||
}
|
||||
_, _ = this.writeDB.Exec(`DROP TABLE "hits"`)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *FileListDB) listOlderItems(count int) (hashList []string, err error) {
|
||||
func (this *SQLiteFileListDB) listOlderItems(count int) (hashList []string, err error) {
|
||||
rows, err := this.listOlderItemsStmt.Query(count)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -655,7 +574,7 @@ func (this *FileListDB) listOlderItems(count int) (hashList []string, err error)
|
||||
return hashList, nil
|
||||
}
|
||||
|
||||
func (this *FileListDB) shouldRecover() bool {
|
||||
func (this *SQLiteFileListDB) shouldRecover() bool {
|
||||
result, err := this.writeDB.Query("pragma integrity_check;")
|
||||
if err != nil {
|
||||
logs.Println(result)
|
||||
@@ -673,8 +592,35 @@ func (this *FileListDB) shouldRecover() bool {
|
||||
}
|
||||
|
||||
// 删除数据库文件
|
||||
func (this *FileListDB) deleteDB() {
|
||||
func (this *SQLiteFileListDB) deleteDB() {
|
||||
_ = os.Remove(this.dbPath)
|
||||
_ = os.Remove(this.dbPath + "-shm")
|
||||
_ = os.Remove(this.dbPath + "-wal")
|
||||
}
|
||||
|
||||
// 加载Hash列表
|
||||
func (this *SQLiteFileListDB) loadHashMap() {
|
||||
this.hashMapIsLoaded = false
|
||||
|
||||
err := this.hashMap.Load(this)
|
||||
if err != nil {
|
||||
remotelogs.Error("LIST_FILE_DB", "load hash map failed: "+err.Error()+"(file: "+this.dbPath+")")
|
||||
|
||||
// 自动修复错误
|
||||
// TODO 将来希望能尽可能恢复以往数据库中的内容
|
||||
if strings.Contains(err.Error(), "database is closed") || strings.Contains(err.Error(), "database disk image is malformed") {
|
||||
_ = this.Close()
|
||||
this.deleteDB()
|
||||
remotelogs.Println("LIST_FILE_DB", "recreating the database (file:"+this.dbPath+") ...")
|
||||
err = this.Open(this.dbPath)
|
||||
if err != nil {
|
||||
remotelogs.Error("LIST_FILE_DB", "recreate the database failed: "+err.Error()+" (file:"+this.dbPath+")")
|
||||
} else {
|
||||
_ = this.Init()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
this.hashMapIsLoaded = true
|
||||
}
|
||||
@@ -4,14 +4,21 @@ package caches_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestFileListDB_ListLFUItems(t *testing.T) {
|
||||
var db = caches.NewFileListDB()
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var db = caches.NewSQLiteFileListDB()
|
||||
|
||||
defer func() {
|
||||
_ = db.Close()
|
||||
@@ -34,34 +41,12 @@ func TestFileListDB_ListLFUItems(t *testing.T) {
|
||||
t.Log("[", len(hashList), "]", hashList)
|
||||
}
|
||||
|
||||
func TestFileListDB_IncreaseHitAsync(t *testing.T) {
|
||||
var db = caches.NewFileListDB()
|
||||
|
||||
defer func() {
|
||||
_ = db.Close()
|
||||
}()
|
||||
|
||||
err := db.Open(Tea.Root + "/data/cache-db-large.db")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.IncreaseHitAsync("4598e5231ba47d6ec7aa9ea640ff2eaf")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// wait transaction
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
func TestFileListDB_CleanMatchKey(t *testing.T) {
|
||||
var db = caches.NewFileListDB()
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var db = caches.NewSQLiteFileListDB()
|
||||
|
||||
defer func() {
|
||||
_ = db.Close()
|
||||
@@ -89,7 +74,11 @@ func TestFileListDB_CleanMatchKey(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileListDB_CleanMatchPrefix(t *testing.T) {
|
||||
var db = caches.NewFileListDB()
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var db = caches.NewSQLiteFileListDB()
|
||||
|
||||
defer func() {
|
||||
_ = db.Close()
|
||||
@@ -115,3 +104,67 @@ func TestFileListDB_CleanMatchPrefix(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileListDB_Memory(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var db = caches.NewSQLiteFileListDB()
|
||||
|
||||
defer func() {
|
||||
_ = db.Close()
|
||||
}()
|
||||
|
||||
err := db.Open(Tea.Root + "/data/cache-index/p1/db-0.db")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Log(db.Total())
|
||||
|
||||
// load hashes
|
||||
var maxId int64
|
||||
var hashList []string
|
||||
var before = time.Now()
|
||||
for i := 0; i < 1_000; i++ {
|
||||
hashList, maxId, err = db.ListHashes(maxId)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(hashList) == 0 {
|
||||
t.Log("hashes loaded", time.Since(before).Seconds()*1000, "ms")
|
||||
break
|
||||
}
|
||||
if i%100 == 0 {
|
||||
t.Log(i)
|
||||
}
|
||||
}
|
||||
|
||||
runtime.GC()
|
||||
debug.FreeOSMemory()
|
||||
|
||||
//time.Sleep(600 * time.Second)
|
||||
|
||||
for i := 0; i < 1_000; i++ {
|
||||
_, err = db.ListLFUItems(5000)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if i%100 == 0 {
|
||||
t.Log(i)
|
||||
}
|
||||
}
|
||||
|
||||
t.Log("loaded")
|
||||
|
||||
runtime.GC()
|
||||
debug.FreeOSMemory()
|
||||
|
||||
time.Sleep(600 * time.Second)
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package caches
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
||||
"math/big"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// FileListHashMap 文件Hash列表
|
||||
type FileListHashMap struct {
|
||||
m map[uint64]zero.Zero
|
||||
|
||||
locker sync.RWMutex
|
||||
isAvailable bool
|
||||
isReady bool
|
||||
}
|
||||
|
||||
func NewFileListHashMap() *FileListHashMap {
|
||||
return &FileListHashMap{
|
||||
m: map[uint64]zero.Zero{},
|
||||
isAvailable: false,
|
||||
isReady: false,
|
||||
}
|
||||
}
|
||||
|
||||
func (this *FileListHashMap) Load(db *FileListDB) error {
|
||||
// 如果系统内存过小,我们不缓存
|
||||
if utils.SystemMemoryGB() < 3 {
|
||||
return nil
|
||||
}
|
||||
|
||||
this.isAvailable = true
|
||||
|
||||
var lastId int64
|
||||
for {
|
||||
hashList, maxId, err := db.ListHashes(lastId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(hashList) == 0 {
|
||||
break
|
||||
}
|
||||
this.AddHashes(hashList)
|
||||
lastId = maxId
|
||||
}
|
||||
|
||||
this.isReady = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *FileListHashMap) Add(hash string) {
|
||||
if !this.isAvailable {
|
||||
return
|
||||
}
|
||||
|
||||
this.locker.Lock()
|
||||
this.m[this.bigInt(hash)] = zero.New()
|
||||
this.locker.Unlock()
|
||||
}
|
||||
|
||||
func (this *FileListHashMap) AddHashes(hashes []string) {
|
||||
if !this.isAvailable {
|
||||
return
|
||||
}
|
||||
|
||||
this.locker.Lock()
|
||||
for _, hash := range hashes {
|
||||
this.m[this.bigInt(hash)] = zero.New()
|
||||
}
|
||||
this.locker.Unlock()
|
||||
}
|
||||
|
||||
func (this *FileListHashMap) Delete(hash string) {
|
||||
if !this.isAvailable {
|
||||
return
|
||||
}
|
||||
|
||||
this.locker.Lock()
|
||||
delete(this.m, this.bigInt(hash))
|
||||
this.locker.Unlock()
|
||||
}
|
||||
|
||||
func (this *FileListHashMap) Exist(hash string) bool {
|
||||
if !this.isAvailable {
|
||||
return true
|
||||
}
|
||||
if !this.isReady {
|
||||
// 只有完全Ready时才能判断是否为false
|
||||
return true
|
||||
}
|
||||
this.locker.RLock()
|
||||
_, ok := this.m[this.bigInt(hash)]
|
||||
this.locker.RUnlock()
|
||||
return ok
|
||||
}
|
||||
|
||||
func (this *FileListHashMap) Clean() {
|
||||
this.locker.Lock()
|
||||
this.m = map[uint64]zero.Zero{}
|
||||
this.locker.Unlock()
|
||||
}
|
||||
|
||||
func (this *FileListHashMap) IsReady() bool {
|
||||
return this.isReady
|
||||
}
|
||||
|
||||
func (this *FileListHashMap) Len() int {
|
||||
this.locker.Lock()
|
||||
defer this.locker.Unlock()
|
||||
return len(this.m)
|
||||
}
|
||||
|
||||
func (this *FileListHashMap) bigInt(hash string) uint64 {
|
||||
var bigInt = big.NewInt(0)
|
||||
bigInt.SetString(hash, 16)
|
||||
return bigInt.Uint64()
|
||||
}
|
||||
183
internal/caches/list_file_hash_map_sqlite.go
Normal file
183
internal/caches/list_file_hash_map_sqlite.go
Normal file
@@ -0,0 +1,183 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package caches
|
||||
|
||||
import (
|
||||
memutils "github.com/TeaOSLab/EdgeNode/internal/utils/mem"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
||||
"math/big"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const HashMapSharding = 31
|
||||
|
||||
var bigIntPool = sync.Pool{
|
||||
New: func() any {
|
||||
return big.NewInt(0)
|
||||
},
|
||||
}
|
||||
|
||||
// SQLiteFileListHashMap 文件Hash列表
|
||||
type SQLiteFileListHashMap struct {
|
||||
m []map[uint64]zero.Zero
|
||||
|
||||
lockers []*sync.RWMutex
|
||||
|
||||
isAvailable bool
|
||||
isReady bool
|
||||
}
|
||||
|
||||
func NewSQLiteFileListHashMap() *SQLiteFileListHashMap {
|
||||
var m = make([]map[uint64]zero.Zero, HashMapSharding)
|
||||
var lockers = make([]*sync.RWMutex, HashMapSharding)
|
||||
|
||||
for i := 0; i < HashMapSharding; i++ {
|
||||
m[i] = map[uint64]zero.Zero{}
|
||||
lockers[i] = &sync.RWMutex{}
|
||||
}
|
||||
|
||||
return &SQLiteFileListHashMap{
|
||||
m: m,
|
||||
lockers: lockers,
|
||||
isAvailable: false,
|
||||
isReady: false,
|
||||
}
|
||||
}
|
||||
|
||||
func (this *SQLiteFileListHashMap) Load(db *SQLiteFileListDB) error {
|
||||
// 如果系统内存过小,我们不缓存
|
||||
if memutils.SystemMemoryGB() < 3 {
|
||||
return nil
|
||||
}
|
||||
|
||||
this.isAvailable = true
|
||||
|
||||
var lastId int64
|
||||
var maxLoops = 50_000
|
||||
for {
|
||||
hashList, maxId, err := db.ListHashes(lastId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(hashList) == 0 {
|
||||
break
|
||||
}
|
||||
this.AddHashes(hashList)
|
||||
lastId = maxId
|
||||
|
||||
maxLoops--
|
||||
if maxLoops <= 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
this.isReady = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *SQLiteFileListHashMap) Add(hash string) {
|
||||
if !this.isAvailable {
|
||||
return
|
||||
}
|
||||
|
||||
hashInt, index := this.bigInt(hash)
|
||||
|
||||
this.lockers[index].Lock()
|
||||
this.m[index][hashInt] = zero.New()
|
||||
this.lockers[index].Unlock()
|
||||
}
|
||||
|
||||
func (this *SQLiteFileListHashMap) AddHashes(hashes []string) {
|
||||
if !this.isAvailable {
|
||||
return
|
||||
}
|
||||
|
||||
for _, hash := range hashes {
|
||||
hashInt, index := this.bigInt(hash)
|
||||
this.lockers[index].Lock()
|
||||
this.m[index][hashInt] = zero.New()
|
||||
this.lockers[index].Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (this *SQLiteFileListHashMap) Delete(hash string) {
|
||||
if !this.isAvailable {
|
||||
return
|
||||
}
|
||||
|
||||
hashInt, index := this.bigInt(hash)
|
||||
this.lockers[index].Lock()
|
||||
delete(this.m[index], hashInt)
|
||||
this.lockers[index].Unlock()
|
||||
}
|
||||
|
||||
func (this *SQLiteFileListHashMap) Exist(hash string) bool {
|
||||
if !this.isAvailable {
|
||||
return true
|
||||
}
|
||||
if !this.isReady {
|
||||
// 只有完全Ready时才能判断是否为false
|
||||
return true
|
||||
}
|
||||
|
||||
hashInt, index := this.bigInt(hash)
|
||||
|
||||
this.lockers[index].RLock()
|
||||
_, ok := this.m[index][hashInt]
|
||||
this.lockers[index].RUnlock()
|
||||
return ok
|
||||
}
|
||||
|
||||
func (this *SQLiteFileListHashMap) Clean() {
|
||||
for i := 0; i < HashMapSharding; i++ {
|
||||
this.lockers[i].Lock()
|
||||
}
|
||||
|
||||
// 这里不能简单清空 this.m ,避免导致别的数据无法写入 map 而产生 panic
|
||||
for i := 0; i < HashMapSharding; i++ {
|
||||
this.m[i] = map[uint64]zero.Zero{}
|
||||
}
|
||||
|
||||
for i := HashMapSharding - 1; i >= 0; i-- {
|
||||
this.lockers[i].Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (this *SQLiteFileListHashMap) IsReady() bool {
|
||||
return this.isReady
|
||||
}
|
||||
|
||||
func (this *SQLiteFileListHashMap) Len() int {
|
||||
for i := 0; i < HashMapSharding; i++ {
|
||||
this.lockers[i].Lock()
|
||||
}
|
||||
|
||||
var count = 0
|
||||
for _, shard := range this.m {
|
||||
count += len(shard)
|
||||
}
|
||||
|
||||
for i := HashMapSharding - 1; i >= 0; i-- {
|
||||
this.lockers[i].Unlock()
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
|
||||
func (this *SQLiteFileListHashMap) SetIsAvailable(isAvailable bool) {
|
||||
this.isAvailable = isAvailable
|
||||
}
|
||||
|
||||
func (this *SQLiteFileListHashMap) SetIsReady(isReady bool) {
|
||||
this.isReady = isReady
|
||||
}
|
||||
|
||||
func (this *SQLiteFileListHashMap) bigInt(hash string) (hashInt uint64, index int) {
|
||||
var bigInt = bigIntPool.Get().(*big.Int)
|
||||
bigInt.SetString(hash, 16)
|
||||
hashInt = bigInt.Uint64()
|
||||
bigIntPool.Put(bigInt)
|
||||
|
||||
index = int(hashInt % HashMapSharding)
|
||||
return
|
||||
}
|
||||
@@ -4,8 +4,11 @@ package caches_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/assert"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
"math/big"
|
||||
@@ -19,16 +22,20 @@ func TestFileListHashMap_Memory(t *testing.T) {
|
||||
var stat1 = &runtime.MemStats{}
|
||||
runtime.ReadMemStats(stat1)
|
||||
|
||||
var m = caches.NewFileListHashMap()
|
||||
var m = caches.NewSQLiteFileListHashMap()
|
||||
m.SetIsAvailable(true)
|
||||
|
||||
for i := 0; i < 1_000_000; i++ {
|
||||
m.Add(stringutil.Md5(types.String(i)))
|
||||
}
|
||||
|
||||
t.Log("added:", m.Len(), "hashes")
|
||||
|
||||
var stat2 = &runtime.MemStats{}
|
||||
runtime.ReadMemStats(stat2)
|
||||
|
||||
t.Log("ready", (stat2.Alloc-stat1.Alloc)/1024/1024, "M")
|
||||
t.Log("remains:", m.Len(), "hashes")
|
||||
}
|
||||
|
||||
func TestFileListHashMap_Memory2(t *testing.T) {
|
||||
@@ -48,17 +55,43 @@ func TestFileListHashMap_Memory2(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileListHashMap_BigInt(t *testing.T) {
|
||||
var bigInt = big.NewInt(0)
|
||||
|
||||
for _, s := range []string{"1", "2", "3", "123", "123456"} {
|
||||
var hash = stringutil.Md5(s)
|
||||
|
||||
var bigInt = big.NewInt(0)
|
||||
var bigInt1 = big.NewInt(0)
|
||||
bigInt1.SetString(hash, 16)
|
||||
|
||||
bigInt.SetString(hash, 16)
|
||||
t.Log(s, "=>", bigInt.Uint64(), "hash:", hash, "format:", strconv.FormatUint(bigInt.Uint64(), 16))
|
||||
|
||||
t.Log(s, "=>", bigInt1.Uint64(), "hash:", hash, "format:", strconv.FormatUint(bigInt1.Uint64(), 16), strconv.FormatUint(bigInt.Uint64(), 16))
|
||||
|
||||
if strconv.FormatUint(bigInt1.Uint64(), 16) != strconv.FormatUint(bigInt.Uint64(), 16) {
|
||||
t.Fatal("not equal")
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < 1_000_000; i++ {
|
||||
var hash = stringutil.Md5(types.String(i))
|
||||
|
||||
var bigInt1 = big.NewInt(0)
|
||||
bigInt1.SetString(hash, 16)
|
||||
|
||||
bigInt.SetString(hash, 16)
|
||||
|
||||
if bigInt1.Uint64() != bigInt.Uint64() {
|
||||
t.Fatal(i, "not equal")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileListHashMap_Load(t *testing.T) {
|
||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1").(*caches.FileList)
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var list = caches.NewSQLiteFileList(Tea.Root + "/data/cache-index/p1").(*caches.SQLiteFileList)
|
||||
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
@@ -69,7 +102,7 @@ func TestFileListHashMap_Load(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var m = caches.NewFileListHashMap()
|
||||
var m = caches.NewSQLiteFileListHashMap()
|
||||
var before = time.Now()
|
||||
var db = list.GetDB("abc")
|
||||
err = m.Load(db)
|
||||
@@ -85,6 +118,25 @@ func TestFileListHashMap_Load(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileListHashMap_Delete(t *testing.T) {
|
||||
var a = assert.NewAssertion(t)
|
||||
|
||||
var m = caches.NewSQLiteFileListHashMap()
|
||||
m.SetIsReady(true)
|
||||
m.SetIsAvailable(true)
|
||||
m.Add("a")
|
||||
a.IsTrue(m.Len() == 1)
|
||||
m.Delete("a")
|
||||
a.IsTrue(m.Len() == 0)
|
||||
}
|
||||
|
||||
func TestFileListHashMap_Clean(t *testing.T) {
|
||||
var m = caches.NewSQLiteFileListHashMap()
|
||||
m.SetIsAvailable(true)
|
||||
m.Clean()
|
||||
m.Add("a")
|
||||
}
|
||||
|
||||
func Benchmark_BigInt(b *testing.B) {
|
||||
var hash = stringutil.Md5("123456")
|
||||
b.ResetTimer()
|
||||
@@ -95,3 +147,23 @@ func Benchmark_BigInt(b *testing.B) {
|
||||
_ = bigInt.Uint64()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFileListHashMap_Exist(b *testing.B) {
|
||||
var m = caches.NewSQLiteFileListHashMap()
|
||||
m.SetIsAvailable(true)
|
||||
m.SetIsReady(true)
|
||||
|
||||
for i := 0; i < 1_000_000; i++ {
|
||||
m.Add(types.String(i))
|
||||
}
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
m.Add(types.String(rands.Int64()))
|
||||
_ = m.Exist(types.String(rands.Int64()))
|
||||
}
|
||||
})
|
||||
}
|
||||
311
internal/caches/list_file_kv.go
Normal file
311
internal/caches/list_file_kv.go
Normal file
@@ -0,0 +1,311 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package caches
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fnv"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const countKVStores = 10
|
||||
|
||||
type KVFileList struct {
|
||||
dir string
|
||||
stores [countKVStores]*KVListFileStore
|
||||
|
||||
onAdd func(item *Item)
|
||||
onRemove func(item *Item)
|
||||
}
|
||||
|
||||
func NewKVFileList(dir string) *KVFileList {
|
||||
dir = strings.TrimSuffix(dir, "/")
|
||||
|
||||
var stores = [countKVStores]*KVListFileStore{}
|
||||
for i := 0; i < countKVStores; i++ {
|
||||
stores[i] = NewKVListFileStore(dir + "/db-" + types.String(i) + ".store")
|
||||
}
|
||||
|
||||
return &KVFileList{
|
||||
dir: dir,
|
||||
stores: stores,
|
||||
}
|
||||
}
|
||||
|
||||
// Init 初始化
|
||||
func (this *KVFileList) Init() error {
|
||||
remotelogs.Println("CACHE", "loading database from '"+this.dir+"' ...")
|
||||
|
||||
var group = goman.NewTaskGroup()
|
||||
var lastErr error
|
||||
|
||||
for _, store := range this.stores {
|
||||
var storeCopy = store
|
||||
group.Run(func() {
|
||||
err := storeCopy.Open()
|
||||
if err != nil {
|
||||
lastErr = fmt.Errorf("open store '"+storeCopy.Path()+"' failed: %w", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
group.Wait()
|
||||
|
||||
return lastErr
|
||||
}
|
||||
|
||||
// Reset 重置数据
|
||||
func (this *KVFileList) Reset() error {
|
||||
// do nothing
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add 添加内容
|
||||
func (this *KVFileList) Add(hash string, item *Item) error {
|
||||
err := this.getStore(hash).AddItem(hash, item)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if this.onAdd != nil {
|
||||
this.onAdd(item)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Exist 检查内容是否存在
|
||||
func (this *KVFileList) Exist(hash string) (bool, int64, error) {
|
||||
return this.getStore(hash).ExistItem(hash)
|
||||
}
|
||||
|
||||
// ExistQuick 快速检查内容是否存在
|
||||
func (this *KVFileList) ExistQuick(hash string) (bool, error) {
|
||||
return this.getStore(hash).ExistQuickItem(hash)
|
||||
}
|
||||
|
||||
// CleanPrefix 清除某个前缀的缓存
|
||||
func (this *KVFileList) CleanPrefix(prefix string) error {
|
||||
var group = goman.NewTaskGroup()
|
||||
var lastErr error
|
||||
for _, store := range this.stores {
|
||||
var storeCopy = store
|
||||
group.Run(func() {
|
||||
err := storeCopy.CleanItemsWithPrefix(prefix)
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
}
|
||||
})
|
||||
}
|
||||
group.Wait()
|
||||
return lastErr
|
||||
}
|
||||
|
||||
// CleanMatchKey 清除通配符匹配的Key
|
||||
func (this *KVFileList) CleanMatchKey(key string) error {
|
||||
var group = goman.NewTaskGroup()
|
||||
var lastErr error
|
||||
for _, store := range this.stores {
|
||||
var storeCopy = store
|
||||
group.Run(func() {
|
||||
err := storeCopy.CleanItemsWithWildcardKey(key)
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
}
|
||||
})
|
||||
}
|
||||
group.Wait()
|
||||
return lastErr
|
||||
}
|
||||
|
||||
// CleanMatchPrefix 清除通配符匹配的前缀
|
||||
func (this *KVFileList) CleanMatchPrefix(prefix string) error {
|
||||
var group = goman.NewTaskGroup()
|
||||
var lastErr error
|
||||
for _, store := range this.stores {
|
||||
var storeCopy = store
|
||||
group.Run(func() {
|
||||
err := storeCopy.CleanItemsWithWildcardPrefix(prefix)
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
}
|
||||
})
|
||||
}
|
||||
group.Wait()
|
||||
return lastErr
|
||||
}
|
||||
|
||||
// Remove 删除内容
|
||||
func (this *KVFileList) Remove(hash string) error {
|
||||
err := this.getStore(hash).RemoveItem(hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if this.onRemove != nil {
|
||||
// when remove file item, no any extra information needed
|
||||
this.onRemove(nil)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Purge 清理过期数据
|
||||
func (this *KVFileList) Purge(count int, callback func(hash string) error) (int, error) {
|
||||
count /= countKVStores
|
||||
if count <= 0 {
|
||||
count = 100
|
||||
}
|
||||
|
||||
var countFound = 0
|
||||
var lastErr error
|
||||
for _, store := range this.stores {
|
||||
purgeCount, err := store.PurgeItems(count, callback)
|
||||
countFound += purgeCount
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
}
|
||||
}
|
||||
|
||||
return countFound, lastErr
|
||||
}
|
||||
|
||||
// PurgeLFU 清理LFU数据
|
||||
func (this *KVFileList) PurgeLFU(count int, callback func(hash string) error) error {
|
||||
count /= countKVStores
|
||||
if count <= 0 {
|
||||
count = 100
|
||||
}
|
||||
|
||||
var lastErr error
|
||||
for _, store := range this.stores {
|
||||
err := store.PurgeLFUItems(count, callback)
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
}
|
||||
}
|
||||
return lastErr
|
||||
}
|
||||
|
||||
// CleanAll 清除所有缓存
|
||||
func (this *KVFileList) CleanAll() error {
|
||||
var group = goman.NewTaskGroup()
|
||||
var lastErr error
|
||||
for _, store := range this.stores {
|
||||
var storeCopy = store
|
||||
group.Run(func() {
|
||||
err := storeCopy.RemoveAllItems()
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
}
|
||||
})
|
||||
}
|
||||
group.Wait()
|
||||
return lastErr
|
||||
}
|
||||
|
||||
// Stat 统计
|
||||
func (this *KVFileList) Stat(check func(hash string) bool) (*Stat, error) {
|
||||
var stat = &Stat{}
|
||||
|
||||
var group = goman.NewTaskGroup()
|
||||
|
||||
var lastErr error
|
||||
for _, store := range this.stores {
|
||||
var storeCopy = store
|
||||
group.Run(func() {
|
||||
storeStat, err := storeCopy.StatItems()
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
return
|
||||
}
|
||||
|
||||
group.Lock()
|
||||
stat.Size += storeStat.Size
|
||||
stat.ValueSize += storeStat.ValueSize
|
||||
stat.Count += storeStat.Count
|
||||
group.Unlock()
|
||||
})
|
||||
}
|
||||
|
||||
group.Wait()
|
||||
|
||||
return stat, lastErr
|
||||
}
|
||||
|
||||
// Count 总数量
|
||||
func (this *KVFileList) Count() (int64, error) {
|
||||
var count int64
|
||||
|
||||
var group = goman.NewTaskGroup()
|
||||
|
||||
var lastErr error
|
||||
for _, store := range this.stores {
|
||||
var storeCopy = store
|
||||
group.Run(func() {
|
||||
countStoreItems, err := storeCopy.CountItems()
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
return
|
||||
}
|
||||
|
||||
group.Lock()
|
||||
count += countStoreItems
|
||||
group.Unlock()
|
||||
})
|
||||
}
|
||||
|
||||
group.Wait()
|
||||
|
||||
return count, lastErr
|
||||
}
|
||||
|
||||
// OnAdd 添加事件
|
||||
func (this *KVFileList) OnAdd(fn func(item *Item)) {
|
||||
this.onAdd = fn
|
||||
}
|
||||
|
||||
// OnRemove 删除事件
|
||||
func (this *KVFileList) OnRemove(fn func(item *Item)) {
|
||||
this.onRemove = fn
|
||||
}
|
||||
|
||||
// Close 关闭
|
||||
func (this *KVFileList) Close() error {
|
||||
var lastErr error
|
||||
var group = goman.NewTaskGroup()
|
||||
for _, store := range this.stores {
|
||||
var storeCopy = store
|
||||
group.Run(func() {
|
||||
err := storeCopy.Close()
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
}
|
||||
})
|
||||
}
|
||||
group.Wait()
|
||||
return lastErr
|
||||
}
|
||||
|
||||
// IncreaseHit 增加点击量
|
||||
func (this *KVFileList) IncreaseHit(hash string) error {
|
||||
// do nothing
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *KVFileList) TestInspect(t *testing.T) error {
|
||||
for _, store := range this.stores {
|
||||
err := store.TestInspect(t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *KVFileList) getStore(hash string) *KVListFileStore {
|
||||
return this.stores[fnv.HashString(hash)%countKVStores]
|
||||
}
|
||||
66
internal/caches/list_file_kv_objects.go
Normal file
66
internal/caches/list_file_kv_objects.go
Normal file
@@ -0,0 +1,66 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package caches
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ItemKVEncoder item encoder
|
||||
type ItemKVEncoder[T interface{ *Item }] struct {
|
||||
}
|
||||
|
||||
func NewItemKVEncoder[T interface{ *Item }]() *ItemKVEncoder[T] {
|
||||
return &ItemKVEncoder[T]{}
|
||||
}
|
||||
|
||||
func (this *ItemKVEncoder[T]) Encode(value T) ([]byte, error) {
|
||||
return json.Marshal(value)
|
||||
}
|
||||
|
||||
func (this *ItemKVEncoder[T]) EncodeField(value T, fieldName string) ([]byte, error) {
|
||||
switch fieldName {
|
||||
case "createdAt":
|
||||
var b = make([]byte, 4)
|
||||
var createdAt = any(value).(*Item).CreatedAt
|
||||
binary.BigEndian.PutUint32(b, uint32(createdAt))
|
||||
return b, nil
|
||||
case "staleAt":
|
||||
var b = make([]byte, 4)
|
||||
var staleAt = any(value).(*Item).StaleAt
|
||||
if staleAt < 0 {
|
||||
staleAt = 0
|
||||
}
|
||||
binary.BigEndian.PutUint32(b, uint32(staleAt))
|
||||
return b, nil
|
||||
case "serverId":
|
||||
var b = make([]byte, 4)
|
||||
var serverId = any(value).(*Item).ServerId
|
||||
if serverId < 0 {
|
||||
serverId = 0
|
||||
}
|
||||
binary.BigEndian.PutUint32(b, uint32(serverId))
|
||||
return b, nil
|
||||
case "key":
|
||||
return []byte(any(value).(*Item).Key), nil
|
||||
case "wildKey":
|
||||
var key = any(value).(*Item).Key
|
||||
var dotIndex = strings.Index(key, ".")
|
||||
if dotIndex > 0 {
|
||||
var slashIndex = strings.LastIndex(key[:dotIndex], "/")
|
||||
if slashIndex > 0 {
|
||||
key = key[:dotIndex][:slashIndex+1] + "*" + key[dotIndex:]
|
||||
}
|
||||
}
|
||||
|
||||
return []byte(key), nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (this *ItemKVEncoder[T]) Decode(valueBytes []byte) (value T, err error) {
|
||||
err = json.Unmarshal(valueBytes, &value)
|
||||
return
|
||||
}
|
||||
69
internal/caches/list_file_kv_objects_test.go
Normal file
69
internal/caches/list_file_kv_objects_test.go
Normal file
@@ -0,0 +1,69 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package caches_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||
"github.com/iwind/TeaGo/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestItemKVEncoder_EncodeField(t *testing.T) {
|
||||
var a = assert.NewAssertion(t)
|
||||
|
||||
var encoder = caches.NewItemKVEncoder[*caches.Item]()
|
||||
{
|
||||
key, err := encoder.EncodeField(&caches.Item{
|
||||
Key: "https://example.com/index.html",
|
||||
}, "key")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("key:", string(key))
|
||||
a.IsTrue(string(key) == "https://example.com/index.html")
|
||||
}
|
||||
|
||||
{
|
||||
key, err := encoder.EncodeField(&caches.Item{
|
||||
Key: "",
|
||||
}, "wildKey")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("key:", string(key))
|
||||
a.IsTrue(string(key) == "")
|
||||
}
|
||||
|
||||
{
|
||||
key, err := encoder.EncodeField(&caches.Item{
|
||||
Key: "example.com/index.html",
|
||||
}, "wildKey")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("key:", string(key))
|
||||
a.IsTrue(string(key) == "example.com/index.html")
|
||||
}
|
||||
|
||||
{
|
||||
key, err := encoder.EncodeField(&caches.Item{
|
||||
Key: "https://example.com/index.html",
|
||||
}, "wildKey")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("key:", string(key))
|
||||
a.IsTrue(string(key) == "https://*.com/index.html")
|
||||
}
|
||||
|
||||
{
|
||||
key, err := encoder.EncodeField(&caches.Item{
|
||||
Key: "https://www.example.com/index.html",
|
||||
}, "wildKey")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("key:", string(key))
|
||||
a.IsTrue(string(key) == "https://*.example.com/index.html")
|
||||
}
|
||||
}
|
||||
481
internal/caches/list_file_kv_store.go
Normal file
481
internal/caches/list_file_kv_store.go
Normal file
@@ -0,0 +1,481 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package caches
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/kvstore"
|
||||
"github.com/cockroachdb/pebble"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type KVListFileStore struct {
|
||||
path string
|
||||
rawStore *kvstore.Store
|
||||
|
||||
// tables
|
||||
itemsTable *kvstore.Table[*Item]
|
||||
|
||||
rawIsReady bool
|
||||
}
|
||||
|
||||
func NewKVListFileStore(path string) *KVListFileStore {
|
||||
return &KVListFileStore{
|
||||
path: path,
|
||||
}
|
||||
}
|
||||
|
||||
func (this *KVListFileStore) Open() error {
|
||||
var reg = regexp.MustCompile(`^(.+)/([\w-]+)(\.store)$`)
|
||||
var matches = reg.FindStringSubmatch(this.path)
|
||||
if len(matches) != 4 {
|
||||
return errors.New("invalid path '" + this.path + "'")
|
||||
}
|
||||
var dir = matches[1]
|
||||
var name = matches[2]
|
||||
|
||||
rawStore, err := kvstore.OpenStoreDir(dir, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
this.rawStore = rawStore
|
||||
|
||||
db, err := rawStore.NewDB("cache")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
{
|
||||
table, tableErr := kvstore.NewTable[*Item]("items", NewItemKVEncoder[*Item]())
|
||||
if tableErr != nil {
|
||||
return tableErr
|
||||
}
|
||||
|
||||
err = table.AddFields("staleAt", "key", "wildKey", "createdAt")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
db.AddTable(table)
|
||||
this.itemsTable = table
|
||||
}
|
||||
|
||||
this.rawIsReady = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *KVListFileStore) Path() string {
|
||||
return this.path
|
||||
}
|
||||
|
||||
func (this *KVListFileStore) AddItem(hash string, item *Item) error {
|
||||
if !this.isReady() {
|
||||
return nil
|
||||
}
|
||||
|
||||
var currentTime = fasttime.Now().Unix()
|
||||
if item.ExpiresAt <= currentTime {
|
||||
return nil
|
||||
}
|
||||
if item.CreatedAt <= 0 {
|
||||
item.CreatedAt = currentTime
|
||||
}
|
||||
if item.StaleAt <= 0 {
|
||||
item.StaleAt = item.ExpiresAt + DefaultStaleCacheSeconds
|
||||
}
|
||||
return this.itemsTable.Set(hash, item)
|
||||
}
|
||||
|
||||
func (this *KVListFileStore) ExistItem(hash string) (bool, int64, error) {
|
||||
if !this.isReady() {
|
||||
return false, -1, nil
|
||||
}
|
||||
|
||||
item, err := this.itemsTable.Get(hash)
|
||||
if err != nil {
|
||||
if kvstore.IsNotFound(err) {
|
||||
return false, -1, nil
|
||||
}
|
||||
return false, -1, err
|
||||
}
|
||||
if item == nil {
|
||||
return false, -1, nil
|
||||
}
|
||||
|
||||
return item.ExpiresAt > fasttime.Now().Unix(), item.HeaderSize + item.BodySize, nil
|
||||
}
|
||||
|
||||
func (this *KVListFileStore) ExistQuickItem(hash string) (bool, error) {
|
||||
if !this.isReady() {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return this.itemsTable.Exist(hash)
|
||||
}
|
||||
|
||||
func (this *KVListFileStore) RemoveItem(hash string) error {
|
||||
if !this.isReady() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return this.itemsTable.Delete(hash)
|
||||
}
|
||||
|
||||
func (this *KVListFileStore) RemoveAllItems() error {
|
||||
if !this.isReady() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return this.itemsTable.Truncate()
|
||||
}
|
||||
|
||||
func (this *KVListFileStore) PurgeItems(count int, callback func(hash string) error) (int, error) {
|
||||
if !this.isReady() {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
var countFound int
|
||||
var currentTime = fasttime.Now().Unix()
|
||||
var hashList []string
|
||||
err := this.itemsTable.
|
||||
Query().
|
||||
FieldAsc("staleAt").
|
||||
Limit(count).
|
||||
FindAll(func(tx *kvstore.Tx[*Item], item kvstore.Item[*Item]) (goNext bool, err error) {
|
||||
if item.Value == nil {
|
||||
return true, nil
|
||||
}
|
||||
if item.Value.StaleAt < currentTime {
|
||||
countFound++
|
||||
hashList = append(hashList, item.Key)
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// delete items
|
||||
if len(hashList) > 0 {
|
||||
txErr := this.itemsTable.WriteTx(func(tx *kvstore.Tx[*Item]) error {
|
||||
for _, hash := range hashList {
|
||||
deleteErr := tx.Delete(hash)
|
||||
if deleteErr != nil {
|
||||
return deleteErr
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if txErr != nil {
|
||||
return 0, txErr
|
||||
}
|
||||
|
||||
for _, hash := range hashList {
|
||||
callbackErr := callback(hash)
|
||||
if callbackErr != nil {
|
||||
return 0, callbackErr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return countFound, nil
|
||||
}
|
||||
|
||||
func (this *KVListFileStore) PurgeLFUItems(count int, callback func(hash string) error) error {
|
||||
if !this.isReady() {
|
||||
return nil
|
||||
}
|
||||
|
||||
var hashList []string
|
||||
err := this.itemsTable.
|
||||
Query().
|
||||
FieldAsc("createdAt").
|
||||
Limit(count).
|
||||
FindAll(func(tx *kvstore.Tx[*Item], item kvstore.Item[*Item]) (goNext bool, err error) {
|
||||
if item.Value != nil {
|
||||
hashList = append(hashList, item.Key)
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// delete items
|
||||
if len(hashList) > 0 {
|
||||
txErr := this.itemsTable.WriteTx(func(tx *kvstore.Tx[*Item]) error {
|
||||
for _, hash := range hashList {
|
||||
deleteErr := tx.Delete(hash)
|
||||
if deleteErr != nil {
|
||||
return deleteErr
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if txErr != nil {
|
||||
return txErr
|
||||
}
|
||||
|
||||
for _, hash := range hashList {
|
||||
callbackErr := callback(hash)
|
||||
if callbackErr != nil {
|
||||
return callbackErr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *KVListFileStore) CleanItemsWithPrefix(prefix string) error {
|
||||
if !this.isReady() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(prefix) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var currentTime = fasttime.Now().Unix()
|
||||
|
||||
var fieldOffset []byte
|
||||
const size = 1000
|
||||
for {
|
||||
var count int
|
||||
err := this.itemsTable.
|
||||
Query().
|
||||
FieldPrefix("key", prefix).
|
||||
FieldOffset(fieldOffset).
|
||||
Limit(size).
|
||||
ForUpdate().
|
||||
FindAll(func(tx *kvstore.Tx[*Item], item kvstore.Item[*Item]) (goNext bool, err error) {
|
||||
if item.Value == nil {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
count++
|
||||
fieldOffset = item.FieldKey
|
||||
|
||||
if item.Value.CreatedAt >= currentTime {
|
||||
return true, nil
|
||||
}
|
||||
if item.Value.ExpiresAt == 0 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
item.Value.ExpiresAt = 0
|
||||
item.Value.StaleAt = 0
|
||||
|
||||
setErr := tx.Set(item.Key, item.Value) // TODO improve performance
|
||||
if setErr != nil {
|
||||
return false, setErr
|
||||
}
|
||||
|
||||
return true, nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if count < size {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *KVListFileStore) CleanItemsWithWildcardPrefix(prefix string) error {
|
||||
if !this.isReady() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(prefix) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var currentTime = fasttime.Now().Unix()
|
||||
|
||||
var fieldOffset []byte
|
||||
const size = 1000
|
||||
for {
|
||||
var count int
|
||||
err := this.itemsTable.
|
||||
Query().
|
||||
FieldPrefix("wildKey", prefix).
|
||||
FieldOffset(fieldOffset).
|
||||
Limit(size).
|
||||
ForUpdate().
|
||||
FindAll(func(tx *kvstore.Tx[*Item], item kvstore.Item[*Item]) (goNext bool, err error) {
|
||||
if item.Value == nil {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
count++
|
||||
fieldOffset = item.FieldKey
|
||||
|
||||
if item.Value.CreatedAt >= currentTime {
|
||||
return true, nil
|
||||
}
|
||||
if item.Value.ExpiresAt == 0 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
item.Value.ExpiresAt = 0
|
||||
item.Value.StaleAt = 0
|
||||
|
||||
setErr := tx.Set(item.Key, item.Value) // TODO improve performance
|
||||
if setErr != nil {
|
||||
return false, setErr
|
||||
}
|
||||
|
||||
return true, nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if count < size {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *KVListFileStore) CleanItemsWithWildcardKey(key string) error {
|
||||
if !this.isReady() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(key) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var currentTime = fasttime.Now().Unix()
|
||||
|
||||
for _, realKey := range []string{key, key + SuffixAll} {
|
||||
var fieldOffset = append(this.itemsTable.FieldKey("wildKey"), '$')
|
||||
fieldOffset = append(fieldOffset, realKey...)
|
||||
const size = 1000
|
||||
|
||||
var wildKey string
|
||||
if !strings.HasSuffix(realKey, SuffixAll) {
|
||||
wildKey = string(append([]byte(realKey), 0, 0))
|
||||
} else {
|
||||
wildKey = realKey
|
||||
}
|
||||
|
||||
for {
|
||||
var count int
|
||||
err := this.itemsTable.
|
||||
Query().
|
||||
FieldPrefix("wildKey", wildKey).
|
||||
FieldOffset(fieldOffset).
|
||||
Limit(size).
|
||||
ForUpdate().
|
||||
FindAll(func(tx *kvstore.Tx[*Item], item kvstore.Item[*Item]) (goNext bool, err error) {
|
||||
if item.Value == nil {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
count++
|
||||
fieldOffset = item.FieldKey
|
||||
|
||||
if item.Value.CreatedAt >= currentTime {
|
||||
return true, nil
|
||||
}
|
||||
if item.Value.ExpiresAt == 0 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
item.Value.ExpiresAt = 0
|
||||
item.Value.StaleAt = 0
|
||||
|
||||
setErr := tx.Set(item.Key, item.Value) // TODO improve performance
|
||||
if setErr != nil {
|
||||
return false, setErr
|
||||
}
|
||||
|
||||
return true, nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if count < size {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *KVListFileStore) CountItems() (int64, error) {
|
||||
if !this.isReady() {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
return this.itemsTable.Count()
|
||||
}
|
||||
|
||||
func (this *KVListFileStore) StatItems() (*Stat, error) {
|
||||
if !this.isReady() {
|
||||
return &Stat{}, nil
|
||||
}
|
||||
|
||||
var stat = &Stat{}
|
||||
|
||||
err := this.itemsTable.
|
||||
Query().
|
||||
FindAll(func(tx *kvstore.Tx[*Item], item kvstore.Item[*Item]) (goNext bool, err error) {
|
||||
if item.Value != nil {
|
||||
stat.Size += item.Value.Size()
|
||||
stat.ValueSize += item.Value.BodySize
|
||||
stat.Count++
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
return stat, err
|
||||
}
|
||||
|
||||
func (this *KVListFileStore) TestInspect(t *testing.T) error {
|
||||
if !this.isReady() {
|
||||
return nil
|
||||
}
|
||||
|
||||
it, err := this.rawStore.RawDB().NewIter(&pebble.IterOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = it.Close()
|
||||
}()
|
||||
|
||||
for it.First(); it.Valid(); it.Next() {
|
||||
valueBytes, valueErr := it.ValueAndErr()
|
||||
if valueErr != nil {
|
||||
return valueErr
|
||||
}
|
||||
t.Log(string(it.Key()), "=>", string(valueBytes))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *KVListFileStore) Close() error {
|
||||
if this.rawStore != nil {
|
||||
return this.rawStore.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *KVListFileStore) isReady() bool {
|
||||
return this.rawIsReady && !this.rawStore.IsClosed()
|
||||
}
|
||||
384
internal/caches/list_file_kv_test.go
Normal file
384
internal/caches/list_file_kv_test.go
Normal file
@@ -0,0 +1,384 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package caches_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var testingKVList *caches.KVFileList
|
||||
|
||||
func testOpenKVFileList(t *testing.T) *caches.KVFileList {
|
||||
var list = caches.NewKVFileList(Tea.Root + "/data/stores/cache-stores")
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
testingKVList = list
|
||||
return list
|
||||
}
|
||||
|
||||
func TestNewKVFileList(t *testing.T) {
|
||||
var list = testOpenKVFileList(t)
|
||||
err := list.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKVFileList_Add(t *testing.T) {
|
||||
var list = testOpenKVFileList(t)
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
err := list.Add(stringutil.Md5("123456"), &caches.Item{
|
||||
Type: caches.ItemTypeFile,
|
||||
Key: "https://example.com/index.html",
|
||||
ExpiresAt: time.Now().Unix() + 60,
|
||||
StaleAt: 0,
|
||||
HeaderSize: 0,
|
||||
BodySize: 4096,
|
||||
MetaSize: 0,
|
||||
Host: "",
|
||||
ServerId: 1,
|
||||
Week: 0,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKVFileList_Add_Many(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var list = testOpenKVFileList(t)
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
const start = 0
|
||||
const count = 1_000_000
|
||||
const concurrent = 100
|
||||
|
||||
var before = time.Now()
|
||||
defer func() {
|
||||
var costSeconds = time.Since(before).Seconds()
|
||||
t.Log("cost:", fmt.Sprintf("%.2fs", costSeconds), "qps:", fmt.Sprintf("%.2fK/s", float64(count)/1000/costSeconds))
|
||||
}()
|
||||
|
||||
var wg = &sync.WaitGroup{}
|
||||
wg.Add(concurrent)
|
||||
for c := 0; c < concurrent; c++ {
|
||||
go func(c int) {
|
||||
defer wg.Done()
|
||||
|
||||
var segmentStart = start + count/concurrent*c
|
||||
for i := segmentStart; i < segmentStart+count/concurrent; i++ {
|
||||
err := list.Add(stringutil.Md5(strconv.Itoa(i)), &caches.Item{
|
||||
Type: caches.ItemTypeFile,
|
||||
Key: "https://www.example.com/index.html" + strconv.Itoa(i),
|
||||
ExpiresAt: time.Now().Unix() + 60,
|
||||
StaleAt: 0,
|
||||
HeaderSize: 0,
|
||||
BodySize: int64(rand.Int() % 1_000_000),
|
||||
MetaSize: 0,
|
||||
Host: "",
|
||||
ServerId: 1,
|
||||
Week: 0,
|
||||
})
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
}
|
||||
}(c)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestKVFileList_Add_Many_Suffix(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var list = testOpenKVFileList(t)
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
const start = 0
|
||||
const count = 1000
|
||||
const concurrent = 100
|
||||
|
||||
var before = time.Now()
|
||||
defer func() {
|
||||
var costSeconds = time.Since(before).Seconds()
|
||||
t.Log("cost:", fmt.Sprintf("%.2fs", costSeconds), "qps:", fmt.Sprintf("%.2fK/s", float64(count)/1000/costSeconds))
|
||||
}()
|
||||
|
||||
var wg = &sync.WaitGroup{}
|
||||
wg.Add(concurrent)
|
||||
for c := 0; c < concurrent; c++ {
|
||||
go func(c int) {
|
||||
defer wg.Done()
|
||||
|
||||
var segmentStart = start + count/concurrent*c
|
||||
for i := segmentStart; i < segmentStart+count/concurrent; i++ {
|
||||
err := list.Add(stringutil.Md5(strconv.Itoa(i)+caches.SuffixAll), &caches.Item{
|
||||
Type: caches.ItemTypeFile,
|
||||
Key: "https://www.example.com/index.html" + strconv.Itoa(i) + caches.SuffixAll + "zip",
|
||||
ExpiresAt: time.Now().Unix() + 60,
|
||||
StaleAt: 0,
|
||||
HeaderSize: 0,
|
||||
BodySize: int64(rand.Int() % 1_000_000),
|
||||
MetaSize: 0,
|
||||
Host: "",
|
||||
ServerId: 1,
|
||||
Week: 0,
|
||||
})
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
}
|
||||
}(c)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestKVFileList_Exist(t *testing.T) {
|
||||
var list = testOpenKVFileList(t)
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
for _, hash := range []string{
|
||||
stringutil.Md5("123456"),
|
||||
stringutil.Md5("654321"),
|
||||
} {
|
||||
b, _, err := list.Exist(hash)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(hash, "=>", b)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKVFileList_ExistQuick(t *testing.T) {
|
||||
var list = testOpenKVFileList(t)
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
for _, hash := range []string{
|
||||
stringutil.Md5("123456"),
|
||||
stringutil.Md5("654321"),
|
||||
} {
|
||||
b, err := list.ExistQuick(hash)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(hash, "=>", b)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKVFileList_Remove(t *testing.T) {
|
||||
var list = testOpenKVFileList(t)
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
for _, hash := range []string{
|
||||
stringutil.Md5("123456"),
|
||||
stringutil.Md5("654321"),
|
||||
} {
|
||||
err := list.Remove(hash)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKVFileList_CleanAll(t *testing.T) {
|
||||
var list = testOpenKVFileList(t)
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
err := list.CleanAll()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKVFileList_Inspect(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var list = testOpenKVFileList(t)
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
err := list.TestInspect(t)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKVFileList_Purge(t *testing.T) {
|
||||
var list = testOpenKVFileList(t)
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
var before = time.Now()
|
||||
count, err := list.Purge(4_000, func(hash string) error {
|
||||
//t.Log("hash:", hash)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("cost:", fmt.Sprintf("%.2fms", time.Since(before).Seconds()*1000), "count:", count)
|
||||
}
|
||||
|
||||
func TestKVFileList_PurgeLFU(t *testing.T) {
|
||||
var list = testOpenKVFileList(t)
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
var before = time.Now()
|
||||
err := list.PurgeLFU(20000, func(hash string) error {
|
||||
t.Log("hash:", hash)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("cost:", fmt.Sprintf("%.2fms", time.Since(before).Seconds()*1000))
|
||||
}
|
||||
|
||||
func TestKVFileList_Count(t *testing.T) {
|
||||
var list = testOpenKVFileList(t)
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
var before = time.Now()
|
||||
count, err := list.Count()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("cost:", fmt.Sprintf("%.2fms", time.Since(before).Seconds()*1000), "count:", count)
|
||||
}
|
||||
|
||||
func TestKVFileList_Stat(t *testing.T) {
|
||||
var list = testOpenKVFileList(t)
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
var before = time.Now()
|
||||
stat, err := list.Stat(func(hash string) bool {
|
||||
return true
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("cost:", fmt.Sprintf("%.2fms", time.Since(before).Seconds()*1000), "stat:", fmt.Sprintf("%+v", stat))
|
||||
}
|
||||
|
||||
func TestKVFileList_CleanPrefix(t *testing.T) {
|
||||
var list = testOpenKVFileList(t)
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
var before = time.Now()
|
||||
|
||||
defer func() {
|
||||
var costSeconds = time.Since(before).Seconds()
|
||||
t.Log("cost:", fmt.Sprintf("%.2fms", costSeconds*1000))
|
||||
}()
|
||||
|
||||
err := list.CleanPrefix("https://www.example.com/index.html")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKVFileList_CleanMatchPrefix(t *testing.T) {
|
||||
var list = testOpenKVFileList(t)
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
var before = time.Now()
|
||||
|
||||
defer func() {
|
||||
var costSeconds = time.Since(before).Seconds()
|
||||
t.Log("cost:", fmt.Sprintf("%.2fms", costSeconds*1000))
|
||||
}()
|
||||
|
||||
err := list.CleanMatchPrefix("https://*.example.com/index.html")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKVFileList_CleanMatchKey(t *testing.T) {
|
||||
var list = testOpenKVFileList(t)
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
var before = time.Now()
|
||||
|
||||
defer func() {
|
||||
var costSeconds = time.Since(before).Seconds()
|
||||
t.Log("cost:", fmt.Sprintf("%.2fms", costSeconds*1000))
|
||||
}()
|
||||
|
||||
err := list.CleanMatchKey("https://*.example.com/index.html123")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkKVFileList_Exist(b *testing.B) {
|
||||
var list = caches.NewKVFileList(Tea.Root + "/data/stores/cache-stores")
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
_, _, existErr := list.Exist(stringutil.Md5(strconv.Itoa(rand.Int() % 2_000_000)))
|
||||
if existErr != nil {
|
||||
b.Fatal(existErr)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -4,44 +4,49 @@ package caches
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/ttlcache"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/dbs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fnv"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const CountFileDB = 20
|
||||
|
||||
// FileList 文件缓存列表管理
|
||||
type FileList struct {
|
||||
// SQLiteFileList 文件缓存列表管理
|
||||
type SQLiteFileList struct {
|
||||
dir string
|
||||
dbList [CountFileDB]*FileListDB
|
||||
dbList [CountFileDB]*SQLiteFileListDB
|
||||
|
||||
onAdd func(item *Item)
|
||||
onRemove func(item *Item)
|
||||
|
||||
memoryCache *ttlcache.Cache
|
||||
memoryCache *ttlcache.Cache[zero.Zero]
|
||||
|
||||
// 老数据库地址
|
||||
oldDir string
|
||||
}
|
||||
|
||||
func NewFileList(dir string) ListInterface {
|
||||
return &FileList{
|
||||
func NewSQLiteFileList(dir string) ListInterface {
|
||||
return &SQLiteFileList{
|
||||
dir: dir,
|
||||
memoryCache: ttlcache.NewCache(),
|
||||
memoryCache: ttlcache.NewCache[zero.Zero](),
|
||||
}
|
||||
}
|
||||
|
||||
func (this *FileList) SetOldDir(oldDir string) {
|
||||
func (this *SQLiteFileList) SetOldDir(oldDir string) {
|
||||
this.oldDir = oldDir
|
||||
}
|
||||
|
||||
func (this *FileList) Init() error {
|
||||
func (this *SQLiteFileList) Init() error {
|
||||
// 检查目录是否存在
|
||||
_, err := os.Stat(this.dir)
|
||||
if err != nil {
|
||||
@@ -59,19 +64,37 @@ func (this *FileList) Init() error {
|
||||
}
|
||||
|
||||
remotelogs.Println("CACHE", "loading database from '"+dir+"' ...")
|
||||
var wg = &sync.WaitGroup{}
|
||||
var locker = sync.Mutex{}
|
||||
var lastErr error
|
||||
|
||||
for i := 0; i < CountFileDB; i++ {
|
||||
var db = NewFileListDB()
|
||||
err = db.Open(dir + "/db-" + types.String(i) + ".db")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
|
||||
err = db.Init()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var db = NewSQLiteFileListDB()
|
||||
dbErr := db.Open(dir + "/db-" + types.String(i) + ".db")
|
||||
if dbErr != nil {
|
||||
lastErr = dbErr
|
||||
return
|
||||
}
|
||||
|
||||
this.dbList[i] = db
|
||||
dbErr = db.Init()
|
||||
if dbErr != nil {
|
||||
lastErr = dbErr
|
||||
return
|
||||
}
|
||||
|
||||
locker.Lock()
|
||||
this.dbList[i] = db
|
||||
locker.Unlock()
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
if lastErr != nil {
|
||||
return lastErr
|
||||
}
|
||||
|
||||
// 升级老版本数据库
|
||||
@@ -82,12 +105,12 @@ func (this *FileList) Init() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *FileList) Reset() error {
|
||||
func (this *SQLiteFileList) Reset() error {
|
||||
// 不做任何事情
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *FileList) Add(hash string, item *Item) error {
|
||||
func (this *SQLiteFileList) Add(hash string, item *Item) error {
|
||||
var db = this.GetDB(hash)
|
||||
|
||||
if !db.IsReady() {
|
||||
@@ -99,9 +122,7 @@ func (this *FileList) Add(hash string, item *Item) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// 这里不增加点击量,以减少对数据库的操作次数
|
||||
|
||||
this.memoryCache.Write(hash, 1, item.ExpiredAt)
|
||||
this.memoryCache.Write(hash, zero.Zero{}, this.maxExpiresAtForMemoryCache(item.ExpiresAt))
|
||||
|
||||
if this.onAdd != nil {
|
||||
this.onAdd(item)
|
||||
@@ -109,42 +130,59 @@ func (this *FileList) Add(hash string, item *Item) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *FileList) Exist(hash string) (bool, error) {
|
||||
func (this *SQLiteFileList) Exist(hash string) (bool, int64, error) {
|
||||
var db = this.GetDB(hash)
|
||||
|
||||
if !db.IsReady() {
|
||||
return false, nil
|
||||
return false, -1, nil
|
||||
}
|
||||
|
||||
// 如果Hash列表里不存在,那么必然不存在
|
||||
if !db.hashMap.Exist(hash) {
|
||||
return false, nil
|
||||
return false, -1, nil
|
||||
}
|
||||
|
||||
var item = this.memoryCache.Read(hash)
|
||||
if item != nil {
|
||||
return true, nil
|
||||
return true, -1, nil
|
||||
}
|
||||
|
||||
var row = db.existsByHashStmt.QueryRow(hash, time.Now().Unix())
|
||||
if row.Err() != nil {
|
||||
return false, nil
|
||||
return false, -1, nil
|
||||
}
|
||||
|
||||
var expiredAt int64
|
||||
err := row.Scan(&expiredAt)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
err = nil
|
||||
}
|
||||
return false, err
|
||||
return false, -1, err
|
||||
}
|
||||
this.memoryCache.Write(hash, 1, expiredAt)
|
||||
return true, nil
|
||||
|
||||
if expiredAt <= fasttime.Now().Unix() {
|
||||
return false, -1, nil
|
||||
}
|
||||
|
||||
this.memoryCache.Write(hash, zero.Zero{}, this.maxExpiresAtForMemoryCache(expiredAt))
|
||||
return true, -1, nil
|
||||
}
|
||||
|
||||
func (this *SQLiteFileList) ExistQuick(hash string) (isReady bool, found bool) {
|
||||
var db = this.GetDB(hash)
|
||||
|
||||
if !db.IsReady() || !db.HashMapIsLoaded() {
|
||||
return
|
||||
}
|
||||
|
||||
isReady = true
|
||||
found = db.hashMap.Exist(hash)
|
||||
return
|
||||
}
|
||||
|
||||
// CleanPrefix 清理某个前缀的缓存数据
|
||||
func (this *FileList) CleanPrefix(prefix string) error {
|
||||
func (this *SQLiteFileList) CleanPrefix(prefix string) error {
|
||||
if len(prefix) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -164,7 +202,7 @@ func (this *FileList) CleanPrefix(prefix string) error {
|
||||
}
|
||||
|
||||
// CleanMatchKey 清理通配符匹配的缓存数据,类似于 https://*.example.com/hello
|
||||
func (this *FileList) CleanMatchKey(key string) error {
|
||||
func (this *SQLiteFileList) CleanMatchKey(key string) error {
|
||||
if len(key) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -184,7 +222,7 @@ func (this *FileList) CleanMatchKey(key string) error {
|
||||
}
|
||||
|
||||
// CleanMatchPrefix 清理通配符匹配的缓存数据,类似于 https://*.example.com/prefix/
|
||||
func (this *FileList) CleanMatchPrefix(prefix string) error {
|
||||
func (this *SQLiteFileList) CleanMatchPrefix(prefix string) error {
|
||||
if len(prefix) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -203,15 +241,15 @@ func (this *FileList) CleanMatchPrefix(prefix string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *FileList) Remove(hash string) error {
|
||||
_, err := this.remove(hash)
|
||||
func (this *SQLiteFileList) Remove(hash string) error {
|
||||
_, err := this.remove(hash, false)
|
||||
return err
|
||||
}
|
||||
|
||||
// Purge 清理过期的缓存
|
||||
// count 每次遍历的最大数量,控制此数字可以保证每次清理的时候不用花太多时间
|
||||
// callback 每次发现过期key的调用
|
||||
func (this *FileList) Purge(count int, callback func(hash string) error) (int, error) {
|
||||
func (this *SQLiteFileList) Purge(count int, callback func(hash string) error) (int, error) {
|
||||
count /= CountFileDB
|
||||
if count <= 0 {
|
||||
count = 100
|
||||
@@ -223,11 +261,16 @@ func (this *FileList) Purge(count int, callback func(hash string) error) (int, e
|
||||
if err != nil {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
if len(hashStrings) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
countFound += len(hashStrings)
|
||||
|
||||
// 不在 rows.Next() 循环中操作是为了避免死锁
|
||||
for _, hash := range hashStrings {
|
||||
err = this.Remove(hash)
|
||||
_, err = this.remove(hash, true)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -237,12 +280,17 @@ func (this *FileList) Purge(count int, callback func(hash string) error) (int, e
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
_, err = db.writeDB.Exec(`DELETE FROM "cacheItems" WHERE "hash" IN ('` + strings.Join(hashStrings, "', '") + `')`)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return countFound, nil
|
||||
}
|
||||
|
||||
func (this *FileList) PurgeLFU(count int, callback func(hash string) error) error {
|
||||
func (this *SQLiteFileList) PurgeLFU(count int, callback func(hash string) error) error {
|
||||
count /= CountFileDB
|
||||
if count <= 0 {
|
||||
count = 100
|
||||
@@ -254,29 +302,32 @@ func (this *FileList) PurgeLFU(count int, callback func(hash string) error) erro
|
||||
return err
|
||||
}
|
||||
|
||||
if len(hashStrings) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// 不在 rows.Next() 循环中操作是为了避免死锁
|
||||
for _, hash := range hashStrings {
|
||||
notFound, err := this.remove(hash)
|
||||
_, err = this.remove(hash, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if notFound {
|
||||
err = db.DeleteHitAsync(hash)
|
||||
if err != nil {
|
||||
return db.WrapError(err)
|
||||
}
|
||||
}
|
||||
|
||||
err = callback(hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
_, err = db.writeDB.Exec(`DELETE FROM "cacheItems" WHERE "hash" IN ('` + strings.Join(hashStrings, "', '") + `')`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *FileList) CleanAll() error {
|
||||
func (this *SQLiteFileList) CleanAll() error {
|
||||
defer this.memoryCache.Clean()
|
||||
|
||||
for _, db := range this.dbList {
|
||||
@@ -289,7 +340,7 @@ func (this *FileList) CleanAll() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *FileList) Stat(check func(hash string) bool) (*Stat, error) {
|
||||
func (this *SQLiteFileList) Stat(check func(hash string) bool) (*Stat, error) {
|
||||
var result = &Stat{}
|
||||
|
||||
for _, db := range this.dbList {
|
||||
@@ -319,7 +370,7 @@ func (this *FileList) Stat(check func(hash string) bool) (*Stat, error) {
|
||||
|
||||
// Count 总数量
|
||||
// 常用的方法,所以避免直接查询数据库
|
||||
func (this *FileList) Count() (int64, error) {
|
||||
func (this *SQLiteFileList) Count() (int64, error) {
|
||||
var total int64
|
||||
for _, db := range this.dbList {
|
||||
count, err := db.Total()
|
||||
@@ -332,7 +383,7 @@ func (this *FileList) Count() (int64, error) {
|
||||
}
|
||||
|
||||
// IncreaseHit 增加点击量
|
||||
func (this *FileList) IncreaseHit(hash string) error {
|
||||
func (this *SQLiteFileList) IncreaseHit(hash string) error {
|
||||
var db = this.GetDB(hash)
|
||||
|
||||
if !db.IsReady() {
|
||||
@@ -343,36 +394,50 @@ func (this *FileList) IncreaseHit(hash string) error {
|
||||
}
|
||||
|
||||
// OnAdd 添加事件
|
||||
func (this *FileList) OnAdd(f func(item *Item)) {
|
||||
func (this *SQLiteFileList) OnAdd(f func(item *Item)) {
|
||||
this.onAdd = f
|
||||
}
|
||||
|
||||
// OnRemove 删除事件
|
||||
func (this *FileList) OnRemove(f func(item *Item)) {
|
||||
func (this *SQLiteFileList) OnRemove(f func(item *Item)) {
|
||||
this.onRemove = f
|
||||
}
|
||||
|
||||
func (this *FileList) Close() error {
|
||||
func (this *SQLiteFileList) Close() error {
|
||||
this.memoryCache.Destroy()
|
||||
|
||||
var group = goman.NewTaskGroup()
|
||||
for _, db := range this.dbList {
|
||||
if db != nil {
|
||||
_ = db.Close()
|
||||
}
|
||||
var dbCopy = db
|
||||
group.Run(func() {
|
||||
if dbCopy != nil {
|
||||
_ = dbCopy.Close()
|
||||
}
|
||||
})
|
||||
}
|
||||
group.Wait()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *FileList) GetDBIndex(hash string) uint64 {
|
||||
func (this *SQLiteFileList) GetDBIndex(hash string) uint64 {
|
||||
return fnv.HashString(hash) % CountFileDB
|
||||
}
|
||||
|
||||
func (this *FileList) GetDB(hash string) *FileListDB {
|
||||
func (this *SQLiteFileList) GetDB(hash string) *SQLiteFileListDB {
|
||||
return this.dbList[fnv.HashString(hash)%CountFileDB]
|
||||
}
|
||||
|
||||
func (this *FileList) remove(hash string) (notFound bool, err error) {
|
||||
func (this *SQLiteFileList) HashMapIsLoaded() bool {
|
||||
for _, db := range this.dbList {
|
||||
if !db.HashMapIsLoaded() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (this *SQLiteFileList) remove(hash string, isDeleted bool) (notFound bool, err error) {
|
||||
var db = this.GetDB(hash)
|
||||
|
||||
if !db.IsReady() {
|
||||
@@ -388,14 +453,11 @@ func (this *FileList) remove(hash string) (notFound bool, err error) {
|
||||
// 从缓存中删除
|
||||
this.memoryCache.Delete(hash)
|
||||
|
||||
err = db.DeleteSync(hash)
|
||||
if err != nil {
|
||||
return false, db.WrapError(err)
|
||||
}
|
||||
|
||||
err = db.DeleteHitAsync(hash)
|
||||
if err != nil {
|
||||
return false, db.WrapError(err)
|
||||
if !isDeleted {
|
||||
err = db.DeleteSync(hash)
|
||||
if err != nil {
|
||||
return false, db.WrapError(err)
|
||||
}
|
||||
}
|
||||
|
||||
if this.onRemove != nil {
|
||||
@@ -407,14 +469,14 @@ func (this *FileList) remove(hash string) (notFound bool, err error) {
|
||||
}
|
||||
|
||||
// 升级老版本数据库
|
||||
func (this *FileList) upgradeOldDB() {
|
||||
func (this *SQLiteFileList) upgradeOldDB() {
|
||||
if len(this.oldDir) == 0 {
|
||||
return
|
||||
}
|
||||
_ = this.UpgradeV3(this.oldDir, false)
|
||||
}
|
||||
|
||||
func (this *FileList) UpgradeV3(oldDir string, brokenOnError bool) error {
|
||||
func (this *SQLiteFileList) UpgradeV3(oldDir string, brokenOnError bool) error {
|
||||
// index.db
|
||||
var indexDBPath = oldDir + "/index.db"
|
||||
_, err := os.Stat(indexDBPath)
|
||||
@@ -428,7 +490,7 @@ func (this *FileList) UpgradeV3(oldDir string, brokenOnError bool) error {
|
||||
remotelogs.Println("CACHE", "upgrading local database finished")
|
||||
}()
|
||||
|
||||
db, err := dbs.OpenWriter("file:" + indexDBPath + "?cache=shared&mode=rwc&_journal_mode=WAL&_sync=OFF&_locking_mode=EXCLUSIVE")
|
||||
db, err := dbs.OpenWriter("file:" + indexDBPath + "?cache=shared&mode=rwc&_journal_mode=WAL&_sync=" + dbs.SyncMode + "&_locking_mode=EXCLUSIVE")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -486,16 +548,13 @@ func (this *FileList) UpgradeV3(oldDir string, brokenOnError bool) error {
|
||||
err = this.Add(hash, &Item{
|
||||
Type: ItemTypeFile,
|
||||
Key: key,
|
||||
ExpiredAt: expiredAt,
|
||||
ExpiresAt: expiredAt,
|
||||
StaleAt: staleAt,
|
||||
HeaderSize: headerSize,
|
||||
BodySize: bodySize,
|
||||
MetaSize: metaSize,
|
||||
Host: host,
|
||||
ServerId: serverId,
|
||||
Week1Hits: 0,
|
||||
Week2Hits: 0,
|
||||
Week: 0,
|
||||
})
|
||||
if err != nil {
|
||||
if brokenOnError {
|
||||
@@ -515,3 +574,11 @@ func (this *FileList) UpgradeV3(oldDir string, brokenOnError bool) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *SQLiteFileList) maxExpiresAtForMemoryCache(expiresAt int64) int64 {
|
||||
var maxTimestamp = fasttime.Now().Unix() + 3600
|
||||
if expiresAt > maxTimestamp {
|
||||
return maxTimestamp
|
||||
}
|
||||
return expiresAt
|
||||
}
|
||||
@@ -17,7 +17,11 @@ import (
|
||||
)
|
||||
|
||||
func TestFileList_Init(t *testing.T) {
|
||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var list = caches.NewSQLiteFileList(Tea.Root + "/data/cache-index/p1")
|
||||
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
@@ -34,7 +38,11 @@ func TestFileList_Init(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileList_Add(t *testing.T) {
|
||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1").(*caches.FileList)
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var list = caches.NewSQLiteFileList(Tea.Root + "/data/cache-index/p1").(*caches.SQLiteFileList)
|
||||
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
@@ -53,7 +61,7 @@ func TestFileList_Add(t *testing.T) {
|
||||
t.Log("db index:", list.GetDBIndex(hash))
|
||||
err = list.Add(hash, &caches.Item{
|
||||
Key: "123456",
|
||||
ExpiredAt: time.Now().Unix() + 1,
|
||||
ExpiresAt: time.Now().Unix() + 1,
|
||||
HeaderSize: 1,
|
||||
MetaSize: 2,
|
||||
BodySize: 3,
|
||||
@@ -74,7 +82,7 @@ func TestFileList_Add_Many(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
||||
var list = caches.NewSQLiteFileList(Tea.Root + "/data/cache-index/p1")
|
||||
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
@@ -86,11 +94,13 @@ func TestFileList_Add_Many(t *testing.T) {
|
||||
}
|
||||
|
||||
var before = time.Now()
|
||||
for i := 0; i < 10_000_000; i++ {
|
||||
const offset = 0
|
||||
const count = 1_000_000
|
||||
for i := offset; i < offset+count; i++ {
|
||||
u := "https://edge.teaos.cn/123456" + strconv.Itoa(i)
|
||||
_ = list.Add(stringutil.Md5(u), &caches.Item{
|
||||
Key: u,
|
||||
ExpiredAt: time.Now().Unix() + 3600,
|
||||
ExpiresAt: time.Now().Unix() + 3600,
|
||||
HeaderSize: 1,
|
||||
MetaSize: 2,
|
||||
BodySize: 3,
|
||||
@@ -107,7 +117,11 @@ func TestFileList_Add_Many(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileList_Exist(t *testing.T) {
|
||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1").(*caches.FileList)
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var list = caches.NewSQLiteFileList(Tea.Root + "/data/cache-index/p1").(*caches.SQLiteFileList)
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
@@ -126,7 +140,7 @@ func TestFileList_Exist(t *testing.T) {
|
||||
}()
|
||||
{
|
||||
var hash = stringutil.Md5("123456")
|
||||
exists, err := list.Exist(hash)
|
||||
exists, _, err := list.Exist(hash)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -134,7 +148,7 @@ func TestFileList_Exist(t *testing.T) {
|
||||
}
|
||||
{
|
||||
var hash = stringutil.Md5("http://edge.teaos.cn/1234561")
|
||||
exists, err := list.Exist(hash)
|
||||
exists, _, err := list.Exist(hash)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -143,10 +157,14 @@ func TestFileList_Exist(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileList_Exist_Many_DB(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
// 测试在多个数据库下的性能
|
||||
var listSlice = []caches.ListInterface{}
|
||||
for i := 1; i <= 10; i++ {
|
||||
var list = caches.NewFileList(Tea.Root + "/data/data" + strconv.Itoa(i))
|
||||
var list = caches.NewSQLiteFileList(Tea.Root + "/data/data" + strconv.Itoa(i))
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -190,7 +208,7 @@ func TestFileList_Exist_Many_DB(t *testing.T) {
|
||||
countLocker.Unlock()
|
||||
|
||||
var list = listSlice[rands.Int(0, len(listSlice)-1)]
|
||||
_, _ = list.Exist(hash)
|
||||
_, _, _ = list.Exist(hash)
|
||||
default:
|
||||
return
|
||||
}
|
||||
@@ -202,7 +220,11 @@ func TestFileList_Exist_Many_DB(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileList_CleanPrefix(t *testing.T) {
|
||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var list = caches.NewSQLiteFileList(Tea.Root + "/data/cache-index/p1")
|
||||
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
@@ -222,7 +244,11 @@ func TestFileList_CleanPrefix(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileList_Remove(t *testing.T) {
|
||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1").(*caches.FileList)
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var list = caches.NewSQLiteFileList(Tea.Root + "/data/cache-index/p1").(*caches.SQLiteFileList)
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
@@ -246,7 +272,11 @@ func TestFileList_Remove(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileList_Purge(t *testing.T) {
|
||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var list = caches.NewSQLiteFileList(Tea.Root + "/data/cache-index/p1")
|
||||
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
@@ -270,7 +300,11 @@ func TestFileList_Purge(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileList_PurgeLFU(t *testing.T) {
|
||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var list = caches.NewSQLiteFileList(Tea.Root + "/data/cache-index/p1")
|
||||
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
@@ -281,11 +315,6 @@ func TestFileList_PurgeLFU(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = list.IncreaseHit(stringutil.Md5("123456"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var count = 0
|
||||
err = list.PurgeLFU(caches.CountFileDB*2, func(hash string) error {
|
||||
t.Log(hash)
|
||||
@@ -299,7 +328,11 @@ func TestFileList_PurgeLFU(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileList_Stat(t *testing.T) {
|
||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var list = caches.NewSQLiteFileList(Tea.Root + "/data/cache-index/p1")
|
||||
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
@@ -318,7 +351,11 @@ func TestFileList_Stat(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileList_Count(t *testing.T) {
|
||||
var list = caches.NewFileList(Tea.Root + "/data")
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var list = caches.NewSQLiteFileList(Tea.Root + "/data")
|
||||
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
@@ -338,7 +375,11 @@ func TestFileList_Count(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileList_CleanAll(t *testing.T) {
|
||||
var list = caches.NewFileList(Tea.Root + "/data")
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var list = caches.NewSQLiteFileList(Tea.Root + "/data")
|
||||
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
@@ -356,43 +397,12 @@ func TestFileList_CleanAll(t *testing.T) {
|
||||
t.Log(list.Count())
|
||||
}
|
||||
|
||||
func TestFileList_IncreaseHit(t *testing.T) {
|
||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
||||
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
var before = time.Now()
|
||||
defer func() {
|
||||
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||
}()
|
||||
|
||||
var count = 1_000_000
|
||||
|
||||
if !testutils.IsSingleTesting() {
|
||||
count = 10
|
||||
}
|
||||
for i := 0; i < count; i++ {
|
||||
err = list.IncreaseHit(stringutil.Md5("abc" + types.String(i)))
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("ok")
|
||||
}
|
||||
|
||||
func TestFileList_UpgradeV3(t *testing.T) {
|
||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p43").(*caches.FileList)
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var list = caches.NewSQLiteFileList(Tea.Root + "/data/cache-index/p43").(*caches.SQLiteFileList)
|
||||
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
@@ -416,7 +426,11 @@ func TestFileList_UpgradeV3(t *testing.T) {
|
||||
}
|
||||
|
||||
func BenchmarkFileList_Exist(b *testing.B) {
|
||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var list = caches.NewSQLiteFileList(Tea.Root + "/data/cache-index/p1")
|
||||
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
@@ -428,6 +442,6 @@ func BenchmarkFileList_Exist(b *testing.B) {
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = list.Exist("f0eb5b87e0b0041f3917002c0707475f" + types.String(i))
|
||||
_, _, _ = list.Exist("f0eb5b87e0b0041f3917002c0707475f" + types.String(i))
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ type ListInterface interface {
|
||||
Add(hash string, item *Item) error
|
||||
|
||||
// Exist 检查内容是否存在
|
||||
Exist(hash string) (bool, error)
|
||||
Exist(hash string) (ok bool, size int64, err error)
|
||||
|
||||
// CleanPrefix 清除某个前缀的缓存
|
||||
CleanPrefix(prefix string) error
|
||||
|
||||
@@ -2,7 +2,6 @@ package caches
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"net"
|
||||
"net/url"
|
||||
@@ -19,9 +18,6 @@ type MemoryList struct {
|
||||
|
||||
itemMaps map[string]map[string]*Item // prefix => { hash => item }
|
||||
|
||||
weekItemMaps map[int32]map[string]zero.Zero // week => { hash => Zero }
|
||||
minWeek int32
|
||||
|
||||
prefixes []string
|
||||
locker sync.RWMutex
|
||||
onAdd func(item *Item)
|
||||
@@ -32,9 +28,7 @@ type MemoryList struct {
|
||||
|
||||
func NewMemoryList() ListInterface {
|
||||
return &MemoryList{
|
||||
itemMaps: map[string]map[string]*Item{},
|
||||
weekItemMaps: map[int32]map[string]zero.Zero{},
|
||||
minWeek: currentWeek(),
|
||||
itemMaps: map[string]map[string]*Item{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +50,6 @@ func (this *MemoryList) Reset() error {
|
||||
for key := range this.itemMaps {
|
||||
this.itemMaps[key] = map[string]*Item{}
|
||||
}
|
||||
this.weekItemMaps = map[int32]map[string]zero.Zero{}
|
||||
this.locker.Unlock()
|
||||
|
||||
atomic.StoreInt64(&this.count, 0)
|
||||
@@ -65,10 +58,6 @@ func (this *MemoryList) Reset() error {
|
||||
}
|
||||
|
||||
func (this *MemoryList) Add(hash string, item *Item) error {
|
||||
if item.Week == 0 {
|
||||
item.Week = currentWeek()
|
||||
}
|
||||
|
||||
this.locker.Lock()
|
||||
|
||||
prefix := this.prefix(hash)
|
||||
@@ -81,14 +70,6 @@ func (this *MemoryList) Add(hash string, item *Item) error {
|
||||
// 先删除,为了可以正确触发统计
|
||||
oldItem, ok := itemMap[hash]
|
||||
if ok {
|
||||
// 从week map中删除
|
||||
if oldItem.Week > 0 {
|
||||
wm, ok := this.weekItemMaps[oldItem.Week]
|
||||
if ok {
|
||||
delete(wm, hash)
|
||||
}
|
||||
}
|
||||
|
||||
// 回调
|
||||
if this.onRemove != nil {
|
||||
this.onRemove(oldItem)
|
||||
@@ -104,33 +85,25 @@ func (this *MemoryList) Add(hash string, item *Item) error {
|
||||
|
||||
itemMap[hash] = item
|
||||
|
||||
// week map
|
||||
wm, ok := this.weekItemMaps[item.Week]
|
||||
if ok {
|
||||
wm[hash] = zero.New()
|
||||
} else {
|
||||
this.weekItemMaps[item.Week] = map[string]zero.Zero{hash: zero.New()}
|
||||
}
|
||||
|
||||
this.locker.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *MemoryList) Exist(hash string) (bool, error) {
|
||||
func (this *MemoryList) Exist(hash string) (bool, int64, error) {
|
||||
this.locker.RLock()
|
||||
defer this.locker.RUnlock()
|
||||
|
||||
prefix := this.prefix(hash)
|
||||
itemMap, ok := this.itemMaps[prefix]
|
||||
if !ok {
|
||||
return false, nil
|
||||
return false, -1, nil
|
||||
}
|
||||
item, ok := itemMap[hash]
|
||||
if !ok {
|
||||
return false, nil
|
||||
return false, -1, nil
|
||||
}
|
||||
|
||||
return !item.IsExpired(), nil
|
||||
return !item.IsExpired(), -1, nil
|
||||
}
|
||||
|
||||
// CleanPrefix 根据前缀进行清除
|
||||
@@ -142,7 +115,7 @@ func (this *MemoryList) CleanPrefix(prefix string) error {
|
||||
for _, itemMap := range this.itemMaps {
|
||||
for _, item := range itemMap {
|
||||
if strings.HasPrefix(item.Key, prefix) {
|
||||
item.ExpiredAt = 0
|
||||
item.ExpiresAt = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -180,7 +153,7 @@ func (this *MemoryList) CleanMatchKey(key string) error {
|
||||
if configutils.MatchDomain(host, item.Host) {
|
||||
var itemRequestURI = item.RequestURI()
|
||||
if itemRequestURI == requestURI || strings.HasPrefix(itemRequestURI, requestURI+SuffixAll) {
|
||||
item.ExpiredAt = 0
|
||||
item.ExpiresAt = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -216,7 +189,7 @@ func (this *MemoryList) CleanMatchPrefix(prefix string) error {
|
||||
if configutils.MatchDomain(host, item.Host) {
|
||||
var itemRequestURI = item.RequestURI()
|
||||
if isRootPath || strings.HasPrefix(itemRequestURI, requestURI) {
|
||||
item.ExpiredAt = 0
|
||||
item.ExpiresAt = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -242,14 +215,6 @@ func (this *MemoryList) Remove(hash string) error {
|
||||
|
||||
atomic.AddInt64(&this.count, -1)
|
||||
delete(itemMap, hash)
|
||||
|
||||
// week map
|
||||
if item.Week > 0 {
|
||||
wm, ok := this.weekItemMaps[item.Week]
|
||||
if ok {
|
||||
delete(wm, hash)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.locker.Unlock()
|
||||
@@ -261,12 +226,12 @@ func (this *MemoryList) Remove(hash string) error {
|
||||
// callback 每次发现过期key的调用
|
||||
func (this *MemoryList) Purge(count int, callback func(hash string) error) (int, error) {
|
||||
this.locker.Lock()
|
||||
deletedHashList := []string{}
|
||||
var deletedHashList = []string{}
|
||||
|
||||
if this.purgeIndex >= len(this.prefixes) {
|
||||
this.purgeIndex = 0
|
||||
}
|
||||
prefix := this.prefixes[this.purgeIndex]
|
||||
var prefix = this.prefixes[this.purgeIndex]
|
||||
|
||||
this.purgeIndex++
|
||||
|
||||
@@ -290,14 +255,6 @@ func (this *MemoryList) Purge(count int, callback func(hash string) error) (int,
|
||||
delete(itemMap, hash)
|
||||
deletedHashList = append(deletedHashList, hash)
|
||||
|
||||
// week map
|
||||
if item.Week > 0 {
|
||||
wm, ok := this.weekItemMaps[item.Week]
|
||||
if ok {
|
||||
delete(wm, hash)
|
||||
}
|
||||
}
|
||||
|
||||
countFound++
|
||||
}
|
||||
|
||||
@@ -322,63 +279,48 @@ func (this *MemoryList) PurgeLFU(count int, callback func(hash string) error) er
|
||||
return nil
|
||||
}
|
||||
|
||||
var week = currentWeek()
|
||||
if this.minWeek > week {
|
||||
this.minWeek = week
|
||||
}
|
||||
|
||||
var deletedHashList = []string{}
|
||||
|
||||
var week = currentWeek()
|
||||
var round = 0
|
||||
|
||||
this.locker.Lock()
|
||||
|
||||
Loop:
|
||||
for w := this.minWeek; w <= week; w++ {
|
||||
this.minWeek = w
|
||||
for {
|
||||
var found = false
|
||||
round++
|
||||
for _, itemMap := range this.itemMaps {
|
||||
for hash, item := range itemMap {
|
||||
found = true
|
||||
|
||||
this.locker.Lock()
|
||||
wm, ok := this.weekItemMaps[w]
|
||||
if ok {
|
||||
var wc = len(wm)
|
||||
if wc == 0 {
|
||||
delete(this.weekItemMaps, w)
|
||||
} else {
|
||||
if wc <= count {
|
||||
delete(this.weekItemMaps, w)
|
||||
if week-item.Week <= 1 /** 最近有在使用 **/ && round <= 3 /** 查找轮数过多还不满足数量要求的就不再限制 **/ {
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO 未来支持按照点击量排序
|
||||
for hash := range wm {
|
||||
count--
|
||||
|
||||
if count < 0 {
|
||||
this.locker.Unlock()
|
||||
break Loop
|
||||
}
|
||||
|
||||
delete(wm, hash)
|
||||
|
||||
itemMap, ok := this.itemMaps[this.prefix(hash)]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
item, ok := itemMap[hash]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if this.onRemove != nil {
|
||||
this.onRemove(item)
|
||||
}
|
||||
|
||||
atomic.AddInt64(&this.count, -1)
|
||||
delete(itemMap, hash)
|
||||
deletedHashList = append(deletedHashList, hash)
|
||||
if this.onRemove != nil {
|
||||
this.onRemove(item)
|
||||
}
|
||||
|
||||
atomic.AddInt64(&this.count, -1)
|
||||
delete(itemMap, hash)
|
||||
deletedHashList = append(deletedHashList, hash)
|
||||
|
||||
count--
|
||||
if count <= 0 {
|
||||
break Loop
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
} else {
|
||||
delete(this.weekItemMaps, w)
|
||||
}
|
||||
this.locker.Unlock()
|
||||
if !found {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
this.locker.Unlock()
|
||||
|
||||
// 执行外部操作
|
||||
for _, hash := range deletedHashList {
|
||||
if callback != nil {
|
||||
@@ -451,30 +393,26 @@ func (this *MemoryList) IncreaseHit(hash string) error {
|
||||
|
||||
item, ok := itemMap[hash]
|
||||
if ok {
|
||||
var week = currentWeek()
|
||||
|
||||
// 交换位置
|
||||
if item.Week > 0 && item.Week != week {
|
||||
wm, ok := this.weekItemMaps[item.Week]
|
||||
if ok {
|
||||
delete(wm, hash)
|
||||
}
|
||||
wm, ok = this.weekItemMaps[week]
|
||||
if ok {
|
||||
wm[hash] = zero.New()
|
||||
} else {
|
||||
this.weekItemMaps[week] = map[string]zero.Zero{hash: zero.New()}
|
||||
}
|
||||
}
|
||||
|
||||
item.IncreaseHit(week)
|
||||
item.Week = currentWeek()
|
||||
}
|
||||
|
||||
this.locker.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *MemoryList) print(t *testing.T) {
|
||||
func (this *MemoryList) Prefixes() []string {
|
||||
return this.prefixes
|
||||
}
|
||||
|
||||
func (this *MemoryList) ItemMaps() map[string]map[string]*Item {
|
||||
return this.itemMaps
|
||||
}
|
||||
|
||||
func (this *MemoryList) PurgeIndex() int {
|
||||
return this.purgeIndex
|
||||
}
|
||||
|
||||
func (this *MemoryList) Print(t *testing.T) {
|
||||
this.locker.Lock()
|
||||
for _, itemMap := range this.itemMaps {
|
||||
if len(itemMap) > 0 {
|
||||
|
||||
@@ -1,109 +1,115 @@
|
||||
package caches
|
||||
package caches_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
"github.com/cespare/xxhash"
|
||||
"github.com/cespare/xxhash/v2"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
"math/rand"
|
||||
"sort"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestMemoryList_Add(t *testing.T) {
|
||||
list := NewMemoryList().(*MemoryList)
|
||||
list := caches.NewMemoryList().(*caches.MemoryList)
|
||||
_ = list.Init()
|
||||
_ = list.Add("a", &Item{
|
||||
_ = list.Add("a", &caches.Item{
|
||||
Key: "a1",
|
||||
ExpiredAt: time.Now().Unix() + 3600,
|
||||
ExpiresAt: time.Now().Unix() + 3600,
|
||||
HeaderSize: 1024,
|
||||
})
|
||||
_ = list.Add("b", &Item{
|
||||
_ = list.Add("b", &caches.Item{
|
||||
Key: "b1",
|
||||
ExpiredAt: time.Now().Unix() + 3600,
|
||||
ExpiresAt: time.Now().Unix() + 3600,
|
||||
HeaderSize: 1024,
|
||||
})
|
||||
_ = list.Add("123456", &Item{
|
||||
_ = list.Add("123456", &caches.Item{
|
||||
Key: "c1",
|
||||
ExpiredAt: time.Now().Unix() + 3600,
|
||||
ExpiresAt: time.Now().Unix() + 3600,
|
||||
HeaderSize: 1024,
|
||||
})
|
||||
t.Log(list.prefixes)
|
||||
logs.PrintAsJSON(list.itemMaps, t)
|
||||
logs.PrintAsJSON(list.weekItemMaps, t)
|
||||
t.Log(list.Prefixes())
|
||||
logs.PrintAsJSON(list.ItemMaps(), t)
|
||||
t.Log(list.Count())
|
||||
}
|
||||
|
||||
func TestMemoryList_Remove(t *testing.T) {
|
||||
list := NewMemoryList().(*MemoryList)
|
||||
list := caches.NewMemoryList().(*caches.MemoryList)
|
||||
_ = list.Init()
|
||||
_ = list.Add("a", &Item{
|
||||
_ = list.Add("a", &caches.Item{
|
||||
Key: "a1",
|
||||
ExpiredAt: time.Now().Unix() + 3600,
|
||||
ExpiresAt: time.Now().Unix() + 3600,
|
||||
HeaderSize: 1024,
|
||||
})
|
||||
_ = list.Add("b", &Item{
|
||||
_ = list.Add("b", &caches.Item{
|
||||
Key: "b1",
|
||||
ExpiredAt: time.Now().Unix() + 3600,
|
||||
ExpiresAt: time.Now().Unix() + 3600,
|
||||
HeaderSize: 1024,
|
||||
})
|
||||
_ = list.Remove("b")
|
||||
list.print(t)
|
||||
logs.PrintAsJSON(list.weekItemMaps, t)
|
||||
list.Print(t)
|
||||
t.Log(list.Count())
|
||||
}
|
||||
|
||||
func TestMemoryList_Purge(t *testing.T) {
|
||||
list := NewMemoryList().(*MemoryList)
|
||||
list := caches.NewMemoryList().(*caches.MemoryList)
|
||||
_ = list.Init()
|
||||
_ = list.Add("a", &Item{
|
||||
_ = list.Add("a", &caches.Item{
|
||||
Key: "a1",
|
||||
ExpiredAt: time.Now().Unix() + 3600,
|
||||
ExpiresAt: time.Now().Unix() + 3600,
|
||||
HeaderSize: 1024,
|
||||
})
|
||||
_ = list.Add("b", &Item{
|
||||
_ = list.Add("b", &caches.Item{
|
||||
Key: "b1",
|
||||
ExpiredAt: time.Now().Unix() + 3600,
|
||||
ExpiresAt: time.Now().Unix() + 3600,
|
||||
HeaderSize: 1024,
|
||||
})
|
||||
_ = list.Add("c", &Item{
|
||||
_ = list.Add("c", &caches.Item{
|
||||
Key: "c1",
|
||||
ExpiredAt: time.Now().Unix() - 3600,
|
||||
ExpiresAt: time.Now().Unix() - 3600,
|
||||
HeaderSize: 1024,
|
||||
})
|
||||
_ = list.Add("d", &Item{
|
||||
_ = list.Add("d", &caches.Item{
|
||||
Key: "d1",
|
||||
ExpiredAt: time.Now().Unix() - 2,
|
||||
ExpiresAt: time.Now().Unix() - 2,
|
||||
HeaderSize: 1024,
|
||||
})
|
||||
_, _ = list.Purge(100, func(hash string) error {
|
||||
t.Log("delete:", hash)
|
||||
return nil
|
||||
})
|
||||
list.print(t)
|
||||
logs.PrintAsJSON(list.weekItemMaps, t)
|
||||
list.Print(t)
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
_, _ = list.Purge(100, func(hash string) error {
|
||||
t.Log("delete:", hash)
|
||||
return nil
|
||||
})
|
||||
t.Log(list.purgeIndex)
|
||||
t.Log(list.PurgeIndex())
|
||||
}
|
||||
|
||||
t.Log(list.Count())
|
||||
}
|
||||
|
||||
func TestMemoryList_Purge_Large_List(t *testing.T) {
|
||||
list := NewMemoryList().(*MemoryList)
|
||||
var list = caches.NewMemoryList().(*caches.MemoryList)
|
||||
_ = list.Init()
|
||||
|
||||
for i := 0; i < 1_000_000; i++ {
|
||||
_ = list.Add("a"+strconv.Itoa(i), &Item{
|
||||
var count = 100
|
||||
if testutils.IsSingleTesting() {
|
||||
count = 1_000_000
|
||||
}
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
_ = list.Add("a"+strconv.Itoa(i), &caches.Item{
|
||||
Key: "a" + strconv.Itoa(i),
|
||||
ExpiredAt: time.Now().Unix() + int64(rands.Int(0, 24*3600)),
|
||||
ExpiresAt: time.Now().Unix() + int64(rands.Int(0, 24*3600)),
|
||||
HeaderSize: 1024,
|
||||
})
|
||||
}
|
||||
@@ -114,45 +120,48 @@ func TestMemoryList_Purge_Large_List(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMemoryList_Stat(t *testing.T) {
|
||||
list := NewMemoryList()
|
||||
list := caches.NewMemoryList()
|
||||
_ = list.Init()
|
||||
_ = list.Add("a", &Item{
|
||||
_ = list.Add("a", &caches.Item{
|
||||
Key: "a1",
|
||||
ExpiredAt: time.Now().Unix() + 3600,
|
||||
ExpiresAt: time.Now().Unix() + 3600,
|
||||
HeaderSize: 1024,
|
||||
})
|
||||
_ = list.Add("b", &Item{
|
||||
_ = list.Add("b", &caches.Item{
|
||||
Key: "b1",
|
||||
ExpiredAt: time.Now().Unix() + 3600,
|
||||
ExpiresAt: time.Now().Unix() + 3600,
|
||||
HeaderSize: 1024,
|
||||
})
|
||||
_ = list.Add("c", &Item{
|
||||
_ = list.Add("c", &caches.Item{
|
||||
Key: "c1",
|
||||
ExpiredAt: time.Now().Unix(),
|
||||
ExpiresAt: time.Now().Unix(),
|
||||
HeaderSize: 1024,
|
||||
})
|
||||
_ = list.Add("d", &Item{
|
||||
_ = list.Add("d", &caches.Item{
|
||||
Key: "d1",
|
||||
ExpiredAt: time.Now().Unix() - 2,
|
||||
ExpiresAt: time.Now().Unix() - 2,
|
||||
HeaderSize: 1024,
|
||||
})
|
||||
result, _ := list.Stat(func(hash string) bool {
|
||||
// 随机测试
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
return rand.Int()%2 == 0
|
||||
})
|
||||
t.Log(result)
|
||||
}
|
||||
|
||||
func TestMemoryList_CleanPrefix(t *testing.T) {
|
||||
list := NewMemoryList()
|
||||
list := caches.NewMemoryList()
|
||||
_ = list.Init()
|
||||
before := time.Now()
|
||||
for i := 0; i < 1_000_000; i++ {
|
||||
var count = 100
|
||||
if testutils.IsSingleTesting() {
|
||||
count = 1_000_000
|
||||
}
|
||||
for i := 0; i < count; i++ {
|
||||
key := "https://www.teaos.cn/hello/" + strconv.Itoa(i/10000) + "/" + strconv.Itoa(i) + ".html"
|
||||
_ = list.Add(fmt.Sprintf("%d", xxhash.Sum64String(key)), &Item{
|
||||
_ = list.Add(fmt.Sprintf("%d", xxhash.Sum64String(key)), &caches.Item{
|
||||
Key: key,
|
||||
ExpiredAt: time.Now().Unix() + 3600,
|
||||
ExpiresAt: time.Now().Unix() + 3600,
|
||||
BodySize: 0,
|
||||
HeaderSize: 0,
|
||||
})
|
||||
@@ -172,27 +181,69 @@ func TestMemoryList_CleanPrefix(t *testing.T) {
|
||||
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||
}
|
||||
|
||||
func TestMapRandomDelete(t *testing.T) {
|
||||
var countMap = map[int]int{} // k => count
|
||||
|
||||
var count = 1000
|
||||
if testutils.IsSingleTesting() {
|
||||
count = 1_000_000
|
||||
}
|
||||
|
||||
for j := 0; j < count; j++ {
|
||||
var m = map[int]bool{}
|
||||
for i := 0; i < 100; i++ {
|
||||
m[i] = true
|
||||
}
|
||||
|
||||
var count = 0
|
||||
for k := range m {
|
||||
delete(m, k)
|
||||
count++
|
||||
if count >= 10 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for k := range m {
|
||||
countMap[k]++
|
||||
}
|
||||
}
|
||||
|
||||
var counts = []int{}
|
||||
for _, count := range countMap {
|
||||
counts = append(counts, count)
|
||||
}
|
||||
sort.Ints(counts)
|
||||
t.Log("["+types.String(len(counts))+"]", counts)
|
||||
}
|
||||
|
||||
func TestMemoryList_PurgeLFU(t *testing.T) {
|
||||
var list = NewMemoryList().(*MemoryList)
|
||||
list.minWeek = 2704
|
||||
var list = caches.NewMemoryList().(*caches.MemoryList)
|
||||
|
||||
var before = time.Now()
|
||||
defer func() {
|
||||
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||
}()
|
||||
|
||||
t.Log("current week:", currentWeek())
|
||||
_ = list.Add("1", &caches.Item{})
|
||||
_ = list.Add("2", &caches.Item{})
|
||||
_ = list.Add("3", &caches.Item{})
|
||||
_ = list.Add("4", &caches.Item{})
|
||||
_ = list.Add("5", &caches.Item{})
|
||||
|
||||
_ = list.Add("1", &Item{})
|
||||
_ = list.Add("2", &Item{})
|
||||
_ = list.Add("3", &Item{})
|
||||
_ = list.Add("4", &Item{})
|
||||
_ = list.Add("5", &Item{})
|
||||
_ = list.Add("6", &Item{Week: 2704})
|
||||
_ = list.Add("7", &Item{Week: 2704})
|
||||
_ = list.Add("8", &Item{Week: 2705})
|
||||
//_ = list.IncreaseHit("1")
|
||||
//_ = list.IncreaseHit("2")
|
||||
//_ = list.IncreaseHit("3")
|
||||
//_ = list.IncreaseHit("4")
|
||||
//_ = list.IncreaseHit("5")
|
||||
|
||||
err := list.PurgeLFU(2, func(hash string) error {
|
||||
count, err := list.Count()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("count items before purge:", count)
|
||||
|
||||
err = list.PurgeLFU(5, func(hash string) error {
|
||||
t.Log("purge lfu:", hash)
|
||||
return nil
|
||||
})
|
||||
@@ -201,59 +252,40 @@ func TestMemoryList_PurgeLFU(t *testing.T) {
|
||||
}
|
||||
t.Log("ok")
|
||||
|
||||
logs.PrintAsJSON(list.weekItemMaps, t)
|
||||
t.Log(list.Count())
|
||||
}
|
||||
|
||||
func TestMemoryList_IncreaseHit(t *testing.T) {
|
||||
var list = NewMemoryList().(*MemoryList)
|
||||
var item = &Item{}
|
||||
item.Week = 2705
|
||||
item.Week2Hits = 100
|
||||
|
||||
_ = list.Add("a", &Item{})
|
||||
_ = list.Add("a", item)
|
||||
t.Log("hits1:", item.Week1Hits, "hits2:", item.Week2Hits, "week:", item.Week)
|
||||
logs.PrintAsJSON(list.weekItemMaps, t)
|
||||
|
||||
_ = list.IncreaseHit("a")
|
||||
t.Log("hits1:", item.Week1Hits, "hits2:", item.Week2Hits, "week:", item.Week)
|
||||
logs.PrintAsJSON(list.weekItemMaps, t)
|
||||
|
||||
_ = list.IncreaseHit("a")
|
||||
t.Log("hits1:", item.Week1Hits, "hits2:", item.Week2Hits, "week:", item.Week)
|
||||
logs.PrintAsJSON(list.weekItemMaps, t)
|
||||
count, err = list.Count()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("count items left:", count)
|
||||
}
|
||||
|
||||
func TestMemoryList_CleanAll(t *testing.T) {
|
||||
var list = NewMemoryList().(*MemoryList)
|
||||
var item = &Item{}
|
||||
item.Week = 2705
|
||||
item.Week2Hits = 100
|
||||
|
||||
_ = list.Add("a", &Item{})
|
||||
var list = caches.NewMemoryList().(*caches.MemoryList)
|
||||
_ = list.Add("a", &caches.Item{})
|
||||
_ = list.CleanAll()
|
||||
logs.PrintAsJSON(list.itemMaps, t)
|
||||
logs.PrintAsJSON(list.weekItemMaps, t)
|
||||
logs.PrintAsJSON(list.ItemMaps(), t)
|
||||
t.Log(list.Count())
|
||||
}
|
||||
|
||||
func TestMemoryList_GC(t *testing.T) {
|
||||
list := NewMemoryList().(*MemoryList)
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
list := caches.NewMemoryList().(*caches.MemoryList)
|
||||
_ = list.Init()
|
||||
for i := 0; i < 1_000_000; i++ {
|
||||
key := "https://www.teaos.cn/hello" + strconv.Itoa(i/100000) + "/" + strconv.Itoa(i) + ".html"
|
||||
_ = list.Add(fmt.Sprintf("%d", xxhash.Sum64String(key)), &Item{
|
||||
_ = list.Add(fmt.Sprintf("%d", xxhash.Sum64String(key)), &caches.Item{
|
||||
Key: key,
|
||||
ExpiredAt: 0,
|
||||
ExpiresAt: 0,
|
||||
BodySize: 0,
|
||||
HeaderSize: 0,
|
||||
})
|
||||
}
|
||||
time.Sleep(10 * time.Second)
|
||||
t.Log("clean...", len(list.itemMaps))
|
||||
t.Log("clean...", len(list.ItemMaps()))
|
||||
_ = list.CleanAll()
|
||||
t.Log("cleanAll...", len(list.itemMaps))
|
||||
t.Log("cleanAll...", len(list.ItemMaps()))
|
||||
before := time.Now()
|
||||
//runtime.GC()
|
||||
t.Log("gc cost:", time.Since(before).Seconds()*1000, "ms")
|
||||
@@ -266,3 +298,30 @@ func TestMemoryList_GC(t *testing.T) {
|
||||
time.Sleep(30 * time.Minute)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMemoryList(b *testing.B) {
|
||||
var list = caches.NewMemoryList()
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
for i := 0; i < 1_000_000; i++ {
|
||||
_ = list.Add(stringutil.Md5(types.String(i)), &caches.Item{
|
||||
Key: "a1",
|
||||
ExpiresAt: time.Now().Unix() + 3600,
|
||||
HeaderSize: 1024,
|
||||
})
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
_, _, _ = list.Exist(types.String("a" + types.String(rands.Int(1, 10000))))
|
||||
_ = list.Add("a"+types.String(rands.Int(1, 100000)), &caches.Item{})
|
||||
_, _ = list.Purge(1000, func(hash string) error {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
memutils "github.com/TeaOSLab/EdgeNode/internal/utils/mem"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"golang.org/x/sys/unix"
|
||||
@@ -35,6 +36,9 @@ type Manager struct {
|
||||
SubDiskDirs []*serverconfigs.CacheDir
|
||||
MaxMemoryCapacity *shared.SizeCapacity
|
||||
|
||||
CountFileStorages int
|
||||
CountMemoryStorages int
|
||||
|
||||
policyMap map[int64]*serverconfigs.HTTPCachePolicy // policyId => []*Policy
|
||||
storageMap map[int64]StorageInterface // policyId => *Storage
|
||||
locker sync.RWMutex
|
||||
@@ -143,6 +147,16 @@ func (this *Manager) UpdatePolicies(newPolicies []*serverconfigs.HTTPCachePolicy
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.CountFileStorages = 0
|
||||
this.CountMemoryStorages = 0
|
||||
for _, storage := range this.storageMap {
|
||||
_, isFileStorage := storage.(*FileStorage)
|
||||
this.CountMemoryStorages++
|
||||
if isFileStorage {
|
||||
this.CountFileStorages++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FindPolicy 获取Policy信息
|
||||
@@ -172,6 +186,11 @@ func (this *Manager) NewStorageWithPolicy(policy *serverconfigs.HTTPCachePolicy)
|
||||
return nil
|
||||
}
|
||||
|
||||
// StorageMap 获取已有的存储对象
|
||||
func (this *Manager) StorageMap() map[int64]StorageInterface {
|
||||
return this.storageMap
|
||||
}
|
||||
|
||||
// TotalDiskSize 消耗的磁盘尺寸
|
||||
func (this *Manager) TotalDiskSize() int64 {
|
||||
this.locker.RLock()
|
||||
@@ -256,3 +275,33 @@ func (this *Manager) FindAllStorages() []StorageInterface {
|
||||
}
|
||||
return storages
|
||||
}
|
||||
|
||||
// ScanGarbageCaches 清理目录中“失联”的缓存文件
|
||||
func (this *Manager) ScanGarbageCaches(callback func(path string) error) error {
|
||||
var storages = this.FindAllStorages()
|
||||
for _, storage := range storages {
|
||||
fileStorage, ok := storage.(*FileStorage)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
err := fileStorage.ScanGarbageCaches(callback)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MaxSystemMemoryBytesPerStorage 计算单个策略能使用的系统最大内存
|
||||
func (this *Manager) MaxSystemMemoryBytesPerStorage() int64 {
|
||||
var count = this.CountMemoryStorages
|
||||
if count < 1 {
|
||||
count = 1
|
||||
}
|
||||
|
||||
var resultBytes = int64(memutils.SystemMemoryBytes()) / 3 / int64(count) // 1/3 of the system memory
|
||||
if resultBytes < 1<<30 {
|
||||
resultBytes = 1 << 30
|
||||
}
|
||||
return resultBytes
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package caches
|
||||
package caches_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"testing"
|
||||
)
|
||||
@@ -10,7 +11,7 @@ import (
|
||||
func TestManager_UpdatePolicies(t *testing.T) {
|
||||
{
|
||||
var policies = []*serverconfigs.HTTPCachePolicy{}
|
||||
SharedManager.UpdatePolicies(policies)
|
||||
caches.SharedManager.UpdatePolicies(policies)
|
||||
printManager(t)
|
||||
}
|
||||
|
||||
@@ -38,7 +39,7 @@ func TestManager_UpdatePolicies(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
SharedManager.UpdatePolicies(policies)
|
||||
caches.SharedManager.UpdatePolicies(policies)
|
||||
printManager(t)
|
||||
}
|
||||
|
||||
@@ -66,7 +67,7 @@ func TestManager_UpdatePolicies(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
SharedManager.UpdatePolicies(policies)
|
||||
caches.SharedManager.UpdatePolicies(policies)
|
||||
printManager(t)
|
||||
}
|
||||
}
|
||||
@@ -80,8 +81,8 @@ func TestManager_ChangePolicy_Memory(t *testing.T) {
|
||||
Capacity: &shared.SizeCapacity{Count: 1, Unit: shared.SizeCapacityUnitGB},
|
||||
},
|
||||
}
|
||||
SharedManager.UpdatePolicies(policies)
|
||||
SharedManager.UpdatePolicies([]*serverconfigs.HTTPCachePolicy{
|
||||
caches.SharedManager.UpdatePolicies(policies)
|
||||
caches.SharedManager.UpdatePolicies([]*serverconfigs.HTTPCachePolicy{
|
||||
{
|
||||
Id: 1,
|
||||
Type: serverconfigs.CachePolicyStorageMemory,
|
||||
@@ -102,8 +103,8 @@ func TestManager_ChangePolicy_File(t *testing.T) {
|
||||
Capacity: &shared.SizeCapacity{Count: 1, Unit: shared.SizeCapacityUnitGB},
|
||||
},
|
||||
}
|
||||
SharedManager.UpdatePolicies(policies)
|
||||
SharedManager.UpdatePolicies([]*serverconfigs.HTTPCachePolicy{
|
||||
caches.SharedManager.UpdatePolicies(policies)
|
||||
caches.SharedManager.UpdatePolicies([]*serverconfigs.HTTPCachePolicy{
|
||||
{
|
||||
Id: 1,
|
||||
Type: serverconfigs.CachePolicyStorageFile,
|
||||
@@ -115,10 +116,17 @@ func TestManager_ChangePolicy_File(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestManager_MaxSystemMemoryBytesPerStorage(t *testing.T) {
|
||||
for i := 0; i < 100; i++ {
|
||||
caches.SharedManager.CountMemoryStorages = i
|
||||
t.Log(i, caches.SharedManager.MaxSystemMemoryBytesPerStorage()>>30, "GB")
|
||||
}
|
||||
}
|
||||
|
||||
func printManager(t *testing.T) {
|
||||
t.Log("===manager==")
|
||||
t.Log("storage:")
|
||||
for _, storage := range SharedManager.storageMap {
|
||||
for _, storage := range caches.SharedManager.StorageMap() {
|
||||
t.Log(" storage:", storage.Policy().Id)
|
||||
}
|
||||
t.Log("===============")
|
||||
|
||||
@@ -12,14 +12,16 @@ type OpenFile struct {
|
||||
meta []byte
|
||||
header []byte
|
||||
version int64
|
||||
size int64
|
||||
}
|
||||
|
||||
func NewOpenFile(fp *os.File, meta []byte, header []byte, version int64) *OpenFile {
|
||||
func NewOpenFile(fp *os.File, meta []byte, header []byte, version int64, size int64) *OpenFile {
|
||||
return &OpenFile{
|
||||
fp: fp,
|
||||
meta: meta,
|
||||
header: header,
|
||||
version: version,
|
||||
size: size,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
package caches
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/linkedlist"
|
||||
memutils "github.com/TeaOSLab/EdgeNode/internal/utils/mem"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
@@ -14,26 +16,34 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
maxOpenFileSize = 256 << 20
|
||||
)
|
||||
|
||||
type OpenFileCache struct {
|
||||
poolMap map[string]*OpenFilePool // file path => Pool
|
||||
poolList *linkedlist.List
|
||||
poolList *linkedlist.List[*OpenFilePool]
|
||||
watcher *fsnotify.Watcher
|
||||
|
||||
locker sync.RWMutex
|
||||
|
||||
maxSize int
|
||||
count int
|
||||
maxCount int
|
||||
capacitySize int64
|
||||
|
||||
count int
|
||||
usedSize int64
|
||||
}
|
||||
|
||||
func NewOpenFileCache(maxSize int) (*OpenFileCache, error) {
|
||||
if maxSize <= 0 {
|
||||
maxSize = 16384
|
||||
func NewOpenFileCache(maxCount int) (*OpenFileCache, error) {
|
||||
if maxCount <= 0 {
|
||||
maxCount = 16384
|
||||
}
|
||||
|
||||
var cache = &OpenFileCache{
|
||||
maxSize: maxSize,
|
||||
poolMap: map[string]*OpenFilePool{},
|
||||
poolList: linkedlist.NewList(),
|
||||
maxCount: maxCount,
|
||||
poolMap: map[string]*OpenFilePool{},
|
||||
poolList: linkedlist.NewList[*OpenFilePool](),
|
||||
capacitySize: (int64(memutils.SystemMemoryGB()) << 30) / 16,
|
||||
}
|
||||
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
@@ -54,28 +64,44 @@ func NewOpenFileCache(maxSize int) (*OpenFileCache, error) {
|
||||
}
|
||||
|
||||
func (this *OpenFileCache) Get(filename string) *OpenFile {
|
||||
filename = filepath.Clean(filename)
|
||||
|
||||
this.locker.RLock()
|
||||
pool, ok := this.poolMap[filename]
|
||||
this.locker.RUnlock()
|
||||
if ok {
|
||||
file, consumed := pool.Get()
|
||||
file, consumed, consumedSize := pool.Get()
|
||||
if consumed {
|
||||
this.locker.Lock()
|
||||
this.count--
|
||||
this.usedSize -= consumedSize
|
||||
|
||||
// pool如果为空,也不需要从列表中删除,避免put时需要重新创建
|
||||
|
||||
this.locker.Unlock()
|
||||
}
|
||||
|
||||
return file
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *OpenFileCache) Put(filename string, file *OpenFile) {
|
||||
filename = filepath.Clean(filename)
|
||||
|
||||
if file.size > maxOpenFileSize {
|
||||
return
|
||||
}
|
||||
|
||||
this.locker.Lock()
|
||||
defer this.locker.Unlock()
|
||||
|
||||
// 如果超过当前容量,则关闭最早的
|
||||
if this.count >= this.maxCount || this.usedSize+file.size >= this.capacitySize {
|
||||
this.consumeHead()
|
||||
return
|
||||
}
|
||||
|
||||
pool, ok := this.poolMap[filename]
|
||||
var success bool
|
||||
if ok {
|
||||
@@ -92,39 +118,13 @@ func (this *OpenFileCache) Put(filename string, file *OpenFile) {
|
||||
// 检查长度
|
||||
if success {
|
||||
this.count++
|
||||
|
||||
// 如果超过当前容量,则关闭最早的
|
||||
if this.count > this.maxSize {
|
||||
var delta = this.maxSize / 100 // 清理1%
|
||||
if delta == 0 {
|
||||
delta = 1
|
||||
}
|
||||
for i := 0; i < delta; i++ {
|
||||
var head = this.poolList.Head()
|
||||
if head == nil {
|
||||
break
|
||||
}
|
||||
|
||||
var headPool = head.Value.(*OpenFilePool)
|
||||
headFile, consumed := headPool.Get()
|
||||
if consumed {
|
||||
this.count--
|
||||
if headFile != nil {
|
||||
_ = headFile.Close()
|
||||
}
|
||||
}
|
||||
|
||||
if headPool.Len() == 0 {
|
||||
delete(this.poolMap, headPool.filename)
|
||||
this.poolList.Remove(head)
|
||||
_ = this.watcher.Remove(headPool.filename)
|
||||
}
|
||||
}
|
||||
}
|
||||
this.usedSize += file.size
|
||||
}
|
||||
}
|
||||
|
||||
func (this *OpenFileCache) Close(filename string) {
|
||||
filename = filepath.Clean(filename)
|
||||
|
||||
this.locker.Lock()
|
||||
|
||||
pool, ok := this.poolMap[filename]
|
||||
@@ -136,6 +136,7 @@ func (this *OpenFileCache) Close(filename string) {
|
||||
this.poolList.Remove(pool.linkItem)
|
||||
_ = this.watcher.Remove(filename)
|
||||
this.count -= pool.Len()
|
||||
this.usedSize -= pool.usedSize
|
||||
}
|
||||
|
||||
this.locker.Unlock()
|
||||
@@ -155,18 +156,55 @@ func (this *OpenFileCache) CloseAll() {
|
||||
this.poolList.Reset()
|
||||
_ = this.watcher.Close()
|
||||
this.count = 0
|
||||
this.usedSize = 0
|
||||
this.locker.Unlock()
|
||||
}
|
||||
|
||||
func (this *OpenFileCache) SetCapacity(capacityBytes int64) {
|
||||
this.capacitySize = capacityBytes
|
||||
}
|
||||
|
||||
func (this *OpenFileCache) Debug() {
|
||||
var ticker = time.NewTicker(5 * time.Second)
|
||||
goman.New(func() {
|
||||
for range ticker.C {
|
||||
logs.Println("==== " + types.String(this.count) + " ====")
|
||||
this.poolList.Range(func(item *linkedlist.Item) (goNext bool) {
|
||||
logs.Println(filepath.Base(item.Value.(*OpenFilePool).Filename()), item.Value.(*OpenFilePool).Len())
|
||||
logs.Println("==== " + types.String(this.count) + ", " + fmt.Sprintf("%.4fMB", float64(this.usedSize)/(1<<20)) + " ====")
|
||||
this.poolList.Range(func(item *linkedlist.Item[*OpenFilePool]) (goNext bool) {
|
||||
logs.Println(filepath.Base(item.Value.Filename()), item.Value.Len())
|
||||
return true
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (this *OpenFileCache) consumeHead() {
|
||||
var delta = 1
|
||||
|
||||
if this.count > 100 {
|
||||
delta = 2
|
||||
}
|
||||
|
||||
for i := 0; i < delta; i++ {
|
||||
var head = this.poolList.Head()
|
||||
if head == nil {
|
||||
break
|
||||
}
|
||||
|
||||
var headPool = head.Value
|
||||
headFile, consumed, consumedSize := headPool.Get()
|
||||
if consumed {
|
||||
this.count--
|
||||
this.usedSize -= consumedSize
|
||||
|
||||
if headFile != nil {
|
||||
_ = headFile.Close()
|
||||
}
|
||||
}
|
||||
|
||||
if headPool.Len() == 0 {
|
||||
delete(this.poolMap, headPool.filename)
|
||||
this.poolList.Remove(head)
|
||||
_ = this.watcher.Remove(headPool.filename)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ package caches_test
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@@ -15,13 +16,14 @@ func TestNewOpenFileCache_Close(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cache.Debug()
|
||||
cache.Put("a.txt", caches.NewOpenFile(nil, nil, nil, 0))
|
||||
cache.Put("b.txt", caches.NewOpenFile(nil, nil, nil, 0))
|
||||
cache.Put("b.txt", caches.NewOpenFile(nil, nil, nil, 0))
|
||||
cache.Put("b.txt", caches.NewOpenFile(nil, nil, nil, 0))
|
||||
cache.Put("c.txt", caches.NewOpenFile(nil, nil, nil, 0))
|
||||
cache.Put("a.txt", caches.NewOpenFile(nil, nil, nil, 0, 1<<20))
|
||||
cache.Put("b.txt", caches.NewOpenFile(nil, nil, nil, 0, 1<<20))
|
||||
cache.Put("b.txt", caches.NewOpenFile(nil, nil, nil, 0, 1<<20))
|
||||
cache.Put("b.txt", caches.NewOpenFile(nil, nil, nil, 0, 1<<20))
|
||||
cache.Put("c.txt", caches.NewOpenFile(nil, nil, nil, 0, 1<<20))
|
||||
|
||||
cache.Get("b.txt")
|
||||
cache.Get("d.txt")
|
||||
cache.Get("d.txt") // not exist
|
||||
cache.Close("a.txt")
|
||||
|
||||
if testutils.IsSingleTesting() {
|
||||
@@ -29,18 +31,39 @@ func TestNewOpenFileCache_Close(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewOpenFileCache_OverSize(t *testing.T) {
|
||||
cache, err := caches.NewOpenFileCache(1024)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cache.SetCapacity(1 << 30)
|
||||
|
||||
cache.Debug()
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
cache.Put("a"+types.String(i)+".txt", caches.NewOpenFile(nil, nil, nil, 0, 128<<20))
|
||||
}
|
||||
|
||||
if testutils.IsSingleTesting() {
|
||||
time.Sleep(100 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewOpenFileCache_CloseAll(t *testing.T) {
|
||||
cache, err := caches.NewOpenFileCache(1024)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cache.Debug()
|
||||
cache.Put("a.txt", caches.NewOpenFile(nil, nil, nil, 0))
|
||||
cache.Put("b.txt", caches.NewOpenFile(nil, nil, nil, 0))
|
||||
cache.Put("c.txt", caches.NewOpenFile(nil, nil, nil, 0))
|
||||
cache.Put("a.txt", caches.NewOpenFile(nil, nil, nil, 0, 1))
|
||||
cache.Put("b.txt", caches.NewOpenFile(nil, nil, nil, 0, 1))
|
||||
cache.Put("c.txt", caches.NewOpenFile(nil, nil, nil, 0, 1))
|
||||
cache.Get("b.txt")
|
||||
cache.Get("d.txt")
|
||||
cache.CloseAll()
|
||||
|
||||
time.Sleep(6 * time.Second)
|
||||
if testutils.IsSingleTesting() {
|
||||
time.Sleep(6 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,11 @@ import (
|
||||
|
||||
type OpenFilePool struct {
|
||||
c chan *OpenFile
|
||||
linkItem *linkedlist.Item
|
||||
linkItem *linkedlist.Item[*OpenFilePool]
|
||||
filename string
|
||||
version int64
|
||||
isClosed bool
|
||||
usedSize int64
|
||||
}
|
||||
|
||||
func NewOpenFilePool(filename string) *OpenFilePool {
|
||||
@@ -21,7 +22,7 @@ func NewOpenFilePool(filename string) *OpenFilePool {
|
||||
c: make(chan *OpenFile, 1024),
|
||||
version: fasttime.Now().UnixMilli(),
|
||||
}
|
||||
pool.linkItem = linkedlist.NewItem(pool)
|
||||
pool.linkItem = linkedlist.NewItem[*OpenFilePool](pool)
|
||||
return pool
|
||||
}
|
||||
|
||||
@@ -29,27 +30,29 @@ func (this *OpenFilePool) Filename() string {
|
||||
return this.filename
|
||||
}
|
||||
|
||||
func (this *OpenFilePool) Get() (*OpenFile, bool) {
|
||||
func (this *OpenFilePool) Get() (resultFile *OpenFile, consumed bool, consumedSize int64) {
|
||||
// 如果已经关闭,直接返回
|
||||
if this.isClosed {
|
||||
return nil, false
|
||||
return nil, false, 0
|
||||
}
|
||||
|
||||
select {
|
||||
case file := <-this.c:
|
||||
if file != nil {
|
||||
this.usedSize -= file.size
|
||||
|
||||
err := file.SeekStart()
|
||||
if err != nil {
|
||||
_ = file.Close()
|
||||
return nil, true
|
||||
return nil, true, file.size
|
||||
}
|
||||
file.version = this.version
|
||||
|
||||
return file, true
|
||||
return file, true, file.size
|
||||
}
|
||||
return nil, false
|
||||
return nil, false, 0
|
||||
default:
|
||||
return nil, false
|
||||
return nil, false, 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +72,7 @@ func (this *OpenFilePool) Put(file *OpenFile) bool {
|
||||
// 加入Pool
|
||||
select {
|
||||
case this.c <- file:
|
||||
this.usedSize += file.size
|
||||
return true
|
||||
default:
|
||||
// 多余的直接关闭
|
||||
@@ -81,6 +85,10 @@ func (this *OpenFilePool) Len() int {
|
||||
return len(this.c)
|
||||
}
|
||||
|
||||
func (this *OpenFilePool) TotalSize() int64 {
|
||||
return this.usedSize
|
||||
}
|
||||
|
||||
func (this *OpenFilePool) SetClosing() {
|
||||
this.isClosed = true
|
||||
}
|
||||
|
||||
@@ -13,15 +13,15 @@ func TestOpenFilePool_Get(t *testing.T) {
|
||||
var pool = caches.NewOpenFilePool("a")
|
||||
t.Log(pool.Filename())
|
||||
t.Log(pool.Get())
|
||||
t.Log(pool.Put(caches.NewOpenFile(nil, nil, nil, 0)))
|
||||
t.Log(pool.Put(caches.NewOpenFile(nil, nil, nil, 0, 1)))
|
||||
t.Log(pool.Get())
|
||||
t.Log(pool.Get())
|
||||
}
|
||||
|
||||
func TestOpenFilePool_Close(t *testing.T) {
|
||||
var pool = caches.NewOpenFilePool("a")
|
||||
pool.Put(caches.NewOpenFile(nil, nil, nil, 0))
|
||||
pool.Put(caches.NewOpenFile(nil, nil, nil, 0))
|
||||
pool.Put(caches.NewOpenFile(nil, nil, nil, 0, 1))
|
||||
pool.Put(caches.NewOpenFile(nil, nil, nil, 0, 1))
|
||||
pool.Close()
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ func TestOpenFilePool_Concurrent(t *testing.T) {
|
||||
defer wg.Done()
|
||||
|
||||
if rands.Int(0, 1) == 1 {
|
||||
pool.Put(caches.NewOpenFile(nil, nil, nil, 0))
|
||||
pool.Put(caches.NewOpenFile(nil, nil, nil, 0, 1))
|
||||
}
|
||||
if rands.Int(0, 1) == 0 {
|
||||
pool.Get()
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
@@ -70,7 +69,7 @@ func NewPartialRangesFromJSON(data []byte) (*PartialRanges, error) {
|
||||
|
||||
// NewPartialRangesFromFile 从文件中加载范围信息
|
||||
func NewPartialRangesFromFile(path string) (*PartialRanges, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
data, err := SharedPartialRangesQueue.Get(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -172,7 +171,8 @@ func (this *PartialRanges) Bytes() []byte {
|
||||
|
||||
// WriteToFile 写入到文件中
|
||||
func (this *PartialRanges) WriteToFile(path string) error {
|
||||
return os.WriteFile(path, this.Bytes(), 0666)
|
||||
SharedPartialRangesQueue.Put(path, this.Bytes())
|
||||
return nil
|
||||
}
|
||||
|
||||
// Max 获取最大位置
|
||||
@@ -188,6 +188,11 @@ func (this *PartialRanges) Reset() {
|
||||
this.Ranges = [][2]int64{}
|
||||
}
|
||||
|
||||
// IsCompleted 是否已下载完整
|
||||
func (this *PartialRanges) IsCompleted() bool {
|
||||
return len(this.Ranges) == 1 && this.Ranges[0][0] == 0 && this.Ranges[0][1] == this.BodySize-1
|
||||
}
|
||||
|
||||
func (this *PartialRanges) merge(index int) {
|
||||
// forward
|
||||
var lastIndex = index
|
||||
|
||||
144
internal/caches/partial_ranges_queue.go
Normal file
144
internal/caches/partial_ranges_queue.go
Normal file
@@ -0,0 +1,144 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package caches
|
||||
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fnv"
|
||||
memutils "github.com/TeaOSLab/EdgeNode/internal/utils/mem"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var SharedPartialRangesQueue = NewPartialRangesQueue()
|
||||
|
||||
func init() {
|
||||
if !teaconst.IsMain {
|
||||
return
|
||||
}
|
||||
|
||||
SharedPartialRangesQueue.Start()
|
||||
}
|
||||
|
||||
const partialRangesQueueSharding = 8
|
||||
|
||||
// PartialRangesQueue ranges file writing queue
|
||||
type PartialRangesQueue struct {
|
||||
m [partialRangesQueueSharding]map[string][]byte // { filename => data, ... }
|
||||
|
||||
c chan string // filename1, ...
|
||||
mu [partialRangesQueueSharding]*sync.RWMutex
|
||||
}
|
||||
|
||||
// NewPartialRangesQueue Create new queue
|
||||
func NewPartialRangesQueue() *PartialRangesQueue {
|
||||
var queueSize = 512
|
||||
var memGB = memutils.SystemMemoryGB()
|
||||
if memGB > 16 {
|
||||
queueSize = 8 << 10
|
||||
} else if memGB > 8 {
|
||||
queueSize = 4 << 10
|
||||
} else if memGB > 4 {
|
||||
queueSize = 2 << 10
|
||||
} else if memGB > 2 {
|
||||
queueSize = 1 << 10
|
||||
}
|
||||
|
||||
var m = [partialRangesQueueSharding]map[string][]byte{}
|
||||
var muList = [partialRangesQueueSharding]*sync.RWMutex{}
|
||||
for i := 0; i < partialRangesQueueSharding; i++ {
|
||||
muList[i] = &sync.RWMutex{}
|
||||
m[i] = map[string][]byte{}
|
||||
}
|
||||
|
||||
return &PartialRangesQueue{
|
||||
mu: muList,
|
||||
m: m,
|
||||
c: make(chan string, queueSize),
|
||||
}
|
||||
}
|
||||
|
||||
// Start the queue
|
||||
func (this *PartialRangesQueue) Start() {
|
||||
goman.New(func() {
|
||||
this.Dump()
|
||||
})
|
||||
}
|
||||
|
||||
// Put ranges data to filename
|
||||
func (this *PartialRangesQueue) Put(filename string, data []byte) {
|
||||
var index = this.indexForKey(filename)
|
||||
|
||||
this.mu[index].Lock()
|
||||
this.m[index][filename] = data
|
||||
this.mu[index].Unlock()
|
||||
|
||||
// always wait to finish
|
||||
this.c <- filename
|
||||
}
|
||||
|
||||
// Get ranges data from filename
|
||||
func (this *PartialRangesQueue) Get(filename string) ([]byte, error) {
|
||||
var index = this.indexForKey(filename)
|
||||
|
||||
this.mu[index].RLock()
|
||||
data, ok := this.m[index][filename]
|
||||
this.mu[index].RUnlock()
|
||||
|
||||
if ok {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
return os.ReadFile(filename)
|
||||
}
|
||||
|
||||
// Delete ranges filename
|
||||
func (this *PartialRangesQueue) Delete(filename string) {
|
||||
var index = this.indexForKey(filename)
|
||||
|
||||
this.mu[index].Lock()
|
||||
delete(this.m[index], filename)
|
||||
this.mu[index].Unlock()
|
||||
}
|
||||
|
||||
// Dump ranges to filename from memory
|
||||
func (this *PartialRangesQueue) Dump() {
|
||||
for filename := range this.c {
|
||||
var index = this.indexForKey(filename)
|
||||
|
||||
this.mu[index].Lock()
|
||||
data, ok := this.m[index][filename]
|
||||
if ok {
|
||||
delete(this.m[index], filename)
|
||||
}
|
||||
this.mu[index].Unlock()
|
||||
|
||||
if !ok || len(data) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
err := os.WriteFile(filename, data, 0666)
|
||||
if err != nil {
|
||||
remotelogs.Println("PARTIAL_RANGES_QUEUE", "write file '"+filename+"' failed: "+err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Len count all files
|
||||
func (this *PartialRangesQueue) Len() int {
|
||||
var count int
|
||||
|
||||
for i := 0; i < partialRangesQueueSharding; i++ {
|
||||
this.mu[i].RLock()
|
||||
count += len(this.m[i])
|
||||
this.mu[i].RUnlock()
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
|
||||
func (this *PartialRangesQueue) indexForKey(filename string) int {
|
||||
return int(fnv.HashString(filename) % partialRangesQueueSharding)
|
||||
}
|
||||
31
internal/caches/partial_ranges_queue_test.go
Normal file
31
internal/caches/partial_ranges_queue_test.go
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package caches_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||
"github.com/iwind/TeaGo/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewPartialRangesQueue(t *testing.T) {
|
||||
var a = assert.NewAssertion(t)
|
||||
|
||||
var queue = caches.NewPartialRangesQueue()
|
||||
queue.Put("a", []byte{1, 2, 3})
|
||||
t.Log("add 'a':", queue.Len())
|
||||
t.Log(queue.Get("a"))
|
||||
a.IsTrue(queue.Len() == 1)
|
||||
|
||||
queue.Put("a", nil)
|
||||
t.Log("add 'a':", queue.Len())
|
||||
a.IsTrue(queue.Len() == 1)
|
||||
|
||||
queue.Put("b", nil)
|
||||
t.Log("add 'b':", queue.Len())
|
||||
a.IsTrue(queue.Len() == 2)
|
||||
|
||||
queue.Delete("a")
|
||||
t.Log("delete 'a':", queue.Len())
|
||||
a.IsTrue(queue.Len() == 1)
|
||||
}
|
||||
@@ -366,7 +366,7 @@ func (this *FileReader) Close() error {
|
||||
} else {
|
||||
var cacheMeta = make([]byte, len(this.meta))
|
||||
copy(cacheMeta, this.meta)
|
||||
this.openFileCache.Put(this.fp.Name(), NewOpenFile(this.fp, cacheMeta, this.header, this.LastModified()))
|
||||
this.openFileCache.Put(this.fp.Name(), NewOpenFile(this.fp, cacheMeta, this.header, this.LastModified(), this.bodySize))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
18
internal/caches/reader_file_mmap.go
Normal file
18
internal/caches/reader_file_mmap.go
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build !plus
|
||||
|
||||
package caches
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
type MMAPFileReader struct {
|
||||
FileReader
|
||||
}
|
||||
|
||||
func (this *MMAPFileReader) CopyBodyTo(writer io.Writer) (int, error) {
|
||||
// stub
|
||||
return 0, errors.New("not implemented")
|
||||
}
|
||||
@@ -20,7 +20,7 @@ type PartialFileReader struct {
|
||||
func NewPartialFileReader(fp *os.File) *PartialFileReader {
|
||||
return &PartialFileReader{
|
||||
FileReader: NewFileReader(fp),
|
||||
rangePath: partialRangesFilePath(fp.Name()),
|
||||
rangePath: PartialRangesFilePath(fp.Name()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ func (this *PartialFileReader) InitAutoDiscard(autoDiscard bool) error {
|
||||
this.header = this.openFile.header
|
||||
}
|
||||
|
||||
isOk := false
|
||||
var isOk = false
|
||||
|
||||
if autoDiscard {
|
||||
defer func() {
|
||||
@@ -54,9 +54,9 @@ func (this *PartialFileReader) InitAutoDiscard(autoDiscard bool) error {
|
||||
var buf = this.meta
|
||||
if len(buf) == 0 {
|
||||
buf = make([]byte, SizeMeta)
|
||||
ok, err := this.readToBuff(this.fp, buf)
|
||||
if err != nil {
|
||||
return err
|
||||
ok, readErr := this.readToBuff(this.fp, buf)
|
||||
if readErr != nil {
|
||||
return readErr
|
||||
}
|
||||
if !ok {
|
||||
return ErrNotFound
|
||||
@@ -73,10 +73,10 @@ func (this *PartialFileReader) InitAutoDiscard(autoDiscard bool) error {
|
||||
this.status = status
|
||||
|
||||
// URL
|
||||
urlLength := binary.BigEndian.Uint32(buf[SizeExpiresAt+SizeStatus : SizeExpiresAt+SizeStatus+SizeURLLength])
|
||||
var urlLength = binary.BigEndian.Uint32(buf[SizeExpiresAt+SizeStatus : SizeExpiresAt+SizeStatus+SizeURLLength])
|
||||
|
||||
// header
|
||||
headerSize := int(binary.BigEndian.Uint32(buf[SizeExpiresAt+SizeStatus+SizeURLLength : SizeExpiresAt+SizeStatus+SizeURLLength+SizeHeaderLength]))
|
||||
var headerSize = int(binary.BigEndian.Uint32(buf[SizeExpiresAt+SizeStatus+SizeURLLength : SizeExpiresAt+SizeStatus+SizeURLLength+SizeHeaderLength]))
|
||||
if headerSize == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -96,7 +96,7 @@ func (this *PartialFileReader) InitAutoDiscard(autoDiscard bool) error {
|
||||
if this.openFileCache != nil && len(this.header) == 0 {
|
||||
if headerSize > 0 && headerSize <= 512 {
|
||||
this.header = make([]byte, headerSize)
|
||||
_, err := this.fp.Seek(this.headerOffset, io.SeekStart)
|
||||
_, err = this.fp.Seek(this.headerOffset, io.SeekStart)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -140,7 +140,13 @@ func (this *PartialFileReader) Ranges() *PartialRanges {
|
||||
return this.ranges
|
||||
}
|
||||
|
||||
func (this *PartialFileReader) IsCompleted() bool {
|
||||
return this.ranges != nil && this.ranges.IsCompleted()
|
||||
}
|
||||
|
||||
func (this *PartialFileReader) discard() error {
|
||||
SharedPartialRangesQueue.Delete(this.rangePath)
|
||||
_ = os.Remove(this.rangePath)
|
||||
|
||||
return this.FileReader.discard()
|
||||
}
|
||||
|
||||
@@ -14,15 +14,17 @@ import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/trackers"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs"
|
||||
memutils "github.com/TeaOSLab/EdgeNode/internal/utils/mem"
|
||||
setutils "github.com/TeaOSLab/EdgeNode/internal/utils/sets"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/sizes"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
"github.com/iwind/gosock/pkg/gosock"
|
||||
"github.com/shirou/gopsutil/v3/load"
|
||||
"math"
|
||||
"os"
|
||||
@@ -52,14 +54,30 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
FileStorageMaxIgnoreKeys = 32768 // 最大可忽略的键值数(尺寸过大的键值)
|
||||
HotItemSize = 1024 // 热点数据数量
|
||||
HotItemLifeSeconds int64 = 3600 // 热点数据生命周期
|
||||
FileToMemoryMaxSize = 32 * sizes.M // 可以从文件写入到内存的最大文件尺寸
|
||||
FileStorageMaxIgnoreKeys = 32768 // 最大可忽略的键值数(尺寸过大的键值)
|
||||
HotItemSize = 1024 // 热点数据数量
|
||||
HotItemLifeSeconds int64 = 3600 // 热点数据生命周期
|
||||
FileTmpSuffix = ".tmp"
|
||||
DefaultMinDiskFreeSpace uint64 = 5 << 30 // 当前磁盘最小剩余空间
|
||||
DefaultStaleCacheSeconds = 1200 // 过时缓存留存时间
|
||||
HashKeyLength = 32
|
||||
)
|
||||
|
||||
var FileToMemoryMaxSize int64 = 32 << 20 // 可以从文件写入到内存的最大文件尺寸
|
||||
|
||||
func init() {
|
||||
var availableMemoryGB = memutils.AvailableMemoryGB()
|
||||
if availableMemoryGB > 64 {
|
||||
FileToMemoryMaxSize = 512 << 20
|
||||
} else if availableMemoryGB > 32 {
|
||||
FileToMemoryMaxSize = 256 << 20
|
||||
} else if availableMemoryGB > 16 {
|
||||
FileToMemoryMaxSize = 128 << 20
|
||||
} else if availableMemoryGB > 8 {
|
||||
FileToMemoryMaxSize = 64 << 20
|
||||
}
|
||||
}
|
||||
|
||||
var sharedWritingFileKeyMap = map[string]zero.Zero{} // key => bool
|
||||
var sharedWritingFileKeyLocker = sync.Mutex{}
|
||||
|
||||
@@ -85,7 +103,8 @@ type FileStorage struct {
|
||||
|
||||
openFileCache *OpenFileCache
|
||||
|
||||
mainDiskIsFull bool
|
||||
mainDiskIsFull bool
|
||||
mainDiskTotalSize uint64
|
||||
|
||||
subDirs []*FileDir
|
||||
}
|
||||
@@ -106,12 +125,21 @@ func (this *FileStorage) Policy() *serverconfigs.HTTPCachePolicy {
|
||||
|
||||
// CanUpdatePolicy 检查策略是否可以更新
|
||||
func (this *FileStorage) CanUpdatePolicy(newPolicy *serverconfigs.HTTPCachePolicy) bool {
|
||||
if newPolicy == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查类型
|
||||
if newPolicy.Type != serverconfigs.CachePolicyStorageFile {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查路径是否有变化
|
||||
oldOptionsJSON, err := json.Marshal(this.policy.Options)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
var oldOptions = &serverconfigs.HTTPFileCacheStorage{}
|
||||
var oldOptions = serverconfigs.NewHTTPFileCacheStorage()
|
||||
err = json.Unmarshal(oldOptionsJSON, oldOptions)
|
||||
if err != nil {
|
||||
return false
|
||||
@@ -121,7 +149,7 @@ func (this *FileStorage) CanUpdatePolicy(newPolicy *serverconfigs.HTTPCachePolic
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
var newOptions = &serverconfigs.HTTPFileCacheStorage{}
|
||||
var newOptions = serverconfigs.NewHTTPFileCacheStorage()
|
||||
err = json.Unmarshal(newOptionsJSON, newOptions)
|
||||
if err != nil {
|
||||
return false
|
||||
@@ -144,7 +172,7 @@ func (this *FileStorage) UpdatePolicy(newPolicy *serverconfigs.HTTPCachePolicy)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var newOptions = &serverconfigs.HTTPFileCacheStorage{}
|
||||
var newOptions = serverconfigs.NewHTTPFileCacheStorage()
|
||||
err = json.Unmarshal(newOptionsJSON, newOptions)
|
||||
if err != nil {
|
||||
remotelogs.Error("CACHE", "update policy '"+types.String(this.policy.Id)+"' failed: decode options failed: "+err.Error())
|
||||
@@ -209,7 +237,7 @@ func (this *FileStorage) Init() error {
|
||||
var before = time.Now()
|
||||
|
||||
// 配置
|
||||
var options = &serverconfigs.HTTPFileCacheStorage{}
|
||||
var options = serverconfigs.NewHTTPFileCacheStorage()
|
||||
optionsJSON, err := json.Marshal(this.policy.Options)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -244,12 +272,25 @@ func (this *FileStorage) Init() error {
|
||||
return errors.New("[CACHE]cache storage dir can not be empty")
|
||||
}
|
||||
|
||||
var list = NewFileList(dir + "/p" + types.String(this.policy.Id) + "/.indexes")
|
||||
err = list.Init()
|
||||
if err != nil {
|
||||
return err
|
||||
// read list
|
||||
var list ListInterface
|
||||
var sqliteIndexesDir = dir + "/p" + types.String(this.policy.Id) + "/.indexes"
|
||||
_, sqliteIndexesDirErr := os.Stat(sqliteIndexesDir)
|
||||
if sqliteIndexesDirErr == nil || !teaconst.EnableKVCacheStore {
|
||||
list = NewSQLiteFileList(sqliteIndexesDir)
|
||||
err = list.Init()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
list.(*SQLiteFileList).SetOldDir(dir + "/p" + types.String(this.policy.Id))
|
||||
} else {
|
||||
list = NewKVFileList(dir + "/p" + types.String(this.policy.Id) + "/.stores")
|
||||
err = list.Init()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
list.(*FileList).SetOldDir(dir + "/p" + types.String(this.policy.Id))
|
||||
|
||||
this.list = list
|
||||
|
||||
// 检查目录是否存在
|
||||
@@ -270,14 +311,19 @@ func (this *FileStorage) Init() error {
|
||||
var totalSize = this.TotalDiskSize()
|
||||
var cost = time.Since(before).Seconds() * 1000
|
||||
var sizeMB = types.String(totalSize) + " Bytes"
|
||||
if totalSize > 1*sizes.G {
|
||||
sizeMB = fmt.Sprintf("%.3f G", float64(totalSize)/float64(sizes.G))
|
||||
} else if totalSize > 1*sizes.M {
|
||||
sizeMB = fmt.Sprintf("%.3f M", float64(totalSize)/float64(sizes.M))
|
||||
} else if totalSize > 1*sizes.K {
|
||||
sizeMB = fmt.Sprintf("%.3f K", float64(totalSize)/float64(sizes.K))
|
||||
if totalSize > (1 << 30) {
|
||||
sizeMB = fmt.Sprintf("%.3f GiB", float64(totalSize)/(1<<30))
|
||||
} else if totalSize > (1 << 20) {
|
||||
sizeMB = fmt.Sprintf("%.3f MiB", float64(totalSize)/(1<<20))
|
||||
} else if totalSize > (1 << 10) {
|
||||
sizeMB = fmt.Sprintf("%.3f KiB", float64(totalSize)/(1<<10))
|
||||
}
|
||||
remotelogs.Println("CACHE", "init policy "+types.String(this.policy.Id)+" from '"+this.options.Dir+"', cost: "+fmt.Sprintf("%.2f", cost)+" ms, size: "+sizeMB)
|
||||
|
||||
var mmapTag = "disabled"
|
||||
if this.options.EnableMMAP {
|
||||
mmapTag = "enabled"
|
||||
}
|
||||
remotelogs.Println("CACHE", "init policy "+types.String(this.policy.Id)+" from '"+this.options.Dir+"', cost: "+fmt.Sprintf("%.2f", cost)+" ms, size: "+sizeMB+", mmap: "+mmapTag)
|
||||
}()
|
||||
|
||||
// 初始化list
|
||||
@@ -333,17 +379,29 @@ func (this *FileStorage) openReader(key string, allowMemory bool, useStale bool,
|
||||
hash, path, _ := this.keyPath(key)
|
||||
|
||||
// 检查文件记录是否已过期
|
||||
var estimatedSize int64
|
||||
if !useStale {
|
||||
exists, err := this.list.Exist(hash)
|
||||
exists, filesize, err := this.list.Exist(hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !exists {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
estimatedSize = filesize
|
||||
}
|
||||
|
||||
// 尝试通过MMAP读取
|
||||
if estimatedSize > 0 {
|
||||
reader, err := this.tryMMAPReader(isPartial, estimatedSize, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if reader != nil {
|
||||
return reader, nil
|
||||
}
|
||||
}
|
||||
|
||||
// TODO 尝试使用mmap加快读取速度
|
||||
var isOk = false
|
||||
var openFile *OpenFile
|
||||
var openFileCache = this.openFileCache // 因为中间可能有修改,所以先赋值再获取
|
||||
@@ -351,6 +409,7 @@ func (this *FileStorage) openReader(key string, allowMemory bool, useStale bool,
|
||||
openFile = openFileCache.Get(path)
|
||||
}
|
||||
var fp *os.File
|
||||
|
||||
var err error
|
||||
if openFile == nil {
|
||||
fp, err = os.OpenFile(path, os.O_RDONLY, 0444)
|
||||
@@ -382,6 +441,7 @@ func (this *FileStorage) openReader(key string, allowMemory bool, useStale bool,
|
||||
fileReader.openFileCache = openFileCache
|
||||
reader = fileReader
|
||||
}
|
||||
|
||||
err = reader.Init()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -418,6 +478,13 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
|
||||
return nil, ErrEntityTooLarge
|
||||
}
|
||||
|
||||
// 检查磁盘是否超出容量
|
||||
// 需要在内存缓存之前执行,避免成功写进到了内存缓存,但无法刷到磁盘
|
||||
var capacityBytes = this.diskCapacityBytes()
|
||||
if capacityBytes > 0 && capacityBytes <= this.TotalDiskSize()+(32<<20 /** 余量 **/) {
|
||||
return nil, NewCapacityError("write file cache failed: over disk size, current: " + types.String(this.TotalDiskSize()) + ", capacity: " + types.String(capacityBytes))
|
||||
}
|
||||
|
||||
// 先尝试内存缓存
|
||||
// 我们限定仅小文件优先存在内存中
|
||||
var maxMemorySize = FileToMemoryMaxSize
|
||||
@@ -425,14 +492,18 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
|
||||
maxMemorySize = maxSize
|
||||
}
|
||||
var memoryStorage = this.memoryStorage
|
||||
if !fsutils.DiskIsExtremelyFast() && !isFlushing && !isPartial && memoryStorage != nil && ((bodySize > 0 && bodySize < maxMemorySize) || bodySize < 0) {
|
||||
if !isFlushing && !isPartial && memoryStorage != nil && ((bodySize > 0 && bodySize < maxMemorySize) || bodySize < 0) {
|
||||
writer, err := memoryStorage.OpenWriter(key, expiredAt, status, headerSize, bodySize, maxMemorySize, false)
|
||||
if err == nil {
|
||||
return writer, nil
|
||||
}
|
||||
|
||||
// 如果队列满了,则等待
|
||||
if err == ErrWritingQueueFull {
|
||||
if errors.Is(err, ErrWritingQueueFull) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if IsCapacityError(err) && bodySize > 0 && memoryStorage.totalDirtySize > (128<<20) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@@ -443,7 +514,7 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
|
||||
_, ok := sharedWritingFileKeyMap[key]
|
||||
if ok {
|
||||
sharedWritingFileKeyLocker.Unlock()
|
||||
return nil, ErrFileIsWriting
|
||||
return nil, fmt.Errorf("%w(001)", ErrFileIsWriting)
|
||||
}
|
||||
|
||||
if !isFlushing && !fsutils.WriteReady() {
|
||||
@@ -461,12 +532,6 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
|
||||
}
|
||||
}()
|
||||
|
||||
// 检查是否超出容量
|
||||
var capacityBytes = this.diskCapacityBytes()
|
||||
if capacityBytes > 0 && capacityBytes <= this.TotalDiskSize() {
|
||||
return nil, NewCapacityError("write file cache failed: over disk size, current total size: " + types.String(this.TotalDiskSize()) + " bytes, capacity: " + types.String(capacityBytes))
|
||||
}
|
||||
|
||||
var hash = stringutil.Md5(key)
|
||||
|
||||
dir, diskIsFull := this.subDir(hash)
|
||||
@@ -490,7 +555,7 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
|
||||
// 检查两次写入缓存的时间是否过于相近,分片内容不受此限制
|
||||
if err == nil && !isPartial && time.Since(stat.ModTime()) <= 1*time.Second {
|
||||
// 防止并发连续写入
|
||||
return nil, ErrFileIsWriting
|
||||
return nil, fmt.Errorf("%w(002)", ErrFileIsWriting)
|
||||
}
|
||||
|
||||
// 构造文件名
|
||||
@@ -521,7 +586,7 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
|
||||
var partialRanges *PartialRanges
|
||||
if isPartial {
|
||||
// 数据库中是否存在
|
||||
existsCacheItem, _ := this.list.Exist(hash)
|
||||
existsCacheItem, _, _ := this.list.Exist(hash)
|
||||
if existsCacheItem {
|
||||
readerFp, err := os.OpenFile(tmpPath, os.O_RDONLY, 0444)
|
||||
if err == nil {
|
||||
@@ -556,9 +621,10 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
|
||||
if isNewCreated && existsFile {
|
||||
flags |= os.O_TRUNC
|
||||
}
|
||||
fsutils.WriteBegin()
|
||||
writer, err := os.OpenFile(tmpPath, flags, 0666)
|
||||
fsutils.WriteEnd()
|
||||
if err != nil {
|
||||
// TODO 检查在各个系统中的稳定性
|
||||
if os.IsNotExist(err) {
|
||||
_ = os.MkdirAll(dir, 0777)
|
||||
|
||||
@@ -589,7 +655,12 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
|
||||
err = syscall.Flock(int(writer.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
|
||||
if err != nil {
|
||||
removeOnFailure = false
|
||||
return nil, ErrFileIsWriting
|
||||
return nil, fmt.Errorf("%w (003)", ErrFileIsWriting)
|
||||
}
|
||||
|
||||
// 关闭
|
||||
if openFileCache != nil {
|
||||
openFileCache.Close(cachePath)
|
||||
}
|
||||
|
||||
var metaBodySize int64 = -1
|
||||
@@ -890,7 +961,7 @@ func (this *FileStorage) Stop() {
|
||||
|
||||
// TotalDiskSize 消耗的磁盘尺寸
|
||||
func (this *FileStorage) TotalDiskSize() int64 {
|
||||
stat, err := fsutils.StatCache(this.options.Dir)
|
||||
stat, err := fsutils.StatDeviceCache(this.options.Dir)
|
||||
if err == nil {
|
||||
return int64(stat.UsedSize())
|
||||
}
|
||||
@@ -930,7 +1001,7 @@ func (this *FileStorage) keyPath(key string) (hash string, path string, diskIsFu
|
||||
|
||||
// 获取Hash对应的文件路径
|
||||
func (this *FileStorage) hashPath(hash string) (path string, diskIsFull bool) {
|
||||
if len(hash) != 32 {
|
||||
if len(hash) != HashKeyLength {
|
||||
return "", false
|
||||
}
|
||||
var dir string
|
||||
@@ -994,28 +1065,31 @@ func (this *FileStorage) initList() error {
|
||||
// 清理任务
|
||||
// TODO purge每个分区
|
||||
func (this *FileStorage) purgeLoop() {
|
||||
// 检查磁盘剩余空间
|
||||
this.checkDiskSpace()
|
||||
// load
|
||||
systemLoad, _ := load.Avg()
|
||||
|
||||
// TODO 计算平均最近每日新增用量
|
||||
|
||||
// 计算是否应该开启LFU清理
|
||||
var capacityBytes = this.diskCapacityBytes()
|
||||
var startLFU = false
|
||||
var requireFullLFU = false // 是否需要完整执行LFU
|
||||
var lfuFreePercent = this.policy.PersistenceLFUFreePercent
|
||||
if lfuFreePercent <= 0 {
|
||||
lfuFreePercent = 5
|
||||
}
|
||||
|
||||
var hasFullDisk = this.mainDiskIsFull
|
||||
if !hasFullDisk {
|
||||
var subDirs = this.subDirs // copy slice
|
||||
for _, subDir := range subDirs {
|
||||
if subDir.IsFull {
|
||||
hasFullDisk = true
|
||||
break
|
||||
if systemLoad == nil || systemLoad.Load5 > 10 {
|
||||
// 2TB级别以上
|
||||
if capacityBytes>>30 > 2000 {
|
||||
lfuFreePercent = 100 /** GB **/ / float32(capacityBytes>>30) * 100 /** % **/
|
||||
if lfuFreePercent > 3 {
|
||||
lfuFreePercent = 3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var hasFullDisk = this.hasFullDisk()
|
||||
if hasFullDisk {
|
||||
startLFU = true
|
||||
} else {
|
||||
@@ -1034,30 +1108,45 @@ func (this *FileStorage) purgeLoop() {
|
||||
var times = 1
|
||||
|
||||
// 空闲时间多清理
|
||||
systemLoad, _ := load.Avg()
|
||||
if systemLoad != nil {
|
||||
if systemLoad.Load5 < 2 {
|
||||
if systemLoad.Load5 < 3 {
|
||||
times = 5
|
||||
} else if systemLoad.Load5 < 3 {
|
||||
times = 3
|
||||
} else if systemLoad.Load5 < 5 {
|
||||
times = 3
|
||||
} else if systemLoad.Load5 < 10 {
|
||||
times = 2
|
||||
}
|
||||
}
|
||||
|
||||
// 高速硬盘多清理
|
||||
if fsutils.DiskIsExtremelyFast() {
|
||||
times *= 8
|
||||
} else if fsutils.DiskIsFast() {
|
||||
times *= 4
|
||||
}
|
||||
|
||||
// 处于LFU阈值时,多清理
|
||||
if startLFU {
|
||||
times = 5
|
||||
times *= 5
|
||||
}
|
||||
|
||||
var purgeCount = this.policy.PersistenceAutoPurgeCount
|
||||
if purgeCount <= 0 {
|
||||
purgeCount = 1000
|
||||
|
||||
if fsutils.DiskIsExtremelyFast() {
|
||||
purgeCount = 4000
|
||||
} else if fsutils.DiskIsFast() {
|
||||
purgeCount = 2000
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < times; i++ {
|
||||
countFound, err := this.list.Purge(purgeCount, func(hash string) error {
|
||||
path, _ := this.hashPath(hash)
|
||||
fsutils.WriteBegin()
|
||||
err := this.removeCacheFile(path)
|
||||
fsutils.WriteEnd()
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
remotelogs.Error("CACHE", "purge '"+path+"' error: "+err.Error())
|
||||
}
|
||||
@@ -1070,6 +1159,10 @@ func (this *FileStorage) purgeLoop() {
|
||||
}
|
||||
|
||||
if countFound < purgeCount {
|
||||
if i == 0 && startLFU {
|
||||
requireFullLFU = true
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
@@ -1079,28 +1172,61 @@ func (this *FileStorage) purgeLoop() {
|
||||
|
||||
// 磁盘空间不足时,清除老旧的缓存
|
||||
if startLFU {
|
||||
var maxCount = 1000
|
||||
var maxLoops = 5
|
||||
|
||||
if fsutils.DiskIsExtremelyFast() {
|
||||
maxCount = 4000
|
||||
} else if fsutils.DiskIsFast() {
|
||||
maxCount = 2000
|
||||
}
|
||||
|
||||
var total, _ = this.list.Count()
|
||||
if total > 0 {
|
||||
var count = types.Int(math.Ceil(float64(total) * float64(lfuFreePercent*2) / 100))
|
||||
if count > 0 {
|
||||
// 限制单次清理的条数,防止占用太多系统资源
|
||||
if count > 2000 {
|
||||
count = 2000
|
||||
for {
|
||||
maxLoops--
|
||||
if maxLoops <= 0 {
|
||||
break
|
||||
}
|
||||
|
||||
remotelogs.Println("CACHE", "LFU purge policy '"+this.policy.Name+"' id: "+types.String(this.policy.Id)+", count: "+types.String(count))
|
||||
// 开始清理
|
||||
var count = types.Int(math.Ceil(float64(total) * float64(lfuFreePercent*2) / 100))
|
||||
if count <= 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// 限制单次清理的条数,防止占用太多系统资源
|
||||
if count > maxCount {
|
||||
count = maxCount
|
||||
}
|
||||
|
||||
var before = time.Now()
|
||||
err := this.list.PurgeLFU(count, func(hash string) error {
|
||||
path, _ := this.hashPath(hash)
|
||||
fsutils.WriteBegin()
|
||||
err := this.removeCacheFile(path)
|
||||
fsutils.WriteEnd()
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
remotelogs.Error("CACHE", "purge '"+path+"' error: "+err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
var prefix = ""
|
||||
if requireFullLFU {
|
||||
prefix = "fully "
|
||||
}
|
||||
remotelogs.Println("CACHE", prefix+"LFU purge policy '"+this.policy.Name+"' id: "+types.String(this.policy.Id)+", count: "+types.String(count)+", cost: "+fmt.Sprintf("%.2fms", time.Since(before).Seconds()*1000))
|
||||
|
||||
if err != nil {
|
||||
remotelogs.Warn("CACHE", "purge file storage in LFU failed: "+err.Error())
|
||||
}
|
||||
|
||||
// 检查硬盘空间状态
|
||||
if !requireFullLFU && !this.hasFullDisk() {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1108,11 +1234,16 @@ func (this *FileStorage) purgeLoop() {
|
||||
|
||||
// 热点数据任务
|
||||
func (this *FileStorage) hotLoop() {
|
||||
var memoryStorage = this.memoryStorage
|
||||
var memoryStorage = this.memoryStorage // copy
|
||||
if memoryStorage == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// check memory space size
|
||||
if !memoryStorage.HasFreeSpaceForHotItems() {
|
||||
return
|
||||
}
|
||||
|
||||
this.hotMapLocker.Lock()
|
||||
if len(this.hotMap) == 0 {
|
||||
this.hotMapLocker.Unlock()
|
||||
@@ -1124,6 +1255,9 @@ func (this *FileStorage) hotLoop() {
|
||||
|
||||
var result = []*HotItem{} // [ {key: ..., hits: ...}, ... ]
|
||||
for _, v := range this.hotMap {
|
||||
if v.Hits <= 1 {
|
||||
continue
|
||||
}
|
||||
result = append(result, v)
|
||||
}
|
||||
|
||||
@@ -1143,6 +1277,7 @@ func (this *FileStorage) hotLoop() {
|
||||
}
|
||||
|
||||
var buf = utils.BytePool16k.Get()
|
||||
|
||||
defer utils.BytePool16k.Put(buf)
|
||||
for _, item := range result[:size] {
|
||||
reader, err := this.openReader(item.Key, false, false, false)
|
||||
@@ -1184,8 +1319,8 @@ func (this *FileStorage) hotLoop() {
|
||||
continue
|
||||
}
|
||||
|
||||
err = reader.ReadHeader(buf, func(n int) (goNext bool, err error) {
|
||||
_, err = writer.WriteHeader(buf[:n])
|
||||
err = reader.ReadHeader(buf.Bytes, func(n int) (goNext bool, err error) {
|
||||
_, err = writer.WriteHeader(buf.Bytes[:n])
|
||||
return
|
||||
})
|
||||
if err != nil {
|
||||
@@ -1194,10 +1329,10 @@ func (this *FileStorage) hotLoop() {
|
||||
continue
|
||||
}
|
||||
|
||||
err = reader.ReadBody(buf, func(n int) (goNext bool, err error) {
|
||||
err = reader.ReadBody(buf.Bytes, func(n int) (goNext bool, err error) {
|
||||
goNext = true
|
||||
if n > 0 {
|
||||
_, err = writer.Write(buf[:n])
|
||||
_, err = writer.Write(buf.Bytes[:n])
|
||||
if err != nil {
|
||||
goNext = false
|
||||
}
|
||||
@@ -1214,7 +1349,7 @@ func (this *FileStorage) hotLoop() {
|
||||
Type: writer.ItemType(),
|
||||
Key: item.Key,
|
||||
Host: ParseHost(item.Key),
|
||||
ExpiredAt: expiresAt,
|
||||
ExpiresAt: expiresAt,
|
||||
HeaderSize: writer.HeaderSize(),
|
||||
BodySize: writer.BodySize(),
|
||||
})
|
||||
@@ -1227,12 +1362,21 @@ func (this *FileStorage) hotLoop() {
|
||||
|
||||
func (this *FileStorage) diskCapacityBytes() int64 {
|
||||
var c1 = this.policy.CapacityBytes()
|
||||
if SharedManager.MaxDiskCapacity != nil {
|
||||
var c2 = SharedManager.MaxDiskCapacity.Bytes()
|
||||
var nodeCapacity = SharedManager.MaxDiskCapacity // copy
|
||||
if nodeCapacity != nil {
|
||||
var c2 = nodeCapacity.Bytes()
|
||||
if c2 > 0 {
|
||||
if this.mainDiskTotalSize > 0 && c2 >= int64(this.mainDiskTotalSize) {
|
||||
c2 = int64(this.mainDiskTotalSize) * 95 / 100 // keep 5% free
|
||||
}
|
||||
return c2
|
||||
}
|
||||
}
|
||||
|
||||
if c1 <= 0 || (this.mainDiskTotalSize > 0 && c1 >= int64(this.mainDiskTotalSize)) {
|
||||
c1 = int64(this.mainDiskTotalSize) * 95 / 100 // keep 5% free
|
||||
}
|
||||
|
||||
return c1
|
||||
}
|
||||
|
||||
@@ -1291,22 +1435,12 @@ func (this *FileStorage) increaseHit(key string, hash string, reader Reader) {
|
||||
if rate <= 0 {
|
||||
rate = 1000
|
||||
}
|
||||
if this.lastHotSize == 0 {
|
||||
// 自动降低采样率来增加热点数据的缓存几率
|
||||
rate = rate / 10
|
||||
}
|
||||
if rands.Int(0, rate) == 0 {
|
||||
var memoryStorage = this.memoryStorage
|
||||
|
||||
var hitErr = this.list.IncreaseHit(hash)
|
||||
if hitErr != nil {
|
||||
// 此错误可以忽略
|
||||
remotelogs.Error("CACHE", "increase hit failed: "+hitErr.Error())
|
||||
}
|
||||
|
||||
// 增加到热点
|
||||
// 这里不收录缓存尺寸过大的文件
|
||||
if memoryStorage != nil && reader.BodySize() > 0 && reader.BodySize() < 128*sizes.M {
|
||||
if memoryStorage != nil && reader.BodySize() > 0 && reader.BodySize() < (128<<20) {
|
||||
this.hotMapLocker.Lock()
|
||||
hotItem, ok := this.hotMap[key]
|
||||
|
||||
@@ -1319,6 +1453,15 @@ func (this *FileStorage) increaseHit(key string, hash string, reader Reader) {
|
||||
}
|
||||
}
|
||||
this.hotMapLocker.Unlock()
|
||||
|
||||
// 只有重复点击的才增加点击量
|
||||
if ok {
|
||||
var hitErr = this.list.IncreaseHit(hash)
|
||||
if hitErr != nil {
|
||||
// 此错误可以忽略
|
||||
remotelogs.Error("CACHE", "increase hit failed: "+hitErr.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1335,11 +1478,16 @@ func (this *FileStorage) removeCacheFile(path string) error {
|
||||
err = nil
|
||||
|
||||
// 删除Partial相关
|
||||
var partialPath = partialRangesFilePath(path)
|
||||
var partialPath = PartialRangesFilePath(path)
|
||||
if openFileCache != nil {
|
||||
openFileCache.Close(partialPath)
|
||||
}
|
||||
_ = os.Remove(partialPath)
|
||||
|
||||
_, statErr := os.Stat(partialPath)
|
||||
if statErr == nil {
|
||||
_ = os.Remove(partialPath)
|
||||
SharedPartialRangesQueue.Delete(partialPath)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -1418,7 +1566,7 @@ func (this *FileStorage) initOpenFileCache() {
|
||||
}
|
||||
|
||||
func (this *FileStorage) runMemoryStorageSafety(f func(memoryStorage *MemoryStorage)) {
|
||||
var memoryStorage = this.memoryStorage
|
||||
var memoryStorage = this.memoryStorage // copy
|
||||
if memoryStorage != nil {
|
||||
f(memoryStorage)
|
||||
}
|
||||
@@ -1434,20 +1582,56 @@ func (this *FileStorage) checkDiskSpace() {
|
||||
}
|
||||
|
||||
if options != nil && len(options.Dir) > 0 {
|
||||
stat, err := fsutils.Stat(options.Dir)
|
||||
stat, err := fsutils.StatDevice(options.Dir)
|
||||
if err == nil {
|
||||
this.mainDiskIsFull = stat.FreeSize() < minFreeSize
|
||||
this.mainDiskTotalSize = stat.TotalSize()
|
||||
|
||||
// check capacity (only on main directory) when node capacity had not been set
|
||||
if !this.mainDiskIsFull {
|
||||
var capacityBytes int64
|
||||
var maxDiskCapacity = SharedManager.MaxDiskCapacity // copy
|
||||
if maxDiskCapacity != nil && maxDiskCapacity.Bytes() > 0 {
|
||||
capacityBytes = SharedManager.MaxDiskCapacity.Bytes()
|
||||
} else {
|
||||
var policy = this.policy // copy
|
||||
if policy != nil {
|
||||
capacityBytes = policy.CapacityBytes() // copy
|
||||
}
|
||||
}
|
||||
|
||||
if capacityBytes > 0 && stat.UsedSize() >= uint64(capacityBytes) {
|
||||
this.mainDiskIsFull = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var subDirs = this.subDirs // copy slice
|
||||
for _, subDir := range subDirs {
|
||||
stat, err := fsutils.Stat(subDir.Path)
|
||||
stat, err := fsutils.StatDevice(subDir.Path)
|
||||
if err == nil {
|
||||
subDir.IsFull = stat.FreeSize() < minFreeSize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否有已满的磁盘分区
|
||||
func (this *FileStorage) hasFullDisk() bool {
|
||||
this.checkDiskSpace()
|
||||
|
||||
var hasFullDisk = this.mainDiskIsFull
|
||||
if !hasFullDisk {
|
||||
var subDirs = this.subDirs // copy slice
|
||||
for _, subDir := range subDirs {
|
||||
if subDir.IsFull {
|
||||
hasFullDisk = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return hasFullDisk
|
||||
}
|
||||
|
||||
// 获取目录
|
||||
func (this *FileStorage) subDir(hash string) (dirPath string, dirIsFull bool) {
|
||||
var suffix = "/p" + types.String(this.policy.Id) + "/" + hash[:2] + "/" + hash[2:4]
|
||||
@@ -1477,6 +1661,134 @@ func (this *FileStorage) subDir(hash string) (dirPath string, dirIsFull bool) {
|
||||
return subDir.Path + suffix, subDir.IsFull
|
||||
}
|
||||
|
||||
// ScanGarbageCaches 清理目录中“失联”的缓存文件
|
||||
// “失联”为不在HashMap中的文件
|
||||
func (this *FileStorage) ScanGarbageCaches(fileCallback func(path string) error) error {
|
||||
_, isSQLite := this.list.(*SQLiteFileList)
|
||||
if isSQLite && !this.list.(*SQLiteFileList).HashMapIsLoaded() {
|
||||
return errors.New("cache list is loading")
|
||||
}
|
||||
|
||||
var mainDir = this.options.Dir
|
||||
var allDirs = []string{mainDir}
|
||||
var subDirs = this.subDirs // copy
|
||||
for _, subDir := range subDirs {
|
||||
allDirs = append(allDirs, subDir.Path)
|
||||
}
|
||||
|
||||
var countDirs = 0
|
||||
|
||||
// process progress
|
||||
var progressSock = gosock.NewTmpSock(teaconst.CacheGarbageSockName)
|
||||
_, sockErr := progressSock.SendTimeout(&gosock.Command{Code: "progress", Params: map[string]any{"progress": 0}}, 1*time.Second)
|
||||
var canReportProgress = sockErr == nil
|
||||
var lastProgress float64
|
||||
var countFound = 0
|
||||
|
||||
for _, subDir := range allDirs {
|
||||
var dir0 = subDir + "/p" + types.String(this.policy.Id)
|
||||
dir1Matches, err := filepath.Glob(dir0 + "/*")
|
||||
if err != nil {
|
||||
// ignore error
|
||||
continue
|
||||
}
|
||||
|
||||
for _, dir1 := range dir1Matches {
|
||||
if len(filepath.Base(dir1)) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
dir2Matches, globErr := filepath.Glob(dir1 + "/*")
|
||||
if globErr != nil {
|
||||
// ignore error
|
||||
continue
|
||||
}
|
||||
for _, dir2 := range dir2Matches {
|
||||
if len(filepath.Base(dir2)) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
countDirs++
|
||||
|
||||
// report progress
|
||||
if canReportProgress {
|
||||
var progress = float64(countDirs) / 65536
|
||||
if fmt.Sprintf("%.2f", lastProgress) != fmt.Sprintf("%.2f", progress) {
|
||||
lastProgress = progress
|
||||
_, _ = progressSock.SendTimeout(&gosock.Command{Code: "progress", Params: map[string]any{
|
||||
"progress": progress,
|
||||
"count": countFound,
|
||||
}}, 100*time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
fileMatches, globDir2Err := filepath.Glob(dir2 + "/*.cache")
|
||||
if globDir2Err != nil {
|
||||
// ignore error
|
||||
continue
|
||||
}
|
||||
|
||||
for _, file := range fileMatches {
|
||||
var filename = filepath.Base(file)
|
||||
var hash = strings.TrimSuffix(filename, ".cache")
|
||||
if len(hash) != HashKeyLength {
|
||||
continue
|
||||
}
|
||||
|
||||
var isReady, found bool
|
||||
switch rawList := this.list.(type) {
|
||||
case *SQLiteFileList:
|
||||
isReady, found = rawList.ExistQuick(hash)
|
||||
case *KVFileList:
|
||||
isReady = true
|
||||
var checkErr error
|
||||
found, checkErr = rawList.ExistQuick(hash)
|
||||
if checkErr != nil {
|
||||
return checkErr
|
||||
}
|
||||
}
|
||||
|
||||
if !isReady {
|
||||
continue
|
||||
}
|
||||
|
||||
if found {
|
||||
continue
|
||||
}
|
||||
|
||||
// 检查文件正在被写入
|
||||
stat, statErr := os.Stat(file)
|
||||
if statErr != nil {
|
||||
continue
|
||||
}
|
||||
if fasttime.Now().Unix()-stat.ModTime().Unix() < 300 /** 5 minutes **/ {
|
||||
continue
|
||||
}
|
||||
|
||||
if fileCallback != nil {
|
||||
countFound++
|
||||
callbackErr := fileCallback(file)
|
||||
if callbackErr != nil {
|
||||
return callbackErr
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 100% progress
|
||||
if canReportProgress && lastProgress != 1 {
|
||||
_, _ = progressSock.SendTimeout(&gosock.Command{Code: "progress", Params: map[string]any{
|
||||
"progress": 1,
|
||||
"count": countFound,
|
||||
}}, 100*time.Millisecond)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 计算字节数字代号
|
||||
func (this *FileStorage) charCode(r byte) uint8 {
|
||||
if r >= '0' && r <= '9' {
|
||||
return r - '0'
|
||||
|
||||
9
internal/caches/storage_file_ext.go
Normal file
9
internal/caches/storage_file_ext.go
Normal file
@@ -0,0 +1,9 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build !plus
|
||||
|
||||
package caches
|
||||
|
||||
func (this *FileStorage) tryMMAPReader(isPartial bool, estimatedSize int64, path string) (Reader, error) {
|
||||
// stub
|
||||
return nil, nil
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
@@ -18,6 +19,10 @@ import (
|
||||
)
|
||||
|
||||
func TestFileStorage_Init(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
IsOn: true,
|
||||
@@ -42,12 +47,16 @@ func TestFileStorage_Init(t *testing.T) {
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
storage.purgeLoop()
|
||||
t.Log(storage.list.(*FileList).Stat(func(hash string) bool {
|
||||
t.Log(storage.list.(*SQLiteFileList).Stat(func(hash string) bool {
|
||||
return true
|
||||
}))
|
||||
}
|
||||
|
||||
func TestFileStorage_OpenWriter(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
IsOn: true,
|
||||
@@ -95,6 +104,10 @@ func TestFileStorage_OpenWriter(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileStorage_OpenWriter_Partial(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 2,
|
||||
IsOn: true,
|
||||
@@ -133,6 +146,10 @@ func TestFileStorage_OpenWriter_Partial(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileStorage_OpenWriter_HTTP(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
IsOn: true,
|
||||
@@ -201,6 +218,10 @@ func TestFileStorage_OpenWriter_HTTP(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileStorage_Concurrent_Open_DifferentFile(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
IsOn: true,
|
||||
@@ -230,7 +251,7 @@ func TestFileStorage_Concurrent_Open_DifferentFile(t *testing.T) {
|
||||
|
||||
writer, err := storage.OpenWriter("abc"+strconv.Itoa(i), time.Now().Unix()+3600, 200, -1, -1, -1, false)
|
||||
if err != nil {
|
||||
if err != ErrFileIsWriting {
|
||||
if errors.Is(err, ErrFileIsWriting) {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
@@ -259,6 +280,10 @@ func TestFileStorage_Concurrent_Open_DifferentFile(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileStorage_Concurrent_Open_SameFile(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
IsOn: true,
|
||||
@@ -288,7 +313,7 @@ func TestFileStorage_Concurrent_Open_SameFile(t *testing.T) {
|
||||
|
||||
writer, err := storage.OpenWriter("abc"+strconv.Itoa(0), time.Now().Unix()+3600, 200, -1, -1, -1, false)
|
||||
if err != nil {
|
||||
if err != ErrFileIsWriting {
|
||||
if errors.Is(err, ErrFileIsWriting) {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
@@ -318,6 +343,10 @@ func TestFileStorage_Concurrent_Open_SameFile(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileStorage_Read(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
IsOn: true,
|
||||
@@ -357,6 +386,10 @@ func TestFileStorage_Read(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileStorage_Read_HTTP_Response(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
IsOn: true,
|
||||
@@ -413,6 +446,10 @@ func TestFileStorage_Read_HTTP_Response(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileStorage_Read_NotFound(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
IsOn: true,
|
||||
@@ -449,6 +486,10 @@ func TestFileStorage_Read_NotFound(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileStorage_Delete(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
IsOn: true,
|
||||
@@ -471,6 +512,10 @@ func TestFileStorage_Delete(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileStorage_Stat(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
IsOn: true,
|
||||
@@ -499,6 +544,10 @@ func TestFileStorage_Stat(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileStorage_CleanAll(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
IsOn: true,
|
||||
@@ -533,6 +582,10 @@ func TestFileStorage_CleanAll(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileStorage_Stop(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
IsOn: true,
|
||||
@@ -551,6 +604,10 @@ func TestFileStorage_Stop(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileStorage_DecodeFile(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
IsOn: true,
|
||||
@@ -570,6 +627,10 @@ func TestFileStorage_DecodeFile(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileStorage_RemoveCacheFile(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var storage = NewFileStorage(nil)
|
||||
|
||||
defer storage.Stop()
|
||||
@@ -577,10 +638,33 @@ func TestFileStorage_RemoveCacheFile(t *testing.T) {
|
||||
t.Log(storage.removeCacheFile("/Users/WorkSpace/EdgeProject/EdgeCache/p43/15/7e/157eba0dfc6dfb6fbbf20b1f9e584674.cache"))
|
||||
}
|
||||
|
||||
func TestFileStorage_ScanGarbageCaches(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 43,
|
||||
Options: map[string]any{"dir": "/Users/WorkSpace/EdgeProject/EdgeCache"},
|
||||
})
|
||||
err := storage.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = storage.ScanGarbageCaches(func(path string) error {
|
||||
t.Log(path, PartialRangesFilePath(path))
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFileStorage_Read(b *testing.B) {
|
||||
runtime.GOMAXPROCS(1)
|
||||
|
||||
_ = utils.SetRLimit(1024 * 1024)
|
||||
_ = utils.SetRLimit(1 << 20)
|
||||
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package caches
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
@@ -8,13 +9,11 @@ import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs"
|
||||
memutils "github.com/TeaOSLab/EdgeNode/internal/utils/mem"
|
||||
setutils "github.com/TeaOSLab/EdgeNode/internal/utils/sets"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/sizes"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
||||
"github.com/cespare/xxhash"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"github.com/cespare/xxhash/v2"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"github.com/shirou/gopsutil/v3/load"
|
||||
"math"
|
||||
"runtime"
|
||||
"strconv"
|
||||
@@ -31,6 +30,11 @@ type MemoryItem struct {
|
||||
Status int
|
||||
IsDone bool
|
||||
ModifiedAt int64
|
||||
|
||||
IsPrepared bool
|
||||
WriteOffset int64
|
||||
|
||||
isReferring bool // if it is referring by other objects
|
||||
}
|
||||
|
||||
func (this *MemoryItem) IsExpired() bool {
|
||||
@@ -51,7 +55,9 @@ type MemoryStorage struct {
|
||||
|
||||
purgeTicker *utils.Ticker
|
||||
|
||||
totalSize int64
|
||||
usedSize int64
|
||||
totalDirtySize int64
|
||||
|
||||
writingKeyMap map[string]zero.Zero // key => bool
|
||||
|
||||
ignoreKeys *setutils.FixedSet
|
||||
@@ -63,7 +69,7 @@ func NewMemoryStorage(policy *serverconfigs.HTTPCachePolicy, parentStorage Stora
|
||||
|
||||
if parentStorage != nil {
|
||||
if queueSize <= 0 {
|
||||
queueSize = 2048 + int(policy.CapacityBytes()/sizes.G)*2048
|
||||
queueSize = memutils.SystemMemoryGB() * 100_000
|
||||
}
|
||||
|
||||
dirtyChan = make(chan string, queueSize)
|
||||
@@ -86,20 +92,29 @@ func (this *MemoryStorage) Init() error {
|
||||
_ = this.list.Init()
|
||||
|
||||
this.list.OnAdd(func(item *Item) {
|
||||
atomic.AddInt64(&this.totalSize, item.TotalSize())
|
||||
atomic.AddInt64(&this.usedSize, item.TotalSize())
|
||||
})
|
||||
this.list.OnRemove(func(item *Item) {
|
||||
atomic.AddInt64(&this.totalSize, -item.TotalSize())
|
||||
atomic.AddInt64(&this.usedSize, -item.TotalSize())
|
||||
})
|
||||
|
||||
this.initPurgeTicker()
|
||||
|
||||
// 启动定时Flush memory to disk任务
|
||||
if this.parentStorage != nil {
|
||||
// TODO 应该根据磁盘性能决定线程数
|
||||
// TODO 线程数应该可以在缓存策略和节点中设定
|
||||
var threads = runtime.NumCPU()
|
||||
|
||||
var threads = 2
|
||||
var numCPU = runtime.NumCPU()
|
||||
if fsutils.DiskIsExtremelyFast() {
|
||||
if numCPU >= 8 {
|
||||
threads = 8
|
||||
} else {
|
||||
threads = 4
|
||||
}
|
||||
} else if fsutils.DiskIsFast() {
|
||||
if numCPU >= 4 {
|
||||
threads = 4
|
||||
}
|
||||
}
|
||||
for i := 0; i < threads; i++ {
|
||||
goman.New(func() {
|
||||
this.startFlush()
|
||||
@@ -115,14 +130,19 @@ func (this *MemoryStorage) OpenReader(key string, useStale bool, isPartial bool)
|
||||
var hash = this.hash(key)
|
||||
|
||||
// check if exists in list
|
||||
exists, _ := this.list.Exist(types.String(hash))
|
||||
exists, _, _ := this.list.Exist(types.String(hash))
|
||||
if !exists {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
|
||||
// read from valuesMap
|
||||
this.locker.RLock()
|
||||
item := this.valuesMap[hash]
|
||||
var item = this.valuesMap[hash]
|
||||
|
||||
if item != nil {
|
||||
item.isReferring = true
|
||||
}
|
||||
|
||||
if item == nil || !item.IsDone {
|
||||
this.locker.RUnlock()
|
||||
return nil, ErrNotFound
|
||||
@@ -137,17 +157,6 @@ func (this *MemoryStorage) OpenReader(key string, useStale bool, isPartial bool)
|
||||
}
|
||||
this.locker.RUnlock()
|
||||
|
||||
// 增加点击量
|
||||
// 1/1000采样
|
||||
// TODO 考虑是否在缓存策略里设置
|
||||
if rands.Int(0, 1000) == 0 {
|
||||
var hitErr = this.list.IncreaseHit(types.String(hash))
|
||||
if hitErr != nil {
|
||||
// 此错误可以忽略
|
||||
remotelogs.Error("CACHE", "increase hit failed: "+hitErr.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return reader, nil
|
||||
}
|
||||
this.locker.RUnlock()
|
||||
@@ -165,7 +174,7 @@ func (this *MemoryStorage) OpenWriter(key string, expiredAt int64, status int, h
|
||||
|
||||
// TODO 内存缓存暂时不支持分块内容存储
|
||||
if isPartial {
|
||||
return nil, ErrFileIsWriting
|
||||
return nil, fmt.Errorf("%w (004)", ErrFileIsWriting)
|
||||
}
|
||||
return this.openWriter(key, expiredAt, status, headerSize, bodySize, maxSize, true)
|
||||
}
|
||||
@@ -180,7 +189,7 @@ func (this *MemoryStorage) openWriter(key string, expiresAt int64, status int, h
|
||||
if isDirty &&
|
||||
this.parentStorage != nil &&
|
||||
this.dirtyQueueSize > 0 &&
|
||||
len(this.dirtyChan) == this.dirtyQueueSize { // 缓存时间过长
|
||||
len(this.dirtyChan) >= this.dirtyQueueSize-int(fsutils.DiskMaxWrites) /** delta **/ { // 缓存时间过长
|
||||
return nil, ErrWritingQueueFull
|
||||
}
|
||||
|
||||
@@ -191,7 +200,7 @@ func (this *MemoryStorage) openWriter(key string, expiresAt int64, status int, h
|
||||
var isWriting = false
|
||||
_, ok := this.writingKeyMap[key]
|
||||
if ok {
|
||||
return nil, ErrFileIsWriting
|
||||
return nil, fmt.Errorf("%w (005)", ErrFileIsWriting)
|
||||
}
|
||||
this.writingKeyMap[key] = zero.New()
|
||||
defer func() {
|
||||
@@ -205,24 +214,24 @@ func (this *MemoryStorage) openWriter(key string, expiresAt int64, status int, h
|
||||
item, ok := this.valuesMap[hash]
|
||||
if ok && !item.IsExpired() {
|
||||
var hashString = types.String(hash)
|
||||
exists, _ := this.list.Exist(hashString)
|
||||
exists, _, _ := this.list.Exist(hashString)
|
||||
if !exists {
|
||||
// remove from values map
|
||||
delete(this.valuesMap, hash)
|
||||
_ = this.list.Remove(hashString)
|
||||
item = nil
|
||||
} else {
|
||||
return nil, ErrFileIsWriting
|
||||
return nil, fmt.Errorf("%w (006)", ErrFileIsWriting)
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否超出最大值
|
||||
capacityBytes := this.memoryCapacityBytes()
|
||||
// 检查是否超出容量最大值
|
||||
var capacityBytes = this.memoryCapacityBytes()
|
||||
if bodySize < 0 {
|
||||
bodySize = 0
|
||||
}
|
||||
if capacityBytes > 0 && capacityBytes <= this.totalSize+bodySize {
|
||||
return nil, NewCapacityError("write memory cache failed: over memory size: " + strconv.FormatInt(capacityBytes, 10) + ", current size: " + strconv.FormatInt(this.totalSize, 10) + " bytes")
|
||||
if capacityBytes > 0 && capacityBytes <= atomic.LoadInt64(&this.usedSize)+bodySize {
|
||||
return nil, NewCapacityError("write memory cache failed: over memory size: " + strconv.FormatInt(capacityBytes, 10) + ", current size: " + strconv.FormatInt(this.usedSize, 10) + " bytes")
|
||||
}
|
||||
|
||||
// 先删除
|
||||
@@ -232,7 +241,7 @@ func (this *MemoryStorage) openWriter(key string, expiresAt int64, status int, h
|
||||
}
|
||||
|
||||
isWriting = true
|
||||
return NewMemoryWriter(this, key, expiresAt, status, isDirty, maxSize, func() {
|
||||
return NewMemoryWriter(this, key, expiresAt, status, isDirty, bodySize, maxSize, func(valueItem *MemoryItem) {
|
||||
this.locker.Lock()
|
||||
delete(this.writingKeyMap, key)
|
||||
this.locker.Unlock()
|
||||
@@ -264,7 +273,7 @@ func (this *MemoryStorage) CleanAll() error {
|
||||
this.locker.Lock()
|
||||
this.valuesMap = map[uint64]*MemoryItem{}
|
||||
_ = this.list.Reset()
|
||||
atomic.StoreInt64(&this.totalSize, 0)
|
||||
atomic.StoreInt64(&this.usedSize, 0)
|
||||
this.locker.Unlock()
|
||||
return nil
|
||||
}
|
||||
@@ -333,6 +342,9 @@ func (this *MemoryStorage) Stop() {
|
||||
close(this.dirtyChan)
|
||||
}
|
||||
|
||||
this.usedSize = 0
|
||||
this.totalDirtySize = 0
|
||||
|
||||
_ = this.list.Close()
|
||||
|
||||
this.locker.Unlock()
|
||||
@@ -370,11 +382,16 @@ func (this *MemoryStorage) UpdatePolicy(newPolicy *serverconfigs.HTTPCachePolicy
|
||||
|
||||
// CanUpdatePolicy 检查策略是否可以更新
|
||||
func (this *MemoryStorage) CanUpdatePolicy(newPolicy *serverconfigs.HTTPCachePolicy) bool {
|
||||
return true
|
||||
return newPolicy != nil && newPolicy.Type == serverconfigs.CachePolicyStorageMemory
|
||||
}
|
||||
|
||||
// AddToList 将缓存添加到列表
|
||||
func (this *MemoryStorage) AddToList(item *Item) {
|
||||
// skip added item
|
||||
if item.MetaSize > 0 {
|
||||
return
|
||||
}
|
||||
|
||||
item.MetaSize = int64(len(item.Key)) + 128 /** 128是我们评估的数据结构的长度 **/
|
||||
var hash = types.String(this.hash(item.Key))
|
||||
|
||||
@@ -392,7 +409,7 @@ func (this *MemoryStorage) TotalDiskSize() int64 {
|
||||
|
||||
// TotalMemorySize 内存尺寸
|
||||
func (this *MemoryStorage) TotalMemorySize() int64 {
|
||||
return atomic.LoadInt64(&this.totalSize)
|
||||
return atomic.LoadInt64(&this.usedSize)
|
||||
}
|
||||
|
||||
// IgnoreKey 忽略某个Key,即不缓存某个Key
|
||||
@@ -405,6 +422,11 @@ func (this *MemoryStorage) CanSendfile() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// HasFreeSpaceForHotItems 是否有足够的空间提供给热门内容
|
||||
func (this *MemoryStorage) HasFreeSpaceForHotItems() bool {
|
||||
return atomic.LoadInt64(&this.usedSize) < this.memoryCapacityBytes()*3/4
|
||||
}
|
||||
|
||||
// 计算Key Hash
|
||||
func (this *MemoryStorage) hash(key string) uint64 {
|
||||
return xxhash.Sum64String(key)
|
||||
@@ -412,22 +434,6 @@ func (this *MemoryStorage) hash(key string) uint64 {
|
||||
|
||||
// 清理任务
|
||||
func (this *MemoryStorage) purgeLoop() {
|
||||
// 计算是否应该开启LFU清理
|
||||
var capacityBytes = this.policy.CapacityBytes()
|
||||
var startLFU = false
|
||||
var usedPercent = float32(this.TotalMemorySize()*100) / float32(capacityBytes)
|
||||
var lfuFreePercent = this.policy.MemoryLFUFreePercent
|
||||
if lfuFreePercent <= 0 {
|
||||
lfuFreePercent = 5
|
||||
}
|
||||
if capacityBytes > 0 {
|
||||
if lfuFreePercent < 100 {
|
||||
if usedPercent >= 100-lfuFreePercent {
|
||||
startLFU = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 清理过期
|
||||
var purgeCount = this.policy.MemoryAutoPurgeCount
|
||||
if purgeCount <= 0 {
|
||||
@@ -444,6 +450,23 @@ func (this *MemoryStorage) purgeLoop() {
|
||||
})
|
||||
|
||||
// LFU
|
||||
// 计算是否应该开启LFU清理
|
||||
var capacityBytes = this.policy.CapacityBytes()
|
||||
var startLFU = false
|
||||
|
||||
var usedPercent = float32(this.TotalMemorySize()*100) / float32(capacityBytes)
|
||||
var lfuFreePercent = this.policy.MemoryLFUFreePercent
|
||||
if lfuFreePercent <= 0 {
|
||||
lfuFreePercent = 5
|
||||
}
|
||||
if capacityBytes > 0 {
|
||||
if lfuFreePercent < 100 {
|
||||
if usedPercent >= 100-lfuFreePercent {
|
||||
startLFU = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if startLFU {
|
||||
var total, _ = this.list.Count()
|
||||
if total > 0 {
|
||||
@@ -476,39 +499,32 @@ func (this *MemoryStorage) purgeLoop() {
|
||||
// 开始Flush任务
|
||||
func (this *MemoryStorage) startFlush() {
|
||||
var statCount = 0
|
||||
var writeDelayMS float64 = 0
|
||||
|
||||
for key := range this.dirtyChan {
|
||||
statCount++
|
||||
|
||||
if statCount == 100 {
|
||||
statCount = 0
|
||||
|
||||
// delay some time to reduce load if needed
|
||||
if !fsutils.DiskIsFast() {
|
||||
loadStat, err := load.Avg()
|
||||
if err == nil && loadStat != nil {
|
||||
if loadStat.Load1 > 10 {
|
||||
writeDelayMS = 100
|
||||
} else if loadStat.Load1 > 5 {
|
||||
writeDelayMS = 50
|
||||
} else {
|
||||
writeDelayMS = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.flushItem(key)
|
||||
|
||||
if writeDelayMS > 0 {
|
||||
time.Sleep(time.Duration(writeDelayMS) * time.Millisecond)
|
||||
if fsutils.IsInExtremelyHighLoad {
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 单次Flush任务
|
||||
func (this *MemoryStorage) flushItem(key string) {
|
||||
func (this *MemoryStorage) flushItem(fullKey string) {
|
||||
sizeString, key, found := strings.Cut(fullKey, "@")
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
atomic.AddInt64(&this.totalDirtySize, -types.Int64(sizeString))
|
||||
}()
|
||||
|
||||
if this.parentStorage == nil {
|
||||
return
|
||||
}
|
||||
@@ -518,9 +534,15 @@ func (this *MemoryStorage) flushItem(key string) {
|
||||
item, ok := this.valuesMap[hash]
|
||||
this.locker.RUnlock()
|
||||
|
||||
// 从内存中移除,并确保无论如何都会执行
|
||||
defer func() {
|
||||
_ = this.Delete(key)
|
||||
}()
|
||||
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if !item.IsDone {
|
||||
remotelogs.Error("CACHE", "flush items failed: open writer failed: item has not been done")
|
||||
return
|
||||
@@ -529,6 +551,31 @@ func (this *MemoryStorage) flushItem(key string) {
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否在列表中,防止未加入列表时就开始flush
|
||||
isInList, _, err := this.list.Exist(types.String(hash))
|
||||
if err != nil {
|
||||
remotelogs.Error("CACHE", "flush items failed: "+err.Error())
|
||||
return
|
||||
}
|
||||
if !isInList {
|
||||
for i := 0; i < 1000; i++ {
|
||||
isInList, _, err = this.list.Exist(types.String(hash))
|
||||
if err != nil {
|
||||
remotelogs.Error("CACHE", "flush items failed: "+err.Error())
|
||||
time.Sleep(1 * time.Second)
|
||||
continue
|
||||
}
|
||||
if isInList {
|
||||
break
|
||||
}
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
}
|
||||
if !isInList {
|
||||
// discard
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
writer, err := this.parentStorage.OpenFlushWriter(key, item.ExpiresAt, item.Status, len(item.HeaderValue), int64(len(item.BodyValue)))
|
||||
if err != nil {
|
||||
if !CanIgnoreErr(err) {
|
||||
@@ -562,30 +609,41 @@ func (this *MemoryStorage) flushItem(key string) {
|
||||
Type: writer.ItemType(),
|
||||
Key: key,
|
||||
Host: ParseHost(key),
|
||||
ExpiredAt: item.ExpiresAt,
|
||||
ExpiresAt: item.ExpiresAt,
|
||||
HeaderSize: writer.HeaderSize(),
|
||||
BodySize: writer.BodySize(),
|
||||
})
|
||||
|
||||
// 从内存中移除
|
||||
_ = this.Delete(key)
|
||||
}
|
||||
|
||||
func (this *MemoryStorage) memoryCapacityBytes() int64 {
|
||||
var maxSystemBytes = SharedManager.MaxSystemMemoryBytesPerStorage()
|
||||
if this.policy == nil {
|
||||
return 0
|
||||
}
|
||||
c1 := int64(0)
|
||||
if this.policy.Capacity != nil {
|
||||
c1 = this.policy.Capacity.Bytes()
|
||||
return maxSystemBytes
|
||||
}
|
||||
|
||||
if SharedManager.MaxMemoryCapacity != nil {
|
||||
c2 := SharedManager.MaxMemoryCapacity.Bytes()
|
||||
if c2 > 0 {
|
||||
return c2
|
||||
var capacityBytes = SharedManager.MaxMemoryCapacity.Bytes()
|
||||
if capacityBytes > 0 {
|
||||
if capacityBytes > maxSystemBytes {
|
||||
return maxSystemBytes
|
||||
}
|
||||
|
||||
return capacityBytes
|
||||
}
|
||||
}
|
||||
return c1
|
||||
|
||||
var capacity = this.policy.Capacity // copy
|
||||
if capacity != nil {
|
||||
var capacityBytes = capacity.Bytes()
|
||||
if capacityBytes > 0 {
|
||||
if capacityBytes > maxSystemBytes {
|
||||
return maxSystemBytes
|
||||
}
|
||||
return capacityBytes
|
||||
}
|
||||
}
|
||||
|
||||
return maxSystemBytes
|
||||
}
|
||||
|
||||
func (this *MemoryStorage) deleteWithoutLocker(key string) error {
|
||||
|
||||
@@ -3,8 +3,10 @@ package caches
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"math/rand"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
@@ -156,7 +158,7 @@ func TestMemoryStorage_Stat(t *testing.T) {
|
||||
storage.AddToList(&Item{
|
||||
Key: "abc",
|
||||
BodySize: 5,
|
||||
ExpiredAt: expiredAt,
|
||||
ExpiresAt: expiredAt,
|
||||
})
|
||||
}
|
||||
{
|
||||
@@ -173,7 +175,7 @@ func TestMemoryStorage_Stat(t *testing.T) {
|
||||
storage.AddToList(&Item{
|
||||
Key: "abc1",
|
||||
BodySize: 5,
|
||||
ExpiredAt: expiredAt,
|
||||
ExpiresAt: expiredAt,
|
||||
})
|
||||
}
|
||||
stat, err := storage.Stat()
|
||||
@@ -200,7 +202,7 @@ func TestMemoryStorage_CleanAll(t *testing.T) {
|
||||
storage.AddToList(&Item{
|
||||
Key: "abc",
|
||||
BodySize: 5,
|
||||
ExpiredAt: expiredAt,
|
||||
ExpiresAt: expiredAt,
|
||||
})
|
||||
}
|
||||
{
|
||||
@@ -216,7 +218,7 @@ func TestMemoryStorage_CleanAll(t *testing.T) {
|
||||
storage.AddToList(&Item{
|
||||
Key: "abc1",
|
||||
BodySize: 5,
|
||||
ExpiredAt: expiredAt,
|
||||
ExpiresAt: expiredAt,
|
||||
})
|
||||
}
|
||||
err := storage.CleanAll()
|
||||
@@ -243,7 +245,7 @@ func TestMemoryStorage_Purge(t *testing.T) {
|
||||
storage.AddToList(&Item{
|
||||
Key: "abc",
|
||||
BodySize: 5,
|
||||
ExpiredAt: expiredAt,
|
||||
ExpiresAt: expiredAt,
|
||||
})
|
||||
}
|
||||
{
|
||||
@@ -259,7 +261,7 @@ func TestMemoryStorage_Purge(t *testing.T) {
|
||||
storage.AddToList(&Item{
|
||||
Key: "abc1",
|
||||
BodySize: 5,
|
||||
ExpiredAt: expiredAt,
|
||||
ExpiresAt: expiredAt,
|
||||
})
|
||||
}
|
||||
err := storage.Purge([]string{"abc", "abc1"}, "")
|
||||
@@ -271,6 +273,10 @@ func TestMemoryStorage_Purge(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMemoryStorage_Expire(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var storage = NewMemoryStorage(&serverconfigs.HTTPCachePolicy{
|
||||
MemoryAutoPurgeInterval: 5,
|
||||
}, nil)
|
||||
@@ -294,7 +300,7 @@ func TestMemoryStorage_Expire(t *testing.T) {
|
||||
storage.AddToList(&Item{
|
||||
Key: key,
|
||||
BodySize: 5,
|
||||
ExpiredAt: expiredAt,
|
||||
ExpiresAt: expiredAt,
|
||||
})
|
||||
}
|
||||
time.Sleep(70 * time.Second)
|
||||
@@ -376,3 +382,31 @@ func BenchmarkValuesMap(b *testing.B) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkNewMemoryStorage(b *testing.B) {
|
||||
var storage = NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
|
||||
|
||||
var data = bytes.Repeat([]byte{'A'}, 1024)
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
func() {
|
||||
writer, err := storage.OpenWriter("abc"+strconv.Itoa(rand.Int()), time.Now().Unix()+60, 200, -1, -1, -1, false)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
_, _ = writer.WriteHeader([]byte("Header"))
|
||||
_, _ = writer.Write(data)
|
||||
err = writer.Close()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ package caches
|
||||
|
||||
import "strings"
|
||||
|
||||
// 获取 ranges 文件路径
|
||||
func partialRangesFilePath(path string) string {
|
||||
// PartialRangesFilePath 获取 ranges 文件路径
|
||||
func PartialRangesFilePath(path string) string {
|
||||
// ranges路径
|
||||
var dotIndex = strings.LastIndex(path, ".")
|
||||
var rangePath string
|
||||
|
||||
@@ -5,7 +5,7 @@ package caches_test
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||
"github.com/cespare/xxhash"
|
||||
"github.com/cespare/xxhash/v2"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
@@ -136,6 +136,13 @@ func (this *FileWriter) Close() error {
|
||||
|
||||
var path = this.rawWriter.Name()
|
||||
|
||||
// check content length
|
||||
if this.metaBodySize > 0 && this.bodySize != this.metaBodySize {
|
||||
_ = this.rawWriter.Close()
|
||||
_ = os.Remove(path)
|
||||
return ErrUnexpectedContentLength
|
||||
}
|
||||
|
||||
err := this.WriteHeaderLength(types.Int(this.headerSize))
|
||||
if err != nil {
|
||||
fsutils.WriteBegin()
|
||||
|
||||
@@ -2,9 +2,11 @@ package caches
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/cespare/xxhash"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
"github.com/cespare/xxhash/v2"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"sync"
|
||||
"time"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type MemoryWriter struct {
|
||||
@@ -16,29 +18,39 @@ type MemoryWriter struct {
|
||||
bodySize int64
|
||||
status int
|
||||
isDirty bool
|
||||
maxSize int64
|
||||
|
||||
expectedBodySize int64
|
||||
maxSize int64
|
||||
|
||||
hash uint64
|
||||
item *MemoryItem
|
||||
endFunc func()
|
||||
endFunc func(valueItem *MemoryItem)
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
func NewMemoryWriter(memoryStorage *MemoryStorage, key string, expiredAt int64, status int, isDirty bool, maxSize int64, endFunc func()) *MemoryWriter {
|
||||
w := &MemoryWriter{
|
||||
storage: memoryStorage,
|
||||
key: key,
|
||||
expiredAt: expiredAt,
|
||||
item: &MemoryItem{
|
||||
ExpiresAt: expiredAt,
|
||||
ModifiedAt: time.Now().Unix(),
|
||||
Status: status,
|
||||
},
|
||||
status: status,
|
||||
isDirty: isDirty,
|
||||
maxSize: maxSize,
|
||||
endFunc: endFunc,
|
||||
func NewMemoryWriter(memoryStorage *MemoryStorage, key string, expiredAt int64, status int, isDirty bool, expectedBodySize int64, maxSize int64, endFunc func(valueItem *MemoryItem)) *MemoryWriter {
|
||||
var valueItem = &MemoryItem{
|
||||
ExpiresAt: expiredAt,
|
||||
ModifiedAt: fasttime.Now().Unix(),
|
||||
Status: status,
|
||||
}
|
||||
|
||||
if expectedBodySize > 0 {
|
||||
valueItem.BodyValue = make([]byte, 0, expectedBodySize)
|
||||
}
|
||||
|
||||
var w = &MemoryWriter{
|
||||
storage: memoryStorage,
|
||||
key: key,
|
||||
expiredAt: expiredAt,
|
||||
item: valueItem,
|
||||
status: status,
|
||||
isDirty: isDirty,
|
||||
expectedBodySize: expectedBodySize,
|
||||
maxSize: maxSize,
|
||||
endFunc: endFunc,
|
||||
}
|
||||
|
||||
w.hash = w.calculateHash(key)
|
||||
|
||||
return w
|
||||
@@ -53,17 +65,32 @@ func (this *MemoryWriter) WriteHeader(data []byte) (n int, err error) {
|
||||
|
||||
// Write 写入数据
|
||||
func (this *MemoryWriter) Write(data []byte) (n int, err error) {
|
||||
this.bodySize += int64(len(data))
|
||||
this.item.BodyValue = append(this.item.BodyValue, data...)
|
||||
var l = len(data)
|
||||
if l == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if this.item.IsPrepared {
|
||||
if this.item.WriteOffset+int64(l) > this.expectedBodySize {
|
||||
err = ErrWritingUnavailable
|
||||
return
|
||||
}
|
||||
copy(this.item.BodyValue[this.item.WriteOffset:], data)
|
||||
this.item.WriteOffset += int64(l)
|
||||
} else {
|
||||
this.item.BodyValue = append(this.item.BodyValue, data...)
|
||||
}
|
||||
|
||||
this.bodySize += int64(l)
|
||||
|
||||
// 检查尺寸
|
||||
if this.maxSize > 0 && this.bodySize > this.maxSize {
|
||||
err = ErrEntityTooLarge
|
||||
this.storage.IgnoreKey(this.key, this.maxSize)
|
||||
return len(data), err
|
||||
return l, err
|
||||
}
|
||||
|
||||
return len(data), nil
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// WriteAt 在指定位置写入数据
|
||||
@@ -87,35 +114,54 @@ func (this *MemoryWriter) BodySize() int64 {
|
||||
func (this *MemoryWriter) Close() error {
|
||||
// 需要在Locker之外
|
||||
defer this.once.Do(func() {
|
||||
this.endFunc()
|
||||
this.endFunc(this.item)
|
||||
})
|
||||
|
||||
if this.item == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// check content length
|
||||
if this.expectedBodySize > 0 && this.bodySize != this.expectedBodySize {
|
||||
this.storage.locker.Lock()
|
||||
delete(this.storage.valuesMap, this.hash)
|
||||
this.storage.locker.Unlock()
|
||||
return ErrUnexpectedContentLength
|
||||
}
|
||||
|
||||
this.storage.locker.Lock()
|
||||
this.item.IsDone = true
|
||||
this.storage.valuesMap[this.hash] = this.item
|
||||
var err error
|
||||
if this.isDirty {
|
||||
if this.storage.parentStorage != nil {
|
||||
select {
|
||||
case this.storage.dirtyChan <- this.key:
|
||||
default:
|
||||
this.storage.valuesMap[this.hash] = this.item
|
||||
|
||||
select {
|
||||
case this.storage.dirtyChan <- types.String(this.bodySize) + "@" + this.key:
|
||||
atomic.AddInt64(&this.storage.totalDirtySize, this.bodySize)
|
||||
default:
|
||||
// remove from values map
|
||||
delete(this.storage.valuesMap, this.hash)
|
||||
|
||||
err = ErrWritingQueueFull
|
||||
}
|
||||
} else {
|
||||
this.storage.valuesMap[this.hash] = this.item
|
||||
}
|
||||
} else {
|
||||
this.storage.valuesMap[this.hash] = this.item
|
||||
}
|
||||
|
||||
this.storage.locker.Unlock()
|
||||
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
// Discard 丢弃
|
||||
func (this *MemoryWriter) Discard() error {
|
||||
// 需要在Locker之外
|
||||
defer this.once.Do(func() {
|
||||
this.endFunc()
|
||||
this.endFunc(this.item)
|
||||
})
|
||||
|
||||
this.storage.locker.Lock()
|
||||
|
||||
143
internal/caches/writer_memory_test.go
Normal file
143
internal/caches/writer_memory_test.go
Normal file
@@ -0,0 +1,143 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package caches_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNewMemoryWriter(t *testing.T) {
|
||||
var storage = caches.NewMemoryStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 0,
|
||||
IsOn: false,
|
||||
Name: "",
|
||||
Description: "",
|
||||
Capacity: &shared.SizeCapacity{
|
||||
Count: 8,
|
||||
Unit: shared.SizeCapacityUnitGB,
|
||||
},
|
||||
}, nil)
|
||||
err := storage.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
const size = 1 << 20
|
||||
const chunkSize = 16 << 10
|
||||
var data = bytes.Repeat([]byte{'A'}, chunkSize)
|
||||
|
||||
var before = time.Now()
|
||||
|
||||
var writer = caches.NewMemoryWriter(storage, "a", time.Now().Unix()+3600, 200, false, size, 1<<30, func(valueItem *caches.MemoryItem) {
|
||||
t.Log(len(valueItem.BodyValue), "bytes")
|
||||
})
|
||||
|
||||
for i := 0; i < size/chunkSize; i++ {
|
||||
_, err = writer.Write(data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
err = writer.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Log("cost:", time.Since(before).Seconds()*1000, "ms")
|
||||
}
|
||||
|
||||
func BenchmarkMemoryWriter_Capacity(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
|
||||
var storage = caches.NewMemoryStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 0,
|
||||
IsOn: false,
|
||||
Name: "",
|
||||
Description: "",
|
||||
Capacity: &shared.SizeCapacity{
|
||||
Count: 8,
|
||||
Unit: shared.SizeCapacityUnitGB,
|
||||
},
|
||||
}, nil)
|
||||
initErr := storage.Init()
|
||||
if initErr != nil {
|
||||
b.Fatal(initErr)
|
||||
}
|
||||
|
||||
const size = 1 << 20
|
||||
const chunkSize = 16 << 10
|
||||
var data = bytes.Repeat([]byte{'A'}, chunkSize)
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
var writer = caches.NewMemoryWriter(storage, "a"+strconv.Itoa(rand.Int()), time.Now().Unix()+3600, 200, false, size, 1<<30, func(valueItem *caches.MemoryItem) {
|
||||
})
|
||||
|
||||
for i := 0; i < size/chunkSize; i++ {
|
||||
_, err := writer.Write(data)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
err := writer.Close()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkMemoryWriter_Capacity_Disabled(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
|
||||
var storage = caches.NewMemoryStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 0,
|
||||
IsOn: false,
|
||||
Name: "",
|
||||
Description: "",
|
||||
Capacity: &shared.SizeCapacity{
|
||||
Count: 8,
|
||||
Unit: shared.SizeCapacityUnitGB,
|
||||
},
|
||||
}, nil)
|
||||
initErr := storage.Init()
|
||||
if initErr != nil {
|
||||
b.Fatal(initErr)
|
||||
}
|
||||
|
||||
const size = 1 << 20
|
||||
const chunkSize = 16 << 10
|
||||
var data = bytes.Repeat([]byte{'A'}, chunkSize)
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
var writer = caches.NewMemoryWriter(storage, "a"+strconv.Itoa(rand.Int()), time.Now().Unix()+3600, 200, false, 0, 1<<30, func(valueItem *caches.MemoryItem) {
|
||||
})
|
||||
|
||||
for i := 0; i < size/chunkSize; i++ {
|
||||
_, err := writer.Write(data)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
err := writer.Close()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -43,7 +43,7 @@ func NewPartialFileWriter(rawWriter *os.File, key string, expiredAt int64, metaH
|
||||
isPartial: isPartial,
|
||||
bodyOffset: bodyOffset,
|
||||
ranges: ranges,
|
||||
rangePath: partialRangesFilePath(rawWriter.Name()),
|
||||
rangePath: PartialRangesFilePath(rawWriter.Name()),
|
||||
metaHeaderSize: metaHeaderSize,
|
||||
metaBodySize: metaBodySize,
|
||||
}
|
||||
@@ -127,6 +127,32 @@ func (this *PartialFileWriter) WriteAt(offset int64, data []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// prevent extending too much space in a single writing
|
||||
var maxOffset = this.ranges.Max()
|
||||
if offset-maxOffset > 16<<20 {
|
||||
var maxExtendSize int64 = 32 << 20
|
||||
if fsutils.DiskIsExtremelyFast() {
|
||||
maxExtendSize = 128 << 20
|
||||
} else if fsutils.DiskIsFast() {
|
||||
maxExtendSize = 64 << 20
|
||||
}
|
||||
if offset-maxOffset > maxExtendSize {
|
||||
stat, err := this.rawWriter.Stat()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// extend min size to prepare for file tail
|
||||
const extendSizePerStep = 8 << 20
|
||||
if stat.Size()+extendSizePerStep <= this.bodyOffset+offset+int64(len(data)) {
|
||||
fsutils.WriteBegin()
|
||||
_ = this.rawWriter.Truncate(stat.Size() + extendSizePerStep)
|
||||
fsutils.WriteEnd()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if this.bodyOffset == 0 {
|
||||
var keyLength = 0
|
||||
if this.ranges.Version == 0 { // 以往的版本包含有Key
|
||||
@@ -228,6 +254,7 @@ func (this *PartialFileWriter) Discard() error {
|
||||
_ = this.rawWriter.Close()
|
||||
fsutils.WriteEnd()
|
||||
|
||||
SharedPartialRangesQueue.Delete(this.rangePath)
|
||||
_ = os.Remove(this.rangePath)
|
||||
|
||||
err := os.Remove(this.rawWriter.Name())
|
||||
@@ -261,5 +288,7 @@ func (this *PartialFileWriter) IsNew() bool {
|
||||
|
||||
func (this *PartialFileWriter) remove() {
|
||||
_ = os.Remove(this.rawWriter.Name())
|
||||
|
||||
SharedPartialRangesQueue.Delete(this.rangePath)
|
||||
_ = os.Remove(this.rangePath)
|
||||
}
|
||||
|
||||
14
internal/compressions/errors.go
Normal file
14
internal/compressions/errors.go
Normal file
@@ -0,0 +1,14 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package compressions
|
||||
|
||||
import "errors"
|
||||
|
||||
var ErrIsBusy = errors.New("the system is busy for compression")
|
||||
|
||||
func CanIgnore(err error) bool {
|
||||
if err == nil {
|
||||
return true
|
||||
}
|
||||
return errors.Is(err, ErrIsBusy)
|
||||
}
|
||||
@@ -9,6 +9,7 @@ type Reader interface {
|
||||
Reset(reader io.Reader) error
|
||||
RawClose() error
|
||||
Close() error
|
||||
IncreaseHit() uint32
|
||||
|
||||
SetPool(pool *ReaderPool)
|
||||
ResetFinish()
|
||||
|
||||
@@ -2,10 +2,13 @@
|
||||
|
||||
package compressions
|
||||
|
||||
import "sync/atomic"
|
||||
|
||||
type BaseReader struct {
|
||||
pool *ReaderPool
|
||||
|
||||
isFinished bool
|
||||
hits uint32
|
||||
}
|
||||
|
||||
func (this *BaseReader) SetPool(pool *ReaderPool) {
|
||||
@@ -13,8 +16,11 @@ func (this *BaseReader) SetPool(pool *ReaderPool) {
|
||||
}
|
||||
|
||||
func (this *BaseReader) Finish(obj Reader) error {
|
||||
if this.isFinished {
|
||||
return nil
|
||||
}
|
||||
err := obj.RawClose()
|
||||
if err == nil && this.pool != nil && !this.isFinished {
|
||||
if err == nil && this.pool != nil {
|
||||
this.pool.Put(obj)
|
||||
}
|
||||
this.isFinished = true
|
||||
@@ -24,3 +30,7 @@ func (this *BaseReader) Finish(obj Reader) error {
|
||||
func (this *BaseReader) ResetFinish() {
|
||||
this.isFinished = false
|
||||
}
|
||||
|
||||
func (this *BaseReader) IncreaseHit() uint32 {
|
||||
return atomic.AddUint32(&this.hits, 1)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build !plus || !linux
|
||||
|
||||
package compressions
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"bytes"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/compressions"
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -49,3 +50,45 @@ func TestBrotliReader(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBrotliReader(b *testing.B) {
|
||||
data, err := os.ReadFile("./reader_brotli.go")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
var buf = bytes.NewBuffer([]byte{})
|
||||
writer, err := compressions.NewBrotliWriter(buf, 5)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
_, err = writer.Write(data)
|
||||
err = writer.Close()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
var compressedData = buf.Bytes()
|
||||
|
||||
b.ResetTimer()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
reader, readerErr := compressions.NewBrotliReader(bytes.NewBuffer(compressedData))
|
||||
if readerErr != nil {
|
||||
b.Fatal(readerErr)
|
||||
}
|
||||
var readBuf = make([]byte, 1024)
|
||||
for {
|
||||
_, readErr := reader.Read(readBuf)
|
||||
if readErr != nil {
|
||||
if readErr != io.EOF {
|
||||
b.Fatal(readErr)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
closeErr := reader.Close()
|
||||
if closeErr != nil {
|
||||
b.Fatal(closeErr)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"io"
|
||||
)
|
||||
|
||||
const maxReadHits = 1 << 20
|
||||
|
||||
type ReaderPool struct {
|
||||
c chan Reader
|
||||
newFunc func(reader io.Reader) (Reader, error)
|
||||
@@ -49,6 +51,11 @@ func (this *ReaderPool) Get(parentReader io.Reader) (Reader, error) {
|
||||
}
|
||||
|
||||
func (this *ReaderPool) Put(reader Reader) {
|
||||
if reader.IncreaseHit() > maxReadHits {
|
||||
// do nothing to discard it
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case this.c <- reader:
|
||||
default:
|
||||
|
||||
@@ -4,7 +4,6 @@ package compressions
|
||||
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"io"
|
||||
)
|
||||
|
||||
@@ -15,11 +14,8 @@ func init() {
|
||||
return
|
||||
}
|
||||
|
||||
var maxSize = utils.SystemMemoryGB() * 256
|
||||
if maxSize == 0 {
|
||||
maxSize = 256
|
||||
}
|
||||
sharedBrotliReaderPool = NewReaderPool(maxSize, func(reader io.Reader) (Reader, error) {
|
||||
|
||||
sharedBrotliReaderPool = NewReaderPool(CalculatePoolSize(), func(reader io.Reader) (Reader, error) {
|
||||
return newBrotliReader(reader)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ package compressions
|
||||
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"io"
|
||||
)
|
||||
|
||||
@@ -15,11 +14,7 @@ func init() {
|
||||
return
|
||||
}
|
||||
|
||||
var maxSize = utils.SystemMemoryGB() * 256
|
||||
if maxSize == 0 {
|
||||
maxSize = 256
|
||||
}
|
||||
sharedDeflateReaderPool = NewReaderPool(maxSize, func(reader io.Reader) (Reader, error) {
|
||||
sharedDeflateReaderPool = NewReaderPool(CalculatePoolSize(), func(reader io.Reader) (Reader, error) {
|
||||
return newDeflateReader(reader)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ package compressions
|
||||
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"io"
|
||||
)
|
||||
|
||||
@@ -15,11 +14,7 @@ func init() {
|
||||
return
|
||||
}
|
||||
|
||||
var maxSize = utils.SystemMemoryGB() * 256
|
||||
if maxSize == 0 {
|
||||
maxSize = 256
|
||||
}
|
||||
sharedGzipReaderPool = NewReaderPool(maxSize, func(reader io.Reader) (Reader, error) {
|
||||
sharedGzipReaderPool = NewReaderPool(CalculatePoolSize(), func(reader io.Reader) (Reader, error) {
|
||||
return newGzipReader(reader)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ package compressions
|
||||
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"io"
|
||||
)
|
||||
|
||||
@@ -15,11 +14,7 @@ func init() {
|
||||
return
|
||||
}
|
||||
|
||||
var maxSize = utils.SystemMemoryGB() * 256
|
||||
if maxSize == 0 {
|
||||
maxSize = 256
|
||||
}
|
||||
sharedZSTDReaderPool = NewReaderPool(maxSize, func(reader io.Reader) (Reader, error) {
|
||||
sharedZSTDReaderPool = NewReaderPool(CalculatePoolSize(), func(reader io.Reader) (Reader, error) {
|
||||
return newZSTDReader(reader)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ func NewZSTDReader(reader io.Reader) (Reader, error) {
|
||||
}
|
||||
|
||||
func newZSTDReader(reader io.Reader) (Reader, error) {
|
||||
r, err := zstd.NewReader(reader)
|
||||
r, err := zstd.NewReader(reader, zstd.WithDecoderMaxWindow(256<<20))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -5,7 +5,10 @@ package compressions
|
||||
import (
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
memutils "github.com/TeaOSLab/EdgeNode/internal/utils/mem"
|
||||
"io"
|
||||
"net/http"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
type ContentEncoding = string
|
||||
@@ -58,3 +61,60 @@ func NewWriter(writer io.Writer, compressType serverconfigs.HTTPCompressionType,
|
||||
}
|
||||
return nil, errors.New("invalid compression type '" + compressType + "'")
|
||||
}
|
||||
|
||||
// SupportEncoding 检查是否支持某个编码
|
||||
func SupportEncoding(encoding string) bool {
|
||||
return encoding == ContentEncodingBr ||
|
||||
encoding == ContentEncodingGzip ||
|
||||
encoding == ContentEncodingDeflate ||
|
||||
encoding == ContentEncodingZSTD
|
||||
}
|
||||
|
||||
// WrapHTTPResponse 包装http.Response对象
|
||||
func WrapHTTPResponse(resp *http.Response) {
|
||||
if resp == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var contentEncoding = resp.Header.Get("Content-Encoding")
|
||||
if len(contentEncoding) == 0 || !SupportEncoding(contentEncoding) {
|
||||
return
|
||||
}
|
||||
|
||||
reader, err := NewReader(resp.Body, contentEncoding)
|
||||
if err != nil {
|
||||
// unable to decode, we ignore the error
|
||||
return
|
||||
}
|
||||
resp.Header.Del("Content-Encoding")
|
||||
resp.Header.Del("Content-Length")
|
||||
resp.Body = reader
|
||||
}
|
||||
|
||||
// 系统CPU线程数
|
||||
var countCPU = runtime.NumCPU()
|
||||
|
||||
// GenerateCompressLevel 根据系统资源自动生成压缩级别
|
||||
func GenerateCompressLevel(minLevel int, maxLevel int) (level int) {
|
||||
if countCPU < 16 {
|
||||
return minLevel
|
||||
}
|
||||
|
||||
if countCPU < 32 {
|
||||
return min(3, maxLevel)
|
||||
}
|
||||
|
||||
return min(5, maxLevel)
|
||||
}
|
||||
|
||||
// CalculatePoolSize 计算Pool尺寸
|
||||
func CalculatePoolSize() int {
|
||||
var maxSize = memutils.SystemMemoryGB() * 32
|
||||
if maxSize == 0 {
|
||||
maxSize = 128
|
||||
}
|
||||
if maxSize > 2048 {
|
||||
maxSize = 2048
|
||||
}
|
||||
return maxSize
|
||||
}
|
||||
|
||||
27
internal/compressions/utils_test.go
Normal file
27
internal/compressions/utils_test.go
Normal file
@@ -0,0 +1,27 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package compressions_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/compressions"
|
||||
"github.com/iwind/TeaGo/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGenerateCompressLevel(t *testing.T) {
|
||||
var a = assert.NewAssertion(t)
|
||||
|
||||
t.Log(compressions.GenerateCompressLevel(0, 10))
|
||||
t.Log(compressions.GenerateCompressLevel(1, 10))
|
||||
t.Log(compressions.GenerateCompressLevel(1, 4))
|
||||
|
||||
{
|
||||
var level = compressions.GenerateCompressLevel(1, 2)
|
||||
t.Log(level)
|
||||
a.IsTrue(level >= 1 && level <= 2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculatePoolSize(t *testing.T) {
|
||||
t.Log(compressions.CalculatePoolSize())
|
||||
}
|
||||
@@ -11,6 +11,7 @@ type Writer interface {
|
||||
RawClose() error
|
||||
Close() error
|
||||
Level() int
|
||||
IncreaseHit() uint32
|
||||
|
||||
SetPool(pool *WriterPool)
|
||||
ResetFinish()
|
||||
|
||||
@@ -2,10 +2,16 @@
|
||||
|
||||
package compressions
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type BaseWriter struct {
|
||||
pool *WriterPool
|
||||
|
||||
isFinished bool
|
||||
|
||||
hits uint32
|
||||
}
|
||||
|
||||
func (this *BaseWriter) SetPool(pool *WriterPool) {
|
||||
@@ -13,8 +19,11 @@ func (this *BaseWriter) SetPool(pool *WriterPool) {
|
||||
}
|
||||
|
||||
func (this *BaseWriter) Finish(obj Writer) error {
|
||||
if this.isFinished {
|
||||
return nil
|
||||
}
|
||||
err := obj.RawClose()
|
||||
if err == nil && this.pool != nil && !this.isFinished {
|
||||
if err == nil && this.pool != nil {
|
||||
this.pool.Put(obj)
|
||||
}
|
||||
this.isFinished = true
|
||||
@@ -24,3 +33,7 @@ func (this *BaseWriter) Finish(obj Writer) error {
|
||||
func (this *BaseWriter) ResetFinish() {
|
||||
this.isFinished = false
|
||||
}
|
||||
|
||||
func (this *BaseWriter) IncreaseHit() uint32 {
|
||||
return atomic.AddUint32(&this.hits, 1)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build !plus || !linux
|
||||
|
||||
package compressions
|
||||
|
||||
@@ -18,16 +19,12 @@ func NewBrotliWriter(writer io.Writer, level int) (Writer, error) {
|
||||
return sharedBrotliWriterPool.Get(writer, level)
|
||||
}
|
||||
|
||||
func newBrotliWriter(writer io.Writer, level int) (*BrotliWriter, error) {
|
||||
if level <= 0 {
|
||||
level = brotli.BestSpeed
|
||||
} else if level > brotli.BestCompression {
|
||||
level = brotli.BestCompression
|
||||
}
|
||||
func newBrotliWriter(writer io.Writer) (*BrotliWriter, error) {
|
||||
var level = GenerateCompressLevel(brotli.BestSpeed, brotli.BestCompression)
|
||||
return &BrotliWriter{
|
||||
writer: brotli.NewWriterOptions(writer, brotli.WriterOptions{
|
||||
Quality: level,
|
||||
LGWin: 13, // TODO 在全局设置里可以设置此值
|
||||
LGWin: 14, // TODO 在全局设置里可以设置此值
|
||||
}),
|
||||
level: level,
|
||||
}, nil
|
||||
|
||||
@@ -18,12 +18,8 @@ func NewDeflateWriter(writer io.Writer, level int) (Writer, error) {
|
||||
return sharedDeflateWriterPool.Get(writer, level)
|
||||
}
|
||||
|
||||
func newDeflateWriter(writer io.Writer, level int) (Writer, error) {
|
||||
if level <= 0 {
|
||||
level = flate.BestSpeed
|
||||
} else if level > flate.BestCompression {
|
||||
level = flate.BestCompression
|
||||
}
|
||||
func newDeflateWriter(writer io.Writer) (Writer, error) {
|
||||
var level = GenerateCompressLevel(flate.BestSpeed, flate.BestCompression)
|
||||
|
||||
flateWriter, err := flate.NewWriter(writer, level)
|
||||
if err != nil {
|
||||
|
||||
@@ -18,12 +18,8 @@ func NewGzipWriter(writer io.Writer, level int) (Writer, error) {
|
||||
return sharedGzipWriterPool.Get(writer, level)
|
||||
}
|
||||
|
||||
func newGzipWriter(writer io.Writer, level int) (Writer, error) {
|
||||
if level <= 0 {
|
||||
level = gzip.BestSpeed
|
||||
} else if level > gzip.BestCompression {
|
||||
level = gzip.BestCompression
|
||||
}
|
||||
func newGzipWriter(writer io.Writer) (Writer, error) {
|
||||
var level = GenerateCompressLevel(gzip.BestSpeed, gzip.BestCompression)
|
||||
|
||||
gzipWriter, err := gzip.NewWriterLevel(writer, level)
|
||||
if err != nil {
|
||||
|
||||
@@ -5,12 +5,15 @@ package compressions_test
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/compressions"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func BenchmarkGzipWriter_Write(b *testing.B) {
|
||||
var data = []byte(strings.Repeat("A", 1024))
|
||||
var data = make([]byte, 1024)
|
||||
for i := 0; i < 1024; i++ {
|
||||
data[i] = 'A' + byte(i%26)
|
||||
}
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
var buf = &bytes.Buffer{}
|
||||
@@ -36,7 +39,12 @@ func BenchmarkGzipWriter_Write(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkGzipWriter_Write_Parallel(b *testing.B) {
|
||||
var data = []byte(strings.Repeat("A", 1024))
|
||||
var data = make([]byte, 1024)
|
||||
for i := 0; i < 1024; i++ {
|
||||
data[i] = 'A' + byte(i%26)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
|
||||
@@ -3,38 +3,58 @@
|
||||
package compressions
|
||||
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
const maxWriterHits = 1 << 20
|
||||
|
||||
var isBusy = false
|
||||
|
||||
func init() {
|
||||
if !teaconst.IsMain {
|
||||
return
|
||||
}
|
||||
|
||||
goman.New(func() {
|
||||
var ticker = time.NewTicker(100 * time.Millisecond)
|
||||
for range ticker.C {
|
||||
if isBusy {
|
||||
isBusy = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func IsBusy() bool {
|
||||
return isBusy
|
||||
}
|
||||
|
||||
type WriterPool struct {
|
||||
m map[int]chan Writer // level => chan Writer
|
||||
c chan Writer // level => chan Writer
|
||||
newFunc func(writer io.Writer, level int) (Writer, error)
|
||||
}
|
||||
|
||||
func NewWriterPool(maxSize int, maxLevel int, newFunc func(writer io.Writer, level int) (Writer, error)) *WriterPool {
|
||||
func NewWriterPool(maxSize int, newFunc func(writer io.Writer, level int) (Writer, error)) *WriterPool {
|
||||
if maxSize <= 0 {
|
||||
maxSize = 1024
|
||||
}
|
||||
|
||||
var m = map[int]chan Writer{}
|
||||
for i := 0; i <= maxLevel; i++ {
|
||||
m[i] = make(chan Writer, maxSize)
|
||||
}
|
||||
|
||||
return &WriterPool{
|
||||
m: m,
|
||||
c: make(chan Writer, maxSize),
|
||||
newFunc: newFunc,
|
||||
}
|
||||
}
|
||||
|
||||
func (this *WriterPool) Get(parentWriter io.Writer, level int) (Writer, error) {
|
||||
c, ok := this.m[level]
|
||||
if !ok {
|
||||
c = this.m[0]
|
||||
if isBusy {
|
||||
return nil, ErrIsBusy
|
||||
}
|
||||
|
||||
select {
|
||||
case writer := <-c:
|
||||
case writer := <-this.c:
|
||||
writer.Reset(parentWriter)
|
||||
writer.ResetFinish()
|
||||
return writer, nil
|
||||
@@ -49,13 +69,14 @@ func (this *WriterPool) Get(parentWriter io.Writer, level int) (Writer, error) {
|
||||
}
|
||||
|
||||
func (this *WriterPool) Put(writer Writer) {
|
||||
var level = writer.Level()
|
||||
c, ok := this.m[level]
|
||||
if !ok {
|
||||
c = this.m[0]
|
||||
if writer.IncreaseHit() > maxWriterHits {
|
||||
// do nothing to discard it
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case c <- writer:
|
||||
case this.c <- writer:
|
||||
default:
|
||||
isBusy = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@ package compressions
|
||||
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/andybalholm/brotli"
|
||||
"io"
|
||||
)
|
||||
|
||||
@@ -16,11 +14,7 @@ func init() {
|
||||
return
|
||||
}
|
||||
|
||||
var maxSize = utils.SystemMemoryGB() * 256
|
||||
if maxSize == 0 {
|
||||
maxSize = 256
|
||||
}
|
||||
sharedBrotliWriterPool = NewWriterPool(maxSize, brotli.BestCompression, func(writer io.Writer, level int) (Writer, error) {
|
||||
return newBrotliWriter(writer, level)
|
||||
sharedBrotliWriterPool = NewWriterPool(CalculatePoolSize(), func(writer io.Writer, level int) (Writer, error) {
|
||||
return newBrotliWriter(writer)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
package compressions
|
||||
|
||||
import (
|
||||
"compress/flate"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"io"
|
||||
)
|
||||
|
||||
@@ -16,11 +14,7 @@ func init() {
|
||||
return
|
||||
}
|
||||
|
||||
var maxSize = utils.SystemMemoryGB() * 256
|
||||
if maxSize == 0 {
|
||||
maxSize = 256
|
||||
}
|
||||
sharedDeflateWriterPool = NewWriterPool(maxSize, flate.BestCompression, func(writer io.Writer, level int) (Writer, error) {
|
||||
return newDeflateWriter(writer, level)
|
||||
sharedDeflateWriterPool = NewWriterPool(CalculatePoolSize(), func(writer io.Writer, level int) (Writer, error) {
|
||||
return newDeflateWriter(writer)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
package compressions
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"io"
|
||||
)
|
||||
|
||||
@@ -16,11 +14,7 @@ func init() {
|
||||
return
|
||||
}
|
||||
|
||||
var maxSize = utils.SystemMemoryGB() * 256
|
||||
if maxSize == 0 {
|
||||
maxSize = 256
|
||||
}
|
||||
sharedGzipWriterPool = NewWriterPool(maxSize, gzip.BestCompression, func(writer io.Writer, level int) (Writer, error) {
|
||||
return newGzipWriter(writer, level)
|
||||
sharedGzipWriterPool = NewWriterPool(CalculatePoolSize(), func(writer io.Writer, level int) (Writer, error) {
|
||||
return newGzipWriter(writer)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@ package compressions
|
||||
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/klauspost/compress/zstd"
|
||||
"io"
|
||||
)
|
||||
|
||||
@@ -16,11 +14,7 @@ func init() {
|
||||
return
|
||||
}
|
||||
|
||||
var maxSize = utils.SystemMemoryGB() * 256
|
||||
if maxSize == 0 {
|
||||
maxSize = 256
|
||||
}
|
||||
sharedZSTDWriterPool = NewWriterPool(maxSize, int(zstd.SpeedBestCompression), func(writer io.Writer, level int) (Writer, error) {
|
||||
return newZSTDWriter(writer, level)
|
||||
sharedZSTDWriterPool = NewWriterPool(CalculatePoolSize(), func(writer io.Writer, level int) (Writer, error) {
|
||||
return newZSTDWriter(writer)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -18,17 +18,18 @@ func NewZSTDWriter(writer io.Writer, level int) (Writer, error) {
|
||||
return sharedZSTDWriterPool.Get(writer, level)
|
||||
}
|
||||
|
||||
func newZSTDWriter(writer io.Writer, level int) (Writer, error) {
|
||||
var zstdLevel = zstd.EncoderLevelFromZstd(level)
|
||||
func newZSTDWriter(writer io.Writer) (Writer, error) {
|
||||
var level = 1
|
||||
var zstdLevel = zstd.SpeedFastest
|
||||
|
||||
zstdWriter, err := zstd.NewWriter(writer, zstd.WithEncoderLevel(zstdLevel))
|
||||
zstdWriter, err := zstd.NewWriter(writer, zstd.WithEncoderLevel(zstdLevel), zstd.WithWindowSize(16<<10), zstd.WithLowerEncoderMem(true))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ZSTDWriter{
|
||||
writer: zstdWriter,
|
||||
level: level,
|
||||
writer: zstdWriter,
|
||||
level: level,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,24 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewZSTDWriter_Level0(t *testing.T) {
|
||||
var buf = &bytes.Buffer{}
|
||||
writer, err := compressions.NewZSTDWriter(buf, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var originData = []byte(strings.Repeat("Hello", 1024))
|
||||
_, err = writer.Write(originData)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = writer.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("origin data:", len(originData), "result:", buf.Len())
|
||||
}
|
||||
|
||||
func TestNewZSTDWriter(t *testing.T) {
|
||||
var buf = &bytes.Buffer{}
|
||||
writer, err := compressions.NewZSTDWriter(buf, 10)
|
||||
|
||||
@@ -12,8 +12,8 @@ const oldConfigFileName = "api.yaml"
|
||||
|
||||
type APIConfig struct {
|
||||
OldRPC struct {
|
||||
Endpoints []string `yaml:"endpoints" json:"endpoints"`
|
||||
DisableUpdate bool `yaml:"disableUpdate" json:"disableUpdate"`
|
||||
Endpoints []string `yaml:"endpoints,omitempty" json:"endpoints"`
|
||||
DisableUpdate bool `yaml:"disableUpdate,omitempty" json:"disableUpdate"`
|
||||
} `yaml:"rpc,omitempty" json:"rpc"`
|
||||
|
||||
RPCEndpoints []string `yaml:"rpc.endpoints,flow" json:"rpc.endpoints"`
|
||||
@@ -67,6 +67,12 @@ func LoadAPIConfig() (*APIConfig, error) {
|
||||
return nil, errors.New("init error: " + err.Error())
|
||||
}
|
||||
|
||||
// 自动生成新的配置文件
|
||||
if filename == oldConfigFileName {
|
||||
config.OldRPC.Endpoints = nil
|
||||
_ = config.WriteFile(Tea.ConfigFile(ConfigFileName))
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
return nil, errors.New("no config file '" + ConfigFileName + "' found")
|
||||
|
||||
@@ -4,11 +4,16 @@ package configs_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/configs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
"gopkg.in/yaml.v3"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLoadClusterConfig(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
config, err := configs.LoadClusterConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
67
internal/conns/map_test_test.go
Normal file
67
internal/conns/map_test_test.go
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package conns_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/conns"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
"net"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type testConn struct {
|
||||
net.Conn
|
||||
|
||||
addr net.Addr
|
||||
}
|
||||
|
||||
func (this *testConn) Read(b []byte) (n int, err error) {
|
||||
return
|
||||
}
|
||||
func (this *testConn) Write(b []byte) (n int, err error) {
|
||||
return
|
||||
}
|
||||
func (this *testConn) Close() error {
|
||||
return nil
|
||||
}
|
||||
func (this *testConn) LocalAddr() net.Addr {
|
||||
return &net.TCPAddr{
|
||||
IP: net.ParseIP(testutils.RandIP()),
|
||||
Port: 1234,
|
||||
}
|
||||
}
|
||||
func (this *testConn) RemoteAddr() net.Addr {
|
||||
if this.addr != nil {
|
||||
return this.addr
|
||||
}
|
||||
this.addr = &net.TCPAddr{
|
||||
IP: net.ParseIP(testutils.RandIP()),
|
||||
Port: 1234,
|
||||
}
|
||||
return this.addr
|
||||
}
|
||||
func (this *testConn) SetDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
func (this *testConn) SetReadDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
func (this *testConn) SetWriteDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func BenchmarkMap_Add(b *testing.B) {
|
||||
runtime.GOMAXPROCS(512)
|
||||
|
||||
var m = conns.NewMap()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
var conn = &testConn{}
|
||||
m.Add(conn)
|
||||
m.Remove(conn)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package teaconst
|
||||
|
||||
const (
|
||||
Version = "1.2.7"
|
||||
Version = "1.3.5"
|
||||
|
||||
ProductName = "Edge Node"
|
||||
ProcessName = "edge-node"
|
||||
@@ -14,5 +14,8 @@ const (
|
||||
// SystemdServiceName systemd
|
||||
SystemdServiceName = "edge-node"
|
||||
|
||||
AccessLogSockName = "edge-node.accesslog.sock"
|
||||
AccessLogSockName = "edge-node.accesslog"
|
||||
CacheGarbageSockName = "edge-node.cache.garbage"
|
||||
|
||||
EnableKVCacheStore = true // determine store cache keys in KVStore or sqlite
|
||||
)
|
||||
|
||||
@@ -6,8 +6,10 @@ import (
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var currentFirewall FirewallInterface
|
||||
@@ -35,6 +37,23 @@ func Firewall() FirewallInterface {
|
||||
return currentFirewall
|
||||
}
|
||||
|
||||
// http firewall
|
||||
{
|
||||
endpoint, _ := os.LookupEnv("EDGE_HTTP_FIREWALL_ENDPOINT")
|
||||
if len(endpoint) > 0 {
|
||||
var httpFirewall = NewHTTPFirewall(endpoint)
|
||||
for i := 0; i < 10; i++ {
|
||||
if httpFirewall.IsReady() {
|
||||
currentFirewall = httpFirewall
|
||||
remotelogs.Println("FIREWALL", "using http firewall '"+endpoint+"'")
|
||||
break
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
return httpFirewall
|
||||
}
|
||||
}
|
||||
|
||||
// nftables
|
||||
if runtime.GOOS == "linux" {
|
||||
nftables, err := NewNFTablesFirewall()
|
||||
|
||||
150
internal/firewalls/firewall_http.go
Normal file
150
internal/firewalls/firewall_http.go
Normal file
@@ -0,0 +1,150 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package firewalls
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type HTTPFirewall struct {
|
||||
client *http.Client
|
||||
endpoint string
|
||||
}
|
||||
|
||||
func NewHTTPFirewall(endpoint string) *HTTPFirewall {
|
||||
return &HTTPFirewall{
|
||||
client: http.DefaultClient,
|
||||
endpoint: endpoint,
|
||||
}
|
||||
}
|
||||
|
||||
// Name 名称
|
||||
func (this *HTTPFirewall) Name() string {
|
||||
result, err := this.get("/name", nil)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return result.GetString("name")
|
||||
}
|
||||
|
||||
// IsReady 是否已准备被调用
|
||||
func (this *HTTPFirewall) IsReady() bool {
|
||||
result, err := this.get("/isReady", nil)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return result.GetBool("result")
|
||||
}
|
||||
|
||||
// IsMock 是否为模拟
|
||||
func (this *HTTPFirewall) IsMock() bool {
|
||||
result, err := this.get("/isMock", nil)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return result.GetBool("result")
|
||||
}
|
||||
|
||||
// AllowPort 允许端口
|
||||
func (this *HTTPFirewall) AllowPort(port int, protocol string) error {
|
||||
_, err := this.get("/allowPort", map[string]string{
|
||||
"port": types.String(port),
|
||||
"protocol": protocol,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// RemovePort 删除端口
|
||||
func (this *HTTPFirewall) RemovePort(port int, protocol string) error {
|
||||
_, err := this.get("/removePort", map[string]string{
|
||||
"port": types.String(port),
|
||||
"protocol": protocol,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// RejectSourceIP 拒绝某个源IP连接
|
||||
func (this *HTTPFirewall) RejectSourceIP(ip string, timeoutSeconds int) error {
|
||||
_, err := this.get("/rejectSourceIP", map[string]string{
|
||||
"ip": ip,
|
||||
"timeoutSeconds": types.String(timeoutSeconds),
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// DropSourceIP 丢弃某个源IP数据
|
||||
// ip 要封禁的IP
|
||||
// timeoutSeconds 过期时间
|
||||
// async 是否异步
|
||||
func (this *HTTPFirewall) DropSourceIP(ip string, timeoutSeconds int, async bool) error {
|
||||
var asyncString = "false"
|
||||
if async {
|
||||
asyncString = "true"
|
||||
}
|
||||
_, err := this.get("/dropSourceIP", map[string]string{
|
||||
"ip": ip,
|
||||
"timeoutSeconds": types.String(timeoutSeconds),
|
||||
"async": asyncString,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// RemoveSourceIP 删除某个源IP
|
||||
func (this *HTTPFirewall) RemoveSourceIP(ip string) error {
|
||||
_, err := this.get("/removeSourceIP", map[string]string{
|
||||
"ip": ip,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (this *HTTPFirewall) get(path string, args map[string]string) (result maps.Map, err error) {
|
||||
var urlString = this.endpoint + path
|
||||
if len(args) > 0 {
|
||||
var query = &url.Values{}
|
||||
for k, v := range args {
|
||||
query.Add(k, v)
|
||||
}
|
||||
urlString += "?" + query.Encode()
|
||||
}
|
||||
req, err := http.NewRequest(http.MethodGet, urlString, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := this.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, errors.New("server response code '" + types.String(resp.StatusCode) + "'")
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read response failed: %w", err)
|
||||
}
|
||||
|
||||
if len(data) == 0 {
|
||||
return maps.Map{}, nil
|
||||
}
|
||||
|
||||
result = maps.Map{}
|
||||
err = json.Unmarshal(data, &result)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decode response failed: %w", err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -6,7 +6,7 @@ package firewalls
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/iputils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/conns"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||||
@@ -386,12 +386,12 @@ func (this *NFTablesFirewall) DropSourceIP(ip string, timeoutSeconds int, async
|
||||
// 再次尝试关闭连接
|
||||
defer conns.SharedMap.CloseIPConns(ip)
|
||||
|
||||
var ipLong = configutils.IPString2Long(ip)
|
||||
if strings.Contains(ip, ":") { // ipv6
|
||||
if len(this.denyIPv6Sets) == 0 {
|
||||
return errors.New("ipv6 ip set not found")
|
||||
}
|
||||
return this.denyIPv6Sets[ipLong%uint64(len(this.denyIPv6Sets))].AddElement(data.To16(), &nftables.ElementOptions{
|
||||
var setIndex = iputils.ParseIP(ip).Mod(len(this.denyIPv6Sets))
|
||||
return this.denyIPv6Sets[setIndex].AddElement(data.To16(), &nftables.ElementOptions{
|
||||
Timeout: time.Duration(timeoutSeconds) * time.Second,
|
||||
}, false)
|
||||
}
|
||||
@@ -400,7 +400,8 @@ func (this *NFTablesFirewall) DropSourceIP(ip string, timeoutSeconds int, async
|
||||
if len(this.denyIPv4Sets) == 0 {
|
||||
return errors.New("ipv4 ip set not found")
|
||||
}
|
||||
return this.denyIPv4Sets[ipLong%uint64(len(this.denyIPv4Sets))].AddElement(data.To4(), &nftables.ElementOptions{
|
||||
var setIndex = iputils.ParseIP(ip).Mod(len(this.denyIPv4Sets))
|
||||
return this.denyIPv4Sets[setIndex].AddElement(data.To4(), &nftables.ElementOptions{
|
||||
Timeout: time.Duration(timeoutSeconds) * time.Second,
|
||||
}, false)
|
||||
}
|
||||
@@ -412,10 +413,10 @@ func (this *NFTablesFirewall) RemoveSourceIP(ip string) error {
|
||||
return errors.New("invalid ip '" + ip + "'")
|
||||
}
|
||||
|
||||
var ipLong = configutils.IPString2Long(ip)
|
||||
if strings.Contains(ip, ":") { // ipv6
|
||||
var setIndex = iputils.ParseIP(ip).Mod(len(this.denyIPv6Sets))
|
||||
if len(this.denyIPv6Sets) > 0 {
|
||||
err := this.denyIPv6Sets[ipLong%uint64(len(this.denyIPv6Sets))].DeleteElement(data.To16())
|
||||
err := this.denyIPv6Sets[setIndex].DeleteElement(data.To16())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -433,7 +434,8 @@ func (this *NFTablesFirewall) RemoveSourceIP(ip string) error {
|
||||
|
||||
// ipv4
|
||||
if len(this.denyIPv4Sets) > 0 {
|
||||
err := this.denyIPv4Sets[ipLong%uint64(len(this.denyIPv4Sets))].DeleteElement(data.To4())
|
||||
var setIndex = iputils.ParseIP(ip).Mod(len(this.denyIPv4Sets))
|
||||
err := this.denyIPv4Sets[setIndex].DeleteElement(data.To4())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ package nftables_test
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/firewalls/nftables"
|
||||
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
|
||||
"os/exec"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -21,7 +20,10 @@ func TestConn_Test(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConn_GetTable_NotFound(t *testing.T) {
|
||||
var conn = nftables.NewConn()
|
||||
conn, err := nftables.NewConn()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
table, err := conn.GetTable("a", nftables.TableFamilyIPv4)
|
||||
if err != nil {
|
||||
@@ -36,7 +38,10 @@ func TestConn_GetTable_NotFound(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConn_GetTable(t *testing.T) {
|
||||
var conn = nftables.NewConn()
|
||||
conn, err := nftables.NewConn()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
table, err := conn.GetTable("myFilter", nftables.TableFamilyIPv4)
|
||||
if err != nil {
|
||||
@@ -51,7 +56,10 @@ func TestConn_GetTable(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConn_AddTable(t *testing.T) {
|
||||
var conn = nftables.NewConn()
|
||||
conn, err := nftables.NewConn()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
{
|
||||
table, err := conn.AddIPv4Table("test_ipv4")
|
||||
@@ -70,8 +78,12 @@ func TestConn_AddTable(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConn_DeleteTable(t *testing.T) {
|
||||
var conn = nftables.NewConn()
|
||||
err := conn.DeleteTable("test_ipv4", nftables.TableFamilyIPv4)
|
||||
conn, err := nftables.NewConn()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = conn.DeleteTable("test_ipv4", nftables.TableFamilyIPv4)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -3,11 +3,16 @@ package iplibrary
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHTTPAPIAction_AddItem(t *testing.T) {
|
||||
action := NewHTTPAPIAction()
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var action = NewHTTPAPIAction()
|
||||
action.config = &firewallconfigs.FirewallActionHTTPAPIConfig{
|
||||
URL: "http://127.0.0.1:2345/post",
|
||||
TimeoutSeconds: 0,
|
||||
@@ -24,7 +29,11 @@ func TestHTTPAPIAction_AddItem(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestHTTPAPIAction_DeleteItem(t *testing.T) {
|
||||
action := NewHTTPAPIAction()
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var action = NewHTTPAPIAction()
|
||||
action.config = &firewallconfigs.FirewallActionHTTPAPIConfig{
|
||||
URL: "http://127.0.0.1:2345/post",
|
||||
TimeoutSeconds: 0,
|
||||
|
||||
@@ -4,13 +4,19 @@ import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/iplibrary"
|
||||
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestIPSetAction_Init(t *testing.T) {
|
||||
action := iplibrary.NewIPSetAction()
|
||||
_, lookupErr := executils.LookPath("iptables")
|
||||
if lookupErr != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var action = iplibrary.NewIPSetAction()
|
||||
err := action.Init(&firewallconfigs.FirewallActionConfig{
|
||||
Params: maps.Map{
|
||||
"path": "/usr/bin/iptables",
|
||||
@@ -25,6 +31,11 @@ func TestIPSetAction_Init(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIPSetAction_AddItem(t *testing.T) {
|
||||
_, lookupErr := executils.LookPath("iptables")
|
||||
if lookupErr != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var action = iplibrary.NewIPSetAction()
|
||||
action.SetConfig(&firewallconfigs.FirewallActionIPSetConfig{
|
||||
Path: "/usr/bin/iptables",
|
||||
@@ -84,7 +95,12 @@ func TestIPSetAction_AddItem(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIPSetAction_DeleteItem(t *testing.T) {
|
||||
action := iplibrary.NewIPSetAction()
|
||||
_, lookupErr := executils.LookPath("firewalld")
|
||||
if lookupErr != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var action = iplibrary.NewIPSetAction()
|
||||
err := action.Init(&firewallconfigs.FirewallActionConfig{
|
||||
Params: maps.Map{
|
||||
"path": "/usr/bin/firewalld",
|
||||
|
||||
@@ -3,12 +3,18 @@ package iplibrary
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestIPTablesAction_AddItem(t *testing.T) {
|
||||
action := NewIPTablesAction()
|
||||
_, lookupErr := executils.LookPath("iptables")
|
||||
if lookupErr != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var action = NewIPTablesAction()
|
||||
action.config = &firewallconfigs.FirewallActionIPTablesConfig{
|
||||
Path: "/usr/bin/iptables",
|
||||
}
|
||||
@@ -40,7 +46,12 @@ func TestIPTablesAction_AddItem(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIPTablesAction_DeleteItem(t *testing.T) {
|
||||
action := NewIPTablesAction()
|
||||
_, lookupErr := executils.LookPath("firewalld")
|
||||
if lookupErr != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var action = NewIPTablesAction()
|
||||
action.config = &firewallconfigs.FirewallActionIPTablesConfig{
|
||||
Path: "/usr/bin/firewalld",
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
func TestActionManager_UpdateActions(t *testing.T) {
|
||||
manager := NewActionManager()
|
||||
var manager = NewActionManager()
|
||||
manager.UpdateActions([]*firewallconfigs.FirewallActionConfig{
|
||||
{
|
||||
Id: 1,
|
||||
|
||||
@@ -3,11 +3,16 @@ package iplibrary
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestScriptAction_AddItem(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
action := NewScriptAction()
|
||||
action.config = &firewallconfigs.FirewallActionScriptConfig{
|
||||
Path: "/tmp/ip-item.sh",
|
||||
@@ -27,6 +32,10 @@ func TestScriptAction_AddItem(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestScriptAction_DeleteItem(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
action := NewScriptAction()
|
||||
action.config = &firewallconfigs.FirewallActionScriptConfig{
|
||||
Path: "/tmp/ip-item.sh",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package iplibrary
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/iputils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
)
|
||||
|
||||
@@ -14,36 +15,37 @@ const (
|
||||
|
||||
// IPItem IP条目
|
||||
type IPItem struct {
|
||||
Type string `json:"type"`
|
||||
Id uint64 `json:"id"`
|
||||
IPFrom uint64 `json:"ipFrom"`
|
||||
IPTo uint64 `json:"ipTo"`
|
||||
Type string `json:"type"`
|
||||
Id uint64 `json:"id"`
|
||||
IPFrom []byte `json:"ipFrom"`
|
||||
IPTo []byte `json:"ipTo"`
|
||||
|
||||
ExpiredAt int64 `json:"expiredAt"`
|
||||
EventLevel string `json:"eventLevel"`
|
||||
}
|
||||
|
||||
// Contains 检查是否包含某个IP
|
||||
func (this *IPItem) Contains(ip uint64) bool {
|
||||
func (this *IPItem) Contains(ipBytes []byte) bool {
|
||||
switch this.Type {
|
||||
case IPItemTypeIPv4:
|
||||
return this.containsIPv4(ip)
|
||||
return this.containsIP(ipBytes)
|
||||
case IPItemTypeIPv6:
|
||||
return this.containsIPv6(ip)
|
||||
return this.containsIP(ipBytes)
|
||||
case IPItemTypeAll:
|
||||
return this.containsAll()
|
||||
default:
|
||||
return this.containsIPv4(ip)
|
||||
return this.containsIP(ipBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否包含某个IPv4
|
||||
func (this *IPItem) containsIPv4(ip uint64) bool {
|
||||
if this.IPTo == 0 {
|
||||
if this.IPFrom != ip {
|
||||
// 检查是否包含某个
|
||||
func (this *IPItem) containsIP(ipBytes []byte) bool {
|
||||
if IsZero(this.IPTo) {
|
||||
if iputils.CompareBytes(this.IPFrom, ipBytes) != 0 {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
if this.IPFrom > ip || this.IPTo < ip {
|
||||
if iputils.CompareBytes(this.IPFrom, ipBytes) > 0 || iputils.CompareBytes(this.IPTo, ipBytes) < 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -53,17 +55,6 @@ func (this *IPItem) containsIPv4(ip uint64) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// 检查是否包含某个IPv6
|
||||
func (this *IPItem) containsIPv6(ip uint64) bool {
|
||||
if this.IPFrom != ip {
|
||||
return false
|
||||
}
|
||||
if this.ExpiredAt > 0 && this.ExpiredAt < fasttime.Now().Unix() {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// 检查是否包所有IP
|
||||
func (this *IPItem) containsAll() bool {
|
||||
if this.ExpiredAt > 0 && this.ExpiredAt < fasttime.Now().Unix() {
|
||||
|
||||
@@ -1,108 +1,125 @@
|
||||
package iplibrary
|
||||
package iplibrary_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/iputils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/iplibrary"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
"github.com/iwind/TeaGo/assert"
|
||||
"math/rand"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestIPItem_Contains(t *testing.T) {
|
||||
a := assert.NewAssertion(t)
|
||||
var a = assert.NewAssertion(t)
|
||||
|
||||
{
|
||||
item := &IPItem{
|
||||
IPFrom: utils.IP2Long("192.168.1.100"),
|
||||
IPTo: 0,
|
||||
var item = &iplibrary.IPItem{
|
||||
IPFrom: iputils.ToBytes("192.168.1.100"),
|
||||
IPTo: nil,
|
||||
ExpiredAt: 0,
|
||||
}
|
||||
a.IsTrue(item.Contains(utils.IP2Long("192.168.1.100")))
|
||||
a.IsTrue(item.Contains(iputils.ToBytes("192.168.1.100")))
|
||||
}
|
||||
|
||||
{
|
||||
item := &IPItem{
|
||||
IPFrom: utils.IP2Long("192.168.1.100"),
|
||||
IPTo: 0,
|
||||
var item = &iplibrary.IPItem{
|
||||
IPFrom: iputils.ToBytes("192.168.1.100"),
|
||||
IPTo: nil,
|
||||
ExpiredAt: time.Now().Unix() + 1,
|
||||
}
|
||||
a.IsTrue(item.Contains(utils.IP2Long("192.168.1.100")))
|
||||
a.IsTrue(item.Contains(iputils.ToBytes("192.168.1.100")))
|
||||
}
|
||||
|
||||
{
|
||||
item := &IPItem{
|
||||
IPFrom: utils.IP2Long("192.168.1.100"),
|
||||
IPTo: 0,
|
||||
var item = &iplibrary.IPItem{
|
||||
IPFrom: iputils.ToBytes("192.168.1.100"),
|
||||
IPTo: nil,
|
||||
ExpiredAt: time.Now().Unix() - 1,
|
||||
}
|
||||
a.IsFalse(item.Contains(utils.IP2Long("192.168.1.100")))
|
||||
a.IsFalse(item.Contains(iputils.ToBytes("192.168.1.100")))
|
||||
}
|
||||
{
|
||||
item := &IPItem{
|
||||
IPFrom: utils.IP2Long("192.168.1.100"),
|
||||
IPTo: 0,
|
||||
var item = &iplibrary.IPItem{
|
||||
IPFrom: iputils.ToBytes("192.168.1.100"),
|
||||
IPTo: nil,
|
||||
ExpiredAt: 0,
|
||||
}
|
||||
a.IsFalse(item.Contains(utils.IP2Long("192.168.1.101")))
|
||||
a.IsFalse(item.Contains(iputils.ToBytes("192.168.1.101")))
|
||||
}
|
||||
|
||||
{
|
||||
item := &IPItem{
|
||||
IPFrom: utils.IP2Long("192.168.1.1"),
|
||||
IPTo: utils.IP2Long("192.168.1.101"),
|
||||
var item = &iplibrary.IPItem{
|
||||
IPFrom: iputils.ToBytes("192.168.1.1"),
|
||||
IPTo: iputils.ToBytes("192.168.1.101"),
|
||||
ExpiredAt: 0,
|
||||
}
|
||||
a.IsTrue(item.Contains(utils.IP2Long("192.168.1.100")))
|
||||
a.IsTrue(item.Contains(iputils.ToBytes("192.168.1.100")))
|
||||
}
|
||||
|
||||
{
|
||||
item := &IPItem{
|
||||
IPFrom: utils.IP2Long("192.168.1.1"),
|
||||
IPTo: utils.IP2Long("192.168.1.100"),
|
||||
var item = &iplibrary.IPItem{
|
||||
IPFrom: iputils.ToBytes("192.168.1.1"),
|
||||
IPTo: iputils.ToBytes("192.168.1.100"),
|
||||
ExpiredAt: 0,
|
||||
}
|
||||
a.IsTrue(item.Contains(utils.IP2Long("192.168.1.100")))
|
||||
a.IsTrue(item.Contains(iputils.ToBytes("192.168.1.100")))
|
||||
}
|
||||
|
||||
{
|
||||
item := &IPItem{
|
||||
IPFrom: utils.IP2Long("192.168.1.1"),
|
||||
IPTo: utils.IP2Long("192.168.1.101"),
|
||||
var item = &iplibrary.IPItem{
|
||||
IPFrom: iputils.ToBytes("192.168.1.1"),
|
||||
IPTo: iputils.ToBytes("192.168.1.101"),
|
||||
ExpiredAt: 0,
|
||||
}
|
||||
a.IsTrue(item.Contains(utils.IP2Long("192.168.1.1")))
|
||||
a.IsTrue(item.Contains(iputils.ToBytes("192.168.1.1")))
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPItem_Memory(t *testing.T) {
|
||||
var list = NewIPList()
|
||||
for i := 0; i < 2_000_000; i ++ {
|
||||
list.Add(&IPItem{
|
||||
var isSingleTest = testutils.IsSingleTesting()
|
||||
|
||||
var list = iplibrary.NewIPList()
|
||||
var count = 100
|
||||
if isSingleTest {
|
||||
count = 2_000_000
|
||||
}
|
||||
for i := 0; i < count; i++ {
|
||||
list.Add(&iplibrary.IPItem{
|
||||
Type: "ip",
|
||||
Id: uint64(i),
|
||||
IPFrom: utils.IP2Long("192.168.1.1"),
|
||||
IPTo: 0,
|
||||
IPFrom: iputils.ToBytes("192.168.1.1"),
|
||||
IPTo: nil,
|
||||
ExpiredAt: time.Now().Unix(),
|
||||
EventLevel: "",
|
||||
})
|
||||
}
|
||||
|
||||
runtime.GC()
|
||||
|
||||
t.Log("waiting")
|
||||
time.Sleep(10 * time.Second)
|
||||
if isSingleTest {
|
||||
time.Sleep(10 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkIPItem_Contains(b *testing.B) {
|
||||
runtime.GOMAXPROCS(1)
|
||||
|
||||
item := &IPItem{
|
||||
IPFrom: utils.IP2Long("192.168.1.1"),
|
||||
IPTo: utils.IP2Long("192.168.1.101"),
|
||||
var item = &iplibrary.IPItem{
|
||||
IPFrom: iputils.ToBytes("192.168.1.1"),
|
||||
IPTo: iputils.ToBytes("192.168.1.101"),
|
||||
ExpiredAt: 0,
|
||||
}
|
||||
ip := utils.IP2Long("192.168.1.1")
|
||||
for i := 0; i < b.N; i++ {
|
||||
for j := 0; j < 10_000; j++ {
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
var ip = iputils.ToBytes("192.168.1." + strconv.Itoa(rand.Int()%255))
|
||||
item.Contains(ip)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package iplibrary
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/iputils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/expires"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
"sort"
|
||||
@@ -12,24 +12,32 @@ var GlobalBlackIPList = NewIPList()
|
||||
var GlobalWhiteIPList = NewIPList()
|
||||
|
||||
// IPList IP名单
|
||||
// TODO IP名单可以分片关闭,这样让每一片的数据量减少,查询更快
|
||||
// TODO 对ipMap进行分区
|
||||
type IPList struct {
|
||||
itemsMap map[uint64]*IPItem // id => item
|
||||
sortedItems []*IPItem
|
||||
isDeleted bool
|
||||
|
||||
itemsMap map[uint64]*IPItem // id => item
|
||||
|
||||
sortedRangeItems []*IPItem
|
||||
ipMap map[string]*IPItem // ipFrom => IPItem
|
||||
bufferItemsMap map[uint64]*IPItem // id => IPItem
|
||||
|
||||
allItemsMap map[uint64]*IPItem // id => item
|
||||
|
||||
expireList *expires.List
|
||||
|
||||
locker sync.RWMutex
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func NewIPList() *IPList {
|
||||
list := &IPList{
|
||||
itemsMap: map[uint64]*IPItem{},
|
||||
allItemsMap: map[uint64]*IPItem{},
|
||||
var list = &IPList{
|
||||
itemsMap: map[uint64]*IPItem{},
|
||||
bufferItemsMap: map[uint64]*IPItem{},
|
||||
allItemsMap: map[uint64]*IPItem{},
|
||||
ipMap: map[string]*IPItem{},
|
||||
}
|
||||
|
||||
expireList := expires.NewList()
|
||||
var expireList = expires.NewList()
|
||||
expireList.OnGC(func(itemId uint64) {
|
||||
list.Delete(itemId)
|
||||
})
|
||||
@@ -38,52 +46,70 @@ func NewIPList() *IPList {
|
||||
}
|
||||
|
||||
func (this *IPList) Add(item *IPItem) {
|
||||
this.addItem(item, true)
|
||||
if this.isDeleted {
|
||||
return
|
||||
}
|
||||
|
||||
this.addItem(item, true, true)
|
||||
}
|
||||
|
||||
// AddDelay 延迟添加,需要手工调用Sort()函数
|
||||
func (this *IPList) AddDelay(item *IPItem) {
|
||||
this.addItem(item, false)
|
||||
if this.isDeleted || item == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !IsZero(item.IPTo) {
|
||||
this.mu.Lock()
|
||||
this.bufferItemsMap[item.Id] = item
|
||||
this.mu.Unlock()
|
||||
} else {
|
||||
this.addItem(item, true, true)
|
||||
}
|
||||
}
|
||||
|
||||
func (this *IPList) Sort() {
|
||||
this.locker.Lock()
|
||||
this.sortItems()
|
||||
this.locker.Unlock()
|
||||
this.mu.Lock()
|
||||
this.sortRangeItems(false)
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
func (this *IPList) Delete(itemId uint64) {
|
||||
this.locker.Lock()
|
||||
this.mu.Lock()
|
||||
this.deleteItem(itemId)
|
||||
this.locker.Unlock()
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// Contains 判断是否包含某个IP
|
||||
func (this *IPList) Contains(ip uint64) bool {
|
||||
this.locker.RLock()
|
||||
func (this *IPList) Contains(ipBytes []byte) bool {
|
||||
if this.isDeleted {
|
||||
return false
|
||||
}
|
||||
|
||||
this.mu.RLock()
|
||||
defer this.mu.RUnlock()
|
||||
|
||||
if len(this.allItemsMap) > 0 {
|
||||
this.locker.RUnlock()
|
||||
return true
|
||||
}
|
||||
|
||||
var item = this.lookupIP(ip)
|
||||
|
||||
this.locker.RUnlock()
|
||||
|
||||
var item = this.lookupIP(ipBytes)
|
||||
return item != nil
|
||||
}
|
||||
|
||||
// ContainsExpires 判断是否包含某个IP
|
||||
func (this *IPList) ContainsExpires(ip uint64) (expiresAt int64, ok bool) {
|
||||
this.locker.RLock()
|
||||
func (this *IPList) ContainsExpires(ipBytes []byte) (expiresAt int64, ok bool) {
|
||||
if this.isDeleted {
|
||||
return
|
||||
}
|
||||
|
||||
this.mu.RLock()
|
||||
defer this.mu.RUnlock()
|
||||
|
||||
if len(this.allItemsMap) > 0 {
|
||||
this.locker.RUnlock()
|
||||
return 0, true
|
||||
}
|
||||
|
||||
var item = this.lookupIP(ip)
|
||||
|
||||
this.locker.RUnlock()
|
||||
var item = this.lookupIP(ipBytes)
|
||||
|
||||
if item == nil {
|
||||
return
|
||||
@@ -94,10 +120,17 @@ func (this *IPList) ContainsExpires(ip uint64) (expiresAt int64, ok bool) {
|
||||
|
||||
// ContainsIPStrings 是否包含一组IP中的任意一个,并返回匹配的第一个Item
|
||||
func (this *IPList) ContainsIPStrings(ipStrings []string) (item *IPItem, found bool) {
|
||||
if this.isDeleted {
|
||||
return
|
||||
}
|
||||
|
||||
if len(ipStrings) == 0 {
|
||||
return
|
||||
}
|
||||
this.locker.RLock()
|
||||
|
||||
this.mu.RLock()
|
||||
defer this.mu.RUnlock()
|
||||
|
||||
if len(this.allItemsMap) > 0 {
|
||||
for _, allItem := range this.allItemsMap {
|
||||
item = allItem
|
||||
@@ -105,7 +138,6 @@ func (this *IPList) ContainsIPStrings(ipStrings []string) (item *IPItem, found b
|
||||
}
|
||||
|
||||
if item != nil {
|
||||
this.locker.RUnlock()
|
||||
found = true
|
||||
return
|
||||
}
|
||||
@@ -114,18 +146,40 @@ func (this *IPList) ContainsIPStrings(ipStrings []string) (item *IPItem, found b
|
||||
if len(ipString) == 0 {
|
||||
continue
|
||||
}
|
||||
item = this.lookupIP(utils.IP2Long(ipString))
|
||||
item = this.lookupIP(iputils.ToBytes(ipString))
|
||||
if item != nil {
|
||||
this.locker.RUnlock()
|
||||
found = true
|
||||
return
|
||||
}
|
||||
}
|
||||
this.locker.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
func (this *IPList) addItem(item *IPItem, sortable bool) {
|
||||
func (this *IPList) SetDeleted() {
|
||||
this.isDeleted = true
|
||||
}
|
||||
|
||||
func (this *IPList) SortedRangeItems() []*IPItem {
|
||||
return this.sortedRangeItems
|
||||
}
|
||||
|
||||
func (this *IPList) IPMap() map[string]*IPItem {
|
||||
return this.ipMap
|
||||
}
|
||||
|
||||
func (this *IPList) ItemsMap() map[uint64]*IPItem {
|
||||
return this.itemsMap
|
||||
}
|
||||
|
||||
func (this *IPList) AllItemsMap() map[uint64]*IPItem {
|
||||
return this.allItemsMap
|
||||
}
|
||||
|
||||
func (this *IPList) BufferItemsMap() map[uint64]*IPItem {
|
||||
return this.bufferItemsMap
|
||||
}
|
||||
|
||||
func (this *IPList) addItem(item *IPItem, lock bool, sortable bool) {
|
||||
if item == nil {
|
||||
return
|
||||
}
|
||||
@@ -134,20 +188,29 @@ func (this *IPList) addItem(item *IPItem, sortable bool) {
|
||||
return
|
||||
}
|
||||
|
||||
if item.IPFrom == 0 && item.IPTo == 0 {
|
||||
var shouldSort bool
|
||||
|
||||
if iputils.CompareBytes(item.IPFrom, item.IPTo) == 0 {
|
||||
item.IPTo = nil
|
||||
}
|
||||
|
||||
if IsZero(item.IPFrom) && IsZero(item.IPTo) {
|
||||
if item.Type != IPItemTypeAll {
|
||||
return
|
||||
}
|
||||
} else if item.IPTo > 0 {
|
||||
if item.IPFrom > item.IPTo {
|
||||
} else if !IsZero(item.IPTo) {
|
||||
if iputils.CompareBytes(item.IPFrom, item.IPTo) > 0 {
|
||||
item.IPFrom, item.IPTo = item.IPTo, item.IPFrom
|
||||
} else if item.IPFrom == 0 {
|
||||
} else if IsZero(item.IPFrom) {
|
||||
item.IPFrom = item.IPTo
|
||||
item.IPTo = 0
|
||||
item.IPTo = nil
|
||||
}
|
||||
}
|
||||
|
||||
this.locker.Lock()
|
||||
if lock {
|
||||
this.mu.Lock()
|
||||
defer this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 是否已经存在
|
||||
_, ok := this.itemsMap[item.Id]
|
||||
@@ -158,51 +221,72 @@ func (this *IPList) addItem(item *IPItem, sortable bool) {
|
||||
this.itemsMap[item.Id] = item
|
||||
|
||||
// 展开
|
||||
if item.IPFrom > 0 {
|
||||
this.sortedItems = append(this.sortedItems, item)
|
||||
} else {
|
||||
if item.Type == IPItemTypeAll {
|
||||
this.allItemsMap[item.Id] = item
|
||||
} else if !IsZero(item.IPFrom) {
|
||||
if !IsZero(item.IPTo) {
|
||||
this.sortedRangeItems = append(this.sortedRangeItems, item)
|
||||
shouldSort = true
|
||||
} else {
|
||||
this.ipMap[ToHex(item.IPFrom)] = item
|
||||
}
|
||||
}
|
||||
|
||||
if item.ExpiredAt > 0 {
|
||||
this.expireList.Add(item.Id, item.ExpiredAt)
|
||||
}
|
||||
|
||||
if sortable {
|
||||
this.sortItems()
|
||||
if shouldSort && sortable {
|
||||
this.sortRangeItems(true)
|
||||
}
|
||||
|
||||
this.locker.Unlock()
|
||||
}
|
||||
|
||||
// 对列表进行排序
|
||||
func (this *IPList) sortItems() {
|
||||
sort.Slice(this.sortedItems, func(i, j int) bool {
|
||||
var item1 = this.sortedItems[i]
|
||||
var item2 = this.sortedItems[j]
|
||||
if item1.IPFrom == item2.IPFrom {
|
||||
return item1.IPTo < item2.IPTo
|
||||
func (this *IPList) sortRangeItems(force bool) {
|
||||
if len(this.bufferItemsMap) > 0 {
|
||||
for _, item := range this.bufferItemsMap {
|
||||
this.addItem(item, false, false)
|
||||
}
|
||||
return item1.IPFrom < item2.IPFrom
|
||||
})
|
||||
this.bufferItemsMap = map[uint64]*IPItem{}
|
||||
force = true
|
||||
}
|
||||
|
||||
if force {
|
||||
sort.Slice(this.sortedRangeItems, func(i, j int) bool {
|
||||
var item1 = this.sortedRangeItems[i]
|
||||
var item2 = this.sortedRangeItems[j]
|
||||
if iputils.CompareBytes(item1.IPFrom, item2.IPFrom) == 0 {
|
||||
return iputils.CompareBytes(item1.IPTo, item2.IPTo) < 0
|
||||
}
|
||||
return iputils.CompareBytes(item1.IPFrom, item2.IPFrom) < 0
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 不加锁的情况下查找Item
|
||||
func (this *IPList) lookupIP(ip uint64) *IPItem {
|
||||
if len(this.sortedItems) == 0 {
|
||||
func (this *IPList) lookupIP(ipBytes []byte) *IPItem {
|
||||
{
|
||||
item, ok := this.ipMap[ToHex(ipBytes)]
|
||||
if ok && (item.ExpiredAt == 0 || item.ExpiredAt > fasttime.Now().Unix()) {
|
||||
return item
|
||||
}
|
||||
}
|
||||
|
||||
if len(this.sortedRangeItems) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var count = len(this.sortedItems)
|
||||
var count = len(this.sortedRangeItems)
|
||||
var resultIndex = -1
|
||||
sort.Search(count, func(i int) bool {
|
||||
var item = this.sortedItems[i]
|
||||
if item.IPFrom < ip {
|
||||
if item.IPTo >= ip {
|
||||
var item = this.sortedRangeItems[i]
|
||||
var cmp = iputils.CompareBytes(item.IPFrom, ipBytes)
|
||||
if cmp < 0 {
|
||||
if iputils.CompareBytes(item.IPTo, ipBytes) >= 0 {
|
||||
resultIndex = i
|
||||
}
|
||||
return false
|
||||
} else if item.IPFrom == ip {
|
||||
} else if cmp == 0 {
|
||||
resultIndex = i
|
||||
return false
|
||||
}
|
||||
@@ -213,36 +297,54 @@ func (this *IPList) lookupIP(ip uint64) *IPItem {
|
||||
return nil
|
||||
}
|
||||
|
||||
return this.sortedItems[resultIndex]
|
||||
var item = this.sortedRangeItems[resultIndex]
|
||||
if item.ExpiredAt == 0 || item.ExpiredAt > fasttime.Now().Unix() {
|
||||
return item
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 在不加锁的情况下删除某个Item
|
||||
// 将会被别的方法引用,切记不能加锁
|
||||
func (this *IPList) deleteItem(itemId uint64) {
|
||||
_, ok := this.itemsMap[itemId]
|
||||
if !ok {
|
||||
// 从buffer中删除
|
||||
delete(this.bufferItemsMap, itemId)
|
||||
|
||||
// 从all items中删除
|
||||
_, ok := this.allItemsMap[itemId]
|
||||
if ok {
|
||||
delete(this.allItemsMap, itemId)
|
||||
}
|
||||
|
||||
// 检查是否存在
|
||||
oldItem, existsOld := this.itemsMap[itemId]
|
||||
if !existsOld {
|
||||
return
|
||||
}
|
||||
|
||||
// 从ipMap中删除
|
||||
if IsZero(oldItem.IPTo) {
|
||||
var ipHex = ToHex(oldItem.IPFrom)
|
||||
ipItem, ok := this.ipMap[ipHex]
|
||||
if ok && ipItem.Id == itemId {
|
||||
delete(this.ipMap, ipHex)
|
||||
}
|
||||
}
|
||||
|
||||
delete(this.itemsMap, itemId)
|
||||
|
||||
// 是否为All Item
|
||||
_, ok = this.allItemsMap[itemId]
|
||||
if ok {
|
||||
delete(this.allItemsMap, itemId)
|
||||
return
|
||||
}
|
||||
|
||||
// 删除排序中的Item
|
||||
var index = -1
|
||||
for itemIndex, item := range this.sortedItems {
|
||||
if item.Id == itemId {
|
||||
index = itemIndex
|
||||
break
|
||||
if !IsZero(oldItem.IPTo) {
|
||||
var index = -1
|
||||
for itemIndex, item := range this.sortedRangeItems {
|
||||
if item.Id == itemId {
|
||||
index = itemIndex
|
||||
break
|
||||
}
|
||||
}
|
||||
if index >= 0 {
|
||||
copy(this.sortedRangeItems[index:], this.sortedRangeItems[index+1:])
|
||||
this.sortedRangeItems = this.sortedRangeItems[:len(this.sortedRangeItems)-1]
|
||||
}
|
||||
}
|
||||
if index >= 0 {
|
||||
copy(this.sortedItems[index:], this.sortedItems[index+1:])
|
||||
this.sortedItems = this.sortedItems[:len(this.sortedItems)-1]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,305 +1,13 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package iplibrary
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/dbs"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
import "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
|
||||
type IPListDB struct {
|
||||
db *dbs.DB
|
||||
|
||||
itemTableName string
|
||||
versionTableName string
|
||||
|
||||
deleteExpiredItemsStmt *dbs.Stmt
|
||||
deleteItemStmt *dbs.Stmt
|
||||
insertItemStmt *dbs.Stmt
|
||||
selectItemsStmt *dbs.Stmt
|
||||
selectMaxItemVersionStmt *dbs.Stmt
|
||||
|
||||
selectVersionStmt *dbs.Stmt
|
||||
updateVersionStmt *dbs.Stmt
|
||||
|
||||
cleanTicker *time.Ticker
|
||||
|
||||
dir string
|
||||
|
||||
isClosed bool
|
||||
}
|
||||
|
||||
func NewIPListDB() (*IPListDB, error) {
|
||||
var db = &IPListDB{
|
||||
itemTableName: "ipItems",
|
||||
versionTableName: "versions",
|
||||
dir: filepath.Clean(Tea.Root + "/data"),
|
||||
cleanTicker: time.NewTicker(24 * time.Hour),
|
||||
}
|
||||
err := db.init()
|
||||
return db, err
|
||||
}
|
||||
|
||||
func (this *IPListDB) init() error {
|
||||
// 检查目录是否存在
|
||||
_, err := os.Stat(this.dir)
|
||||
if err != nil {
|
||||
err = os.MkdirAll(this.dir, 0777)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
remotelogs.Println("IP_LIST_DB", "create data dir '"+this.dir+"'")
|
||||
}
|
||||
|
||||
var path = this.dir + "/ip_list.db"
|
||||
|
||||
db, err := dbs.OpenWriter("file:" + path + "?cache=shared&mode=rwc&_journal_mode=WAL&_sync=OFF&_locking_mode=EXCLUSIVE")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
db.SetMaxOpenConns(1)
|
||||
|
||||
//_, err = db.Exec("VACUUM")
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
|
||||
this.db = db
|
||||
|
||||
// 恢复数据库
|
||||
var recoverEnv, _ = os.LookupEnv("EdgeRecover")
|
||||
if len(recoverEnv) > 0 {
|
||||
for _, indexName := range []string{"ip_list_itemId", "ip_list_expiredAt"} {
|
||||
_, _ = db.Exec(`REINDEX "` + indexName + `"`)
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化数据库
|
||||
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS "` + this.itemTableName + `" (
|
||||
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"listId" integer DEFAULT 0,
|
||||
"listType" varchar(32),
|
||||
"isGlobal" integer(1) DEFAULT 0,
|
||||
"type" varchar(16),
|
||||
"itemId" integer DEFAULT 0,
|
||||
"ipFrom" varchar(64) DEFAULT 0,
|
||||
"ipTo" varchar(64) DEFAULT 0,
|
||||
"expiredAt" integer DEFAULT 0,
|
||||
"eventLevel" varchar(32),
|
||||
"isDeleted" integer(1) DEFAULT 0,
|
||||
"version" integer DEFAULT 0,
|
||||
"nodeId" integer DEFAULT 0,
|
||||
"serverId" integer DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "ip_list_itemId"
|
||||
ON "` + this.itemTableName + `" (
|
||||
"itemId" ASC
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "ip_list_expiredAt"
|
||||
ON "` + this.itemTableName + `" (
|
||||
"expiredAt" ASC
|
||||
);
|
||||
`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS "` + this.versionTableName + `" (
|
||||
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"version" integer DEFAULT 0
|
||||
);
|
||||
`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 初始化SQL语句
|
||||
this.deleteExpiredItemsStmt, err = this.db.Prepare(`DELETE FROM "` + this.itemTableName + `" WHERE "expiredAt">0 AND "expiredAt"<?`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
this.deleteItemStmt, err = this.db.Prepare(`DELETE FROM "` + this.itemTableName + `" WHERE "itemId"=?`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
this.insertItemStmt, err = this.db.Prepare(`INSERT INTO "` + this.itemTableName + `" ("listId", "listType", "isGlobal", "type", "itemId", "ipFrom", "ipTo", "expiredAt", "eventLevel", "isDeleted", "version", "nodeId", "serverId") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
this.selectItemsStmt, err = this.db.Prepare(`SELECT "listId", "listType", "isGlobal", "type", "itemId", "ipFrom", "ipTo", "expiredAt", "eventLevel", "isDeleted", "version", "nodeId", "serverId" FROM "` + this.itemTableName + `" WHERE isDeleted=0 ORDER BY "version" ASC, "itemId" ASC LIMIT ?, ?`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
this.selectMaxItemVersionStmt, err = this.db.Prepare(`SELECT "version" FROM "` + this.itemTableName + `" ORDER BY "id" DESC LIMIT 1`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
this.selectVersionStmt, err = this.db.Prepare(`SELECT "version" FROM "` + this.versionTableName + `" LIMIT 1`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
this.updateVersionStmt, err = this.db.Prepare(`REPLACE INTO "` + this.versionTableName + `" ("id", "version") VALUES (1, ?)`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
this.db = db
|
||||
|
||||
goman.New(func() {
|
||||
events.OnClose(func() {
|
||||
_ = this.Close()
|
||||
this.cleanTicker.Stop()
|
||||
})
|
||||
|
||||
for range this.cleanTicker.C {
|
||||
err := this.DeleteExpiredItems()
|
||||
if err != nil {
|
||||
remotelogs.Error("IP_LIST_DB", "clean expired items failed: "+err.Error())
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteExpiredItems 删除过期的条目
|
||||
func (this *IPListDB) DeleteExpiredItems() error {
|
||||
if this.isClosed {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := this.deleteExpiredItemsStmt.Exec(time.Now().Unix() - 7*86400)
|
||||
return err
|
||||
}
|
||||
|
||||
func (this *IPListDB) AddItem(item *pb.IPItem) error {
|
||||
if this.isClosed {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := this.deleteItemStmt.Exec(item.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 如果是删除,则不再创建新记录
|
||||
if item.IsDeleted {
|
||||
return this.UpdateMaxVersion(item.Version)
|
||||
}
|
||||
|
||||
_, err = this.insertItemStmt.Exec(item.ListId, item.ListType, item.IsGlobal, item.Type, item.Id, item.IpFrom, item.IpTo, item.ExpiredAt, item.EventLevel, item.IsDeleted, item.Version, item.NodeId, item.ServerId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return this.UpdateMaxVersion(item.Version)
|
||||
}
|
||||
|
||||
func (this *IPListDB) ReadItems(offset int64, size int64) (items []*pb.IPItem, err error) {
|
||||
if this.isClosed {
|
||||
return
|
||||
}
|
||||
|
||||
rows, err := this.selectItemsStmt.Query(offset, size)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
_ = rows.Close()
|
||||
}()
|
||||
|
||||
for rows.Next() {
|
||||
// "listId", "listType", "isGlobal", "type", "itemId", "ipFrom", "ipTo", "expiredAt", "eventLevel", "isDeleted", "version", "nodeId", "serverId"
|
||||
var pbItem = &pb.IPItem{}
|
||||
err = rows.Scan(&pbItem.ListId, &pbItem.ListType, &pbItem.IsGlobal, &pbItem.Type, &pbItem.Id, &pbItem.IpFrom, &pbItem.IpTo, &pbItem.ExpiredAt, &pbItem.EventLevel, &pbItem.IsDeleted, &pbItem.Version, &pbItem.NodeId, &pbItem.ServerId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, pbItem)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ReadMaxVersion 读取当前最大版本号
|
||||
func (this *IPListDB) ReadMaxVersion() int64 {
|
||||
if this.isClosed {
|
||||
return 0
|
||||
}
|
||||
|
||||
// from version table
|
||||
{
|
||||
var row = this.selectVersionStmt.QueryRow()
|
||||
if row == nil {
|
||||
return 0
|
||||
}
|
||||
var version int64
|
||||
err := row.Scan(&version)
|
||||
if err == nil {
|
||||
return version
|
||||
}
|
||||
}
|
||||
|
||||
// from items table
|
||||
{
|
||||
var row = this.selectMaxItemVersionStmt.QueryRow()
|
||||
if row == nil {
|
||||
return 0
|
||||
}
|
||||
var version int64
|
||||
err := row.Scan(&version)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return version
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateMaxVersion 修改版本号
|
||||
func (this *IPListDB) UpdateMaxVersion(version int64) error {
|
||||
if this.isClosed {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := this.updateVersionStmt.Exec(version)
|
||||
return err
|
||||
}
|
||||
|
||||
func (this *IPListDB) Close() error {
|
||||
this.isClosed = true
|
||||
|
||||
if this.db != nil {
|
||||
for _, stmt := range []*dbs.Stmt{
|
||||
this.deleteExpiredItemsStmt,
|
||||
this.deleteItemStmt,
|
||||
this.insertItemStmt,
|
||||
this.selectItemsStmt,
|
||||
this.selectMaxItemVersionStmt, // ipItems table
|
||||
|
||||
this.selectVersionStmt, // versions table
|
||||
this.updateVersionStmt,
|
||||
} {
|
||||
if stmt != nil {
|
||||
_ = stmt.Close()
|
||||
}
|
||||
}
|
||||
|
||||
return this.db.Close()
|
||||
}
|
||||
return nil
|
||||
type IPListDB interface {
|
||||
Name() string
|
||||
DeleteExpiredItems() error
|
||||
ReadMaxVersion() (int64, error)
|
||||
ReadItems(offset int64, size int64) (items []*pb.IPItem, goNext bool, err error)
|
||||
AddItem(item *pb.IPItem) error
|
||||
}
|
||||
|
||||
233
internal/iplibrary/ip_list_kv.go
Normal file
233
internal/iplibrary/ip_list_kv.go
Normal file
@@ -0,0 +1,233 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package iplibrary
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/idles"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/kvstore"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type KVIPList struct {
|
||||
ipTable *kvstore.Table[*pb.IPItem]
|
||||
versionsTable *kvstore.Table[int64]
|
||||
|
||||
encoder *IPItemEncoder[*pb.IPItem]
|
||||
|
||||
cleanTicker *time.Ticker
|
||||
|
||||
isClosed bool
|
||||
|
||||
offsetItemKey string
|
||||
}
|
||||
|
||||
func NewKVIPList() (*KVIPList, error) {
|
||||
var db = &KVIPList{
|
||||
cleanTicker: time.NewTicker(24 * time.Hour),
|
||||
encoder: &IPItemEncoder[*pb.IPItem]{},
|
||||
}
|
||||
err := db.init()
|
||||
return db, err
|
||||
}
|
||||
|
||||
func (this *KVIPList) init() error {
|
||||
store, storeErr := kvstore.DefaultStore()
|
||||
if storeErr != nil {
|
||||
return storeErr
|
||||
}
|
||||
db, dbErr := store.NewDB("ip_list")
|
||||
if dbErr != nil {
|
||||
return dbErr
|
||||
}
|
||||
|
||||
{
|
||||
table, err := kvstore.NewTable[*pb.IPItem]("ip_items", this.encoder)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
this.ipTable = table
|
||||
|
||||
err = table.AddFields("expiresAt")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
db.AddTable(table)
|
||||
}
|
||||
|
||||
{
|
||||
table, err := kvstore.NewTable[int64]("versions", kvstore.NewIntValueEncoder[int64]())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
this.versionsTable = table
|
||||
db.AddTable(table)
|
||||
}
|
||||
|
||||
goman.New(func() {
|
||||
events.OnClose(func() {
|
||||
_ = this.Close()
|
||||
this.cleanTicker.Stop()
|
||||
})
|
||||
|
||||
idles.RunTicker(this.cleanTicker, func() {
|
||||
if this.isClosed {
|
||||
return
|
||||
}
|
||||
deleteErr := this.DeleteExpiredItems()
|
||||
if deleteErr != nil {
|
||||
remotelogs.Error("IP_LIST_DB", "clean expired items failed: "+deleteErr.Error())
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Name 数据库名称代号
|
||||
func (this *KVIPList) Name() string {
|
||||
return "kvstore"
|
||||
}
|
||||
|
||||
// DeleteExpiredItems 删除过期的条目
|
||||
func (this *KVIPList) DeleteExpiredItems() error {
|
||||
if this.isClosed {
|
||||
return nil
|
||||
}
|
||||
|
||||
for {
|
||||
var found bool
|
||||
var currentTime = fasttime.Now().Unix()
|
||||
err := this.ipTable.
|
||||
Query().
|
||||
FieldAsc("expiresAt").
|
||||
ForUpdate().
|
||||
Limit(1000).
|
||||
FindAll(func(tx *kvstore.Tx[*pb.IPItem], item kvstore.Item[*pb.IPItem]) (goNext bool, err error) {
|
||||
if !item.Value.IsDeleted && item.Value.ExpiredAt == 0 { // never expires
|
||||
return kvstore.Skip()
|
||||
}
|
||||
if item.Value.ExpiredAt < currentTime-7*86400 /** keep for 7 days **/ {
|
||||
err = tx.Delete(item.Key)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
found = true
|
||||
return true, nil
|
||||
}
|
||||
|
||||
found = false
|
||||
return false, nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !found {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *KVIPList) AddItem(item *pb.IPItem) error {
|
||||
if this.isClosed {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 先删除
|
||||
var key = this.encoder.EncodeKey(item)
|
||||
err := this.ipTable.Delete(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 如果是删除,则不再创建新记录
|
||||
if item.IsDeleted {
|
||||
return this.UpdateMaxVersion(item.Version)
|
||||
}
|
||||
|
||||
err = this.ipTable.Set(key, item)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return this.UpdateMaxVersion(item.Version)
|
||||
}
|
||||
|
||||
func (this *KVIPList) ReadItems(offset int64, size int64) (items []*pb.IPItem, goNextLoop bool, err error) {
|
||||
if this.isClosed {
|
||||
return
|
||||
}
|
||||
|
||||
err = this.ipTable.
|
||||
Query().
|
||||
Offset(this.offsetItemKey).
|
||||
Limit(int(size)).
|
||||
FindAll(func(tx *kvstore.Tx[*pb.IPItem], item kvstore.Item[*pb.IPItem]) (goNext bool, err error) {
|
||||
this.offsetItemKey = item.Key
|
||||
goNextLoop = true
|
||||
|
||||
if !item.Value.IsDeleted {
|
||||
items = append(items, item.Value)
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// ReadMaxVersion 读取当前最大版本号
|
||||
func (this *KVIPList) ReadMaxVersion() (int64, error) {
|
||||
if this.isClosed {
|
||||
return 0, errors.New("database has been closed")
|
||||
}
|
||||
|
||||
version, err := this.versionsTable.Get("version")
|
||||
if err != nil {
|
||||
if kvstore.IsNotFound(err) {
|
||||
return 0, nil
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
return version, nil
|
||||
}
|
||||
|
||||
// UpdateMaxVersion 修改版本号
|
||||
func (this *KVIPList) UpdateMaxVersion(version int64) error {
|
||||
if this.isClosed {
|
||||
return nil
|
||||
}
|
||||
|
||||
return this.versionsTable.Set("version", version)
|
||||
}
|
||||
|
||||
func (this *KVIPList) TestInspect(t *testing.T) error {
|
||||
return this.ipTable.
|
||||
Query().
|
||||
FindAll(func(tx *kvstore.Tx[*pb.IPItem], item kvstore.Item[*pb.IPItem]) (goNext bool, err error) {
|
||||
if len(item.Key) != 8 {
|
||||
return false, errors.New("invalid key '" + item.Key + "'")
|
||||
}
|
||||
|
||||
t.Log(binary.BigEndian.Uint64([]byte(item.Key)), "=>", item.Value)
|
||||
return true, nil
|
||||
})
|
||||
}
|
||||
|
||||
// Flush to disk
|
||||
func (this *KVIPList) Flush() error {
|
||||
return this.ipTable.DB().Store().Flush()
|
||||
}
|
||||
|
||||
func (this *KVIPList) Close() error {
|
||||
this.isClosed = true
|
||||
return nil
|
||||
}
|
||||
55
internal/iplibrary/ip_list_kv_objects.go
Normal file
55
internal/iplibrary/ip_list_kv_objects.go
Normal file
@@ -0,0 +1,55 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package iplibrary
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"math"
|
||||
)
|
||||
|
||||
type IPItemEncoder[T interface{ *pb.IPItem }] struct {
|
||||
}
|
||||
|
||||
func NewIPItemEncoder[T interface{ *pb.IPItem }]() *IPItemEncoder[T] {
|
||||
return &IPItemEncoder[T]{}
|
||||
}
|
||||
|
||||
func (this *IPItemEncoder[T]) Encode(value T) ([]byte, error) {
|
||||
return proto.Marshal(any(value).(*pb.IPItem))
|
||||
}
|
||||
|
||||
func (this *IPItemEncoder[T]) EncodeField(value T, fieldName string) ([]byte, error) {
|
||||
switch fieldName {
|
||||
case "expiresAt":
|
||||
var expiresAt = any(value).(*pb.IPItem).ExpiredAt
|
||||
if expiresAt < 0 || expiresAt > int64(math.MaxUint32) {
|
||||
expiresAt = 0
|
||||
}
|
||||
var b = make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(b, uint32(expiresAt))
|
||||
return b, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("field '" + fieldName + "' not found")
|
||||
}
|
||||
|
||||
func (this *IPItemEncoder[T]) Decode(valueBytes []byte) (value T, err error) {
|
||||
var item = &pb.IPItem{}
|
||||
err = proto.Unmarshal(valueBytes, item)
|
||||
value = item
|
||||
return
|
||||
}
|
||||
|
||||
// EncodeKey generate key for ip item
|
||||
func (this *IPItemEncoder[T]) EncodeKey(item *pb.IPItem) string {
|
||||
var b = make([]byte, 8)
|
||||
if item.Id < 0 {
|
||||
item.Id = 0
|
||||
}
|
||||
|
||||
binary.BigEndian.PutUint64(b, uint64(item.Id))
|
||||
return string(b)
|
||||
}
|
||||
221
internal/iplibrary/ip_list_kv_test.go
Normal file
221
internal/iplibrary/ip_list_kv_test.go
Normal file
@@ -0,0 +1,221 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package iplibrary_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/iplibrary"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestKVIPList_AddItem(t *testing.T) {
|
||||
kv, err := iplibrary.NewKVIPList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = kv.Flush()
|
||||
}()
|
||||
|
||||
{
|
||||
err = kv.AddItem(&pb.IPItem{
|
||||
Id: 1,
|
||||
IpFrom: "192.168.1.101",
|
||||
IpTo: "",
|
||||
Version: 1,
|
||||
ExpiredAt: fasttime.NewFastTime().Unix() + 60,
|
||||
ListId: 1,
|
||||
IsDeleted: false,
|
||||
ListType: "white",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
err = kv.AddItem(&pb.IPItem{
|
||||
Id: 2,
|
||||
IpFrom: "192.168.1.102",
|
||||
IpTo: "",
|
||||
Version: 2,
|
||||
ExpiredAt: fasttime.NewFastTime().Unix() + 60,
|
||||
ListId: 1,
|
||||
IsDeleted: false,
|
||||
ListType: "white",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
err = kv.AddItem(&pb.IPItem{
|
||||
Id: 3,
|
||||
IpFrom: "192.168.1.103",
|
||||
IpTo: "",
|
||||
Version: 3,
|
||||
ExpiredAt: fasttime.NewFastTime().Unix() + 60,
|
||||
ListId: 1,
|
||||
IsDeleted: false,
|
||||
ListType: "white",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKVIPList_AddItems_Many(t *testing.T) {
|
||||
kv, err := iplibrary.NewKVIPList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = kv.Flush()
|
||||
}()
|
||||
|
||||
var count = 2
|
||||
var from = 1
|
||||
if testutils.IsSingleTesting() {
|
||||
count = 2_000_000
|
||||
}
|
||||
|
||||
var before = time.Now()
|
||||
defer func() {
|
||||
t.Logf("cost: %.2f s", time.Since(before).Seconds())
|
||||
}()
|
||||
|
||||
for i := from; i <= from+count; i++ {
|
||||
err = kv.AddItem(&pb.IPItem{
|
||||
Id: int64(i),
|
||||
IpFrom: testutils.RandIP(),
|
||||
IpTo: "",
|
||||
Version: int64(i),
|
||||
ExpiredAt: fasttime.NewFastTime().Unix() + 86400,
|
||||
ListId: 1,
|
||||
IsDeleted: false,
|
||||
ListType: "white",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKVIPList_DeleteExpiredItems(t *testing.T) {
|
||||
kv, err := iplibrary.NewKVIPList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = kv.Flush()
|
||||
}()
|
||||
|
||||
err = kv.DeleteExpiredItems()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKVIPList_UpdateMaxVersion(t *testing.T) {
|
||||
kv, err := iplibrary.NewKVIPList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = kv.Flush()
|
||||
}()
|
||||
|
||||
err = kv.UpdateMaxVersion(101)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
maxVersion, err := kv.ReadMaxVersion()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Log("version:", maxVersion)
|
||||
}
|
||||
|
||||
func TestKVIPList_ReadMaxVersion(t *testing.T) {
|
||||
kv, err := iplibrary.NewKVIPList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
maxVersion, err := kv.ReadMaxVersion()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Log("version:", maxVersion)
|
||||
}
|
||||
|
||||
func TestKVIPList_ReadItems(t *testing.T) {
|
||||
kv, err := iplibrary.NewKVIPList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for {
|
||||
items, goNext, readErr := kv.ReadItems(0, 2)
|
||||
if readErr != nil {
|
||||
t.Fatal(readErr)
|
||||
}
|
||||
t.Log("====")
|
||||
for _, item := range items {
|
||||
t.Log(item.Id)
|
||||
}
|
||||
|
||||
if !goNext {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKVIPList_CountItems(t *testing.T) {
|
||||
kv, err := iplibrary.NewKVIPList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var count int
|
||||
var m = map[int64]zero.Zero{}
|
||||
for {
|
||||
items, goNext, readErr := kv.ReadItems(0, 1000)
|
||||
if readErr != nil {
|
||||
t.Fatal(readErr)
|
||||
}
|
||||
for _, item := range items {
|
||||
count++
|
||||
m[item.Id] = zero.Zero{}
|
||||
}
|
||||
|
||||
if !goNext {
|
||||
break
|
||||
}
|
||||
}
|
||||
t.Log("count:", count, "len:", len(m))
|
||||
}
|
||||
|
||||
func TestKVIPList_Inspect(t *testing.T) {
|
||||
kv, err := iplibrary.NewKVIPList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = kv.TestInspect(t)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
313
internal/iplibrary/ip_list_sqlite.go
Normal file
313
internal/iplibrary/ip_list_sqlite.go
Normal file
@@ -0,0 +1,313 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package iplibrary
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/dbs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/idles"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
type SQLiteIPList struct {
|
||||
db *dbs.DB
|
||||
|
||||
itemTableName string
|
||||
versionTableName string
|
||||
|
||||
deleteExpiredItemsStmt *dbs.Stmt
|
||||
deleteItemStmt *dbs.Stmt
|
||||
insertItemStmt *dbs.Stmt
|
||||
selectItemsStmt *dbs.Stmt
|
||||
selectMaxItemVersionStmt *dbs.Stmt
|
||||
|
||||
selectVersionStmt *dbs.Stmt
|
||||
updateVersionStmt *dbs.Stmt
|
||||
|
||||
cleanTicker *time.Ticker
|
||||
|
||||
dir string
|
||||
|
||||
isClosed bool
|
||||
}
|
||||
|
||||
func NewSQLiteIPList() (*SQLiteIPList, error) {
|
||||
var db = &SQLiteIPList{
|
||||
itemTableName: "ipItems",
|
||||
versionTableName: "versions",
|
||||
dir: filepath.Clean(Tea.Root + "/data"),
|
||||
cleanTicker: time.NewTicker(24 * time.Hour),
|
||||
}
|
||||
err := db.init()
|
||||
return db, err
|
||||
}
|
||||
|
||||
func (this *SQLiteIPList) init() error {
|
||||
// 检查目录是否存在
|
||||
_, err := os.Stat(this.dir)
|
||||
if err != nil {
|
||||
err = os.MkdirAll(this.dir, 0777)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
remotelogs.Println("IP_LIST_DB", "create data dir '"+this.dir+"'")
|
||||
}
|
||||
|
||||
var path = this.dir + "/ip_list.db"
|
||||
|
||||
db, err := dbs.OpenWriter("file:" + path + "?cache=shared&mode=rwc&_journal_mode=WAL&_sync=" + dbs.SyncMode + "&_locking_mode=EXCLUSIVE")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
db.SetMaxOpenConns(1)
|
||||
|
||||
//_, err = db.Exec("VACUUM")
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
|
||||
this.db = db
|
||||
|
||||
// 恢复数据库
|
||||
var recoverEnv, _ = os.LookupEnv("EdgeRecover")
|
||||
if len(recoverEnv) > 0 {
|
||||
for _, indexName := range []string{"ip_list_itemId", "ip_list_expiredAt"} {
|
||||
_, _ = db.Exec(`REINDEX "` + indexName + `"`)
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化数据库
|
||||
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS "` + this.itemTableName + `" (
|
||||
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"listId" integer DEFAULT 0,
|
||||
"listType" varchar(32),
|
||||
"isGlobal" integer(1) DEFAULT 0,
|
||||
"type" varchar(16),
|
||||
"itemId" integer DEFAULT 0,
|
||||
"ipFrom" varchar(64) DEFAULT 0,
|
||||
"ipTo" varchar(64) DEFAULT 0,
|
||||
"expiredAt" integer DEFAULT 0,
|
||||
"eventLevel" varchar(32),
|
||||
"isDeleted" integer(1) DEFAULT 0,
|
||||
"version" integer DEFAULT 0,
|
||||
"nodeId" integer DEFAULT 0,
|
||||
"serverId" integer DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "ip_list_itemId"
|
||||
ON "` + this.itemTableName + `" (
|
||||
"itemId" ASC
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "ip_list_expiredAt"
|
||||
ON "` + this.itemTableName + `" (
|
||||
"expiredAt" ASC
|
||||
);
|
||||
`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS "` + this.versionTableName + `" (
|
||||
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"version" integer DEFAULT 0
|
||||
);
|
||||
`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 初始化SQL语句
|
||||
this.deleteExpiredItemsStmt, err = this.db.Prepare(`DELETE FROM "` + this.itemTableName + `" WHERE "expiredAt">0 AND "expiredAt"<?`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
this.deleteItemStmt, err = this.db.Prepare(`DELETE FROM "` + this.itemTableName + `" WHERE "itemId"=?`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
this.insertItemStmt, err = this.db.Prepare(`INSERT INTO "` + this.itemTableName + `" ("listId", "listType", "isGlobal", "type", "itemId", "ipFrom", "ipTo", "expiredAt", "eventLevel", "isDeleted", "version", "nodeId", "serverId") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
this.selectItemsStmt, err = this.db.Prepare(`SELECT "listId", "listType", "isGlobal", "type", "itemId", "ipFrom", "ipTo", "expiredAt", "eventLevel", "isDeleted", "version", "nodeId", "serverId" FROM "` + this.itemTableName + `" WHERE isDeleted=0 ORDER BY "version" ASC, "itemId" ASC LIMIT ?, ?`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
this.selectMaxItemVersionStmt, err = this.db.Prepare(`SELECT "version" FROM "` + this.itemTableName + `" ORDER BY "id" DESC LIMIT 1`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
this.selectVersionStmt, err = this.db.Prepare(`SELECT "version" FROM "` + this.versionTableName + `" LIMIT 1`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
this.updateVersionStmt, err = this.db.Prepare(`REPLACE INTO "` + this.versionTableName + `" ("id", "version") VALUES (1, ?)`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
this.db = db
|
||||
|
||||
goman.New(func() {
|
||||
events.OnClose(func() {
|
||||
_ = this.Close()
|
||||
this.cleanTicker.Stop()
|
||||
})
|
||||
|
||||
idles.RunTicker(this.cleanTicker, func() {
|
||||
deleteErr := this.DeleteExpiredItems()
|
||||
if deleteErr != nil {
|
||||
remotelogs.Error("IP_LIST_DB", "clean expired items failed: "+deleteErr.Error())
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Name 数据库名称代号
|
||||
func (this *SQLiteIPList) Name() string {
|
||||
return "sqlite"
|
||||
}
|
||||
|
||||
// DeleteExpiredItems 删除过期的条目
|
||||
func (this *SQLiteIPList) DeleteExpiredItems() error {
|
||||
if this.isClosed {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := this.deleteExpiredItemsStmt.Exec(time.Now().Unix() - 7*86400)
|
||||
return err
|
||||
}
|
||||
|
||||
func (this *SQLiteIPList) AddItem(item *pb.IPItem) error {
|
||||
if this.isClosed {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := this.deleteItemStmt.Exec(item.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 如果是删除,则不再创建新记录
|
||||
if item.IsDeleted {
|
||||
return this.UpdateMaxVersion(item.Version)
|
||||
}
|
||||
|
||||
_, err = this.insertItemStmt.Exec(item.ListId, item.ListType, item.IsGlobal, item.Type, item.Id, item.IpFrom, item.IpTo, item.ExpiredAt, item.EventLevel, item.IsDeleted, item.Version, item.NodeId, item.ServerId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return this.UpdateMaxVersion(item.Version)
|
||||
}
|
||||
|
||||
func (this *SQLiteIPList) ReadItems(offset int64, size int64) (items []*pb.IPItem, goNext bool, err error) {
|
||||
if this.isClosed {
|
||||
return
|
||||
}
|
||||
|
||||
rows, err := this.selectItemsStmt.Query(offset, size)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
defer func() {
|
||||
_ = rows.Close()
|
||||
}()
|
||||
|
||||
for rows.Next() {
|
||||
// "listId", "listType", "isGlobal", "type", "itemId", "ipFrom", "ipTo", "expiredAt", "eventLevel", "isDeleted", "version", "nodeId", "serverId"
|
||||
var pbItem = &pb.IPItem{}
|
||||
err = rows.Scan(&pbItem.ListId, &pbItem.ListType, &pbItem.IsGlobal, &pbItem.Type, &pbItem.Id, &pbItem.IpFrom, &pbItem.IpTo, &pbItem.ExpiredAt, &pbItem.EventLevel, &pbItem.IsDeleted, &pbItem.Version, &pbItem.NodeId, &pbItem.ServerId)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
items = append(items, pbItem)
|
||||
}
|
||||
|
||||
goNext = int64(len(items)) == size
|
||||
return
|
||||
}
|
||||
|
||||
// ReadMaxVersion 读取当前最大版本号
|
||||
func (this *SQLiteIPList) ReadMaxVersion() (int64, error) {
|
||||
if this.isClosed {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// from version table
|
||||
{
|
||||
var row = this.selectVersionStmt.QueryRow()
|
||||
if row == nil {
|
||||
return 0, nil
|
||||
}
|
||||
var version int64
|
||||
err := row.Scan(&version)
|
||||
if err == nil {
|
||||
return version, nil
|
||||
}
|
||||
}
|
||||
|
||||
// from items table
|
||||
{
|
||||
var row = this.selectMaxItemVersionStmt.QueryRow()
|
||||
if row == nil {
|
||||
return 0, nil
|
||||
}
|
||||
var version int64
|
||||
err := row.Scan(&version)
|
||||
if err != nil {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
return version, nil
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateMaxVersion 修改版本号
|
||||
func (this *SQLiteIPList) UpdateMaxVersion(version int64) error {
|
||||
if this.isClosed {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := this.updateVersionStmt.Exec(version)
|
||||
return err
|
||||
}
|
||||
|
||||
func (this *SQLiteIPList) Close() error {
|
||||
this.isClosed = true
|
||||
|
||||
if this.db != nil {
|
||||
for _, stmt := range []*dbs.Stmt{
|
||||
this.deleteExpiredItemsStmt,
|
||||
this.deleteItemStmt,
|
||||
this.insertItemStmt,
|
||||
this.selectItemsStmt,
|
||||
this.selectMaxItemVersionStmt, // ipItems table
|
||||
|
||||
this.selectVersionStmt, // versions table
|
||||
this.updateVersionStmt,
|
||||
} {
|
||||
if stmt != nil {
|
||||
_ = stmt.Close()
|
||||
}
|
||||
}
|
||||
|
||||
return this.db.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -11,21 +11,24 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestIPListDB_AddItem(t *testing.T) {
|
||||
db, err := iplibrary.NewIPListDB()
|
||||
func TestSQLiteIPList_AddItem(t *testing.T) {
|
||||
db, err := iplibrary.NewSQLiteIPList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
_ = db.Close()
|
||||
}()
|
||||
|
||||
err = db.AddItem(&pb.IPItem{
|
||||
Id: 1,
|
||||
IpFrom: "192.168.1.101",
|
||||
IpTo: "",
|
||||
Version: 1024,
|
||||
ExpiredAt: time.Now().Unix(),
|
||||
ExpiredAt: time.Now().Unix() + 3600,
|
||||
Reason: "",
|
||||
ListId: 2,
|
||||
IsDeleted: true,
|
||||
IsDeleted: false,
|
||||
Type: "ipv4",
|
||||
EventLevel: "error",
|
||||
ListType: "black",
|
||||
@@ -55,36 +58,47 @@ func TestIPListDB_AddItem(t *testing.T) {
|
||||
t.Log("ok")
|
||||
}
|
||||
|
||||
func TestIPListDB_ReadItems(t *testing.T) {
|
||||
db, err := iplibrary.NewIPListDB()
|
||||
func TestSQLiteIPList_ReadItems(t *testing.T) {
|
||||
db, err := iplibrary.NewSQLiteIPList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
_ = db.Close()
|
||||
}()
|
||||
|
||||
defer func() {
|
||||
_ = db.Close()
|
||||
}()
|
||||
|
||||
items, err := db.ReadItems(0, 2)
|
||||
items, goNext, err := db.ReadItems(0, 2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("goNext:", goNext)
|
||||
logs.PrintAsJSON(items, t)
|
||||
}
|
||||
|
||||
func TestIPListDB_ReadMaxVersion(t *testing.T) {
|
||||
db, err := iplibrary.NewIPListDB()
|
||||
func TestSQLiteIPList_ReadMaxVersion(t *testing.T) {
|
||||
db, err := iplibrary.NewSQLiteIPList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
_ = db.Close()
|
||||
}()
|
||||
t.Log(db.ReadMaxVersion())
|
||||
}
|
||||
|
||||
func TestIPListDB_UpdateMaxVersion(t *testing.T) {
|
||||
db, err := iplibrary.NewIPListDB()
|
||||
func TestSQLiteIPList_UpdateMaxVersion(t *testing.T) {
|
||||
db, err := iplibrary.NewSQLiteIPList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
_ = db.Close()
|
||||
}()
|
||||
|
||||
err = db.UpdateMaxVersion(1027)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -1,10 +1,15 @@
|
||||
package iplibrary
|
||||
package iplibrary_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/iputils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/iplibrary"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
"github.com/iwind/TeaGo/assert"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"math/rand"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
@@ -14,216 +19,278 @@ import (
|
||||
)
|
||||
|
||||
func TestIPList_Add_Empty(t *testing.T) {
|
||||
ipList := NewIPList()
|
||||
ipList.Add(&IPItem{
|
||||
var ipList = iplibrary.NewIPList()
|
||||
ipList.Add(&iplibrary.IPItem{
|
||||
Id: 1,
|
||||
})
|
||||
logs.PrintAsJSON(ipList.itemsMap, t)
|
||||
logs.PrintAsJSON(ipList.allItemsMap, t)
|
||||
logs.PrintAsJSON(ipList.ItemsMap(), t)
|
||||
logs.PrintAsJSON(ipList.AllItemsMap(), t)
|
||||
logs.PrintAsJSON(ipList.IPMap(), t)
|
||||
}
|
||||
|
||||
func TestIPList_Add_One(t *testing.T) {
|
||||
ipList := NewIPList()
|
||||
ipList.Add(&IPItem{
|
||||
var a = assert.NewAssertion(t)
|
||||
|
||||
var ipList = iplibrary.NewIPList()
|
||||
ipList.Add(&iplibrary.IPItem{
|
||||
Id: 1,
|
||||
IPFrom: utils.IP2Long("192.168.1.1"),
|
||||
IPFrom: iputils.ToBytes("192.168.1.1"),
|
||||
})
|
||||
ipList.Add(&IPItem{
|
||||
ipList.Add(&iplibrary.IPItem{
|
||||
Id: 2,
|
||||
IPTo: utils.IP2Long("192.168.1.2"),
|
||||
IPTo: iputils.ToBytes("192.168.1.2"),
|
||||
})
|
||||
ipList.Add(&IPItem{
|
||||
ipList.Add(&iplibrary.IPItem{
|
||||
Id: 3,
|
||||
IPFrom: utils.IP2Long("192.168.0.2"),
|
||||
IPFrom: iputils.ToBytes("192.168.0.2"),
|
||||
})
|
||||
ipList.Add(&IPItem{
|
||||
ipList.Add(&iplibrary.IPItem{
|
||||
Id: 4,
|
||||
IPFrom: utils.IP2Long("192.168.0.2"),
|
||||
IPTo: utils.IP2Long("192.168.0.1"),
|
||||
IPFrom: iputils.ToBytes("192.168.0.2"),
|
||||
IPTo: iputils.ToBytes("192.168.0.1"),
|
||||
})
|
||||
ipList.Add(&IPItem{
|
||||
ipList.Add(&iplibrary.IPItem{
|
||||
Id: 5,
|
||||
IPFrom: utils.IP2Long("2001:db8:0:1::101"),
|
||||
IPFrom: iputils.ToBytes("2001:db8:0:1::101"),
|
||||
})
|
||||
ipList.Add(&IPItem{
|
||||
ipList.Add(&iplibrary.IPItem{
|
||||
Id: 6,
|
||||
IPFrom: 0,
|
||||
IPFrom: nil,
|
||||
Type: "all",
|
||||
})
|
||||
t.Log("===items===")
|
||||
logs.PrintAsJSON(ipList.itemsMap, t)
|
||||
logs.PrintAsJSON(ipList.ItemsMap(), t)
|
||||
|
||||
t.Log("===sorted items===")
|
||||
logs.PrintAsJSON(ipList.sortedItems, t)
|
||||
logs.PrintAsJSON(ipList.SortedRangeItems(), t)
|
||||
|
||||
t.Log("===all items===")
|
||||
logs.PrintAsJSON(ipList.allItemsMap, t) // ip => items
|
||||
a.IsTrue(len(ipList.AllItemsMap()) == 1)
|
||||
logs.PrintAsJSON(ipList.AllItemsMap(), t) // ip => items
|
||||
|
||||
t.Log("===ip items===")
|
||||
logs.PrintAsJSON(ipList.IPMap())
|
||||
}
|
||||
|
||||
func TestIPList_Update(t *testing.T) {
|
||||
ipList := NewIPList()
|
||||
ipList.Add(&IPItem{
|
||||
var ipList = iplibrary.NewIPList()
|
||||
ipList.Add(&iplibrary.IPItem{
|
||||
Id: 1,
|
||||
IPFrom: utils.IP2Long("192.168.1.1"),
|
||||
IPFrom: iputils.ToBytes("192.168.1.1"),
|
||||
})
|
||||
/**ipList.Add(&IPItem{
|
||||
|
||||
t.Log("===before===")
|
||||
logs.PrintAsJSON(ipList.ItemsMap(), t)
|
||||
logs.PrintAsJSON(ipList.SortedRangeItems(), t)
|
||||
logs.PrintAsJSON(ipList.IPMap(), t)
|
||||
|
||||
/**ipList.Add(&iplibrary.IPItem{
|
||||
Id: 2,
|
||||
IPFrom: utils.IP2Long("192.168.1.1"),
|
||||
IPFrom: iputils.ToBytes("192.168.1.1"),
|
||||
})**/
|
||||
ipList.Add(&IPItem{
|
||||
Id: 1,
|
||||
IPTo: utils.IP2Long("192.168.1.2"),
|
||||
ipList.Add(&iplibrary.IPItem{
|
||||
Id: 1,
|
||||
//IPFrom: 123,
|
||||
IPTo: iputils.ToBytes("192.168.1.2"),
|
||||
})
|
||||
logs.PrintAsJSON(ipList.itemsMap, t)
|
||||
logs.PrintAsJSON(ipList.sortedItems, t)
|
||||
|
||||
t.Log("===after===")
|
||||
logs.PrintAsJSON(ipList.ItemsMap(), t)
|
||||
logs.PrintAsJSON(ipList.SortedRangeItems(), t)
|
||||
logs.PrintAsJSON(ipList.IPMap(), t)
|
||||
}
|
||||
|
||||
func TestIPList_Update_AllItems(t *testing.T) {
|
||||
ipList := NewIPList()
|
||||
ipList.Add(&IPItem{
|
||||
var ipList = iplibrary.NewIPList()
|
||||
ipList.Add(&iplibrary.IPItem{
|
||||
Id: 1,
|
||||
Type: IPItemTypeAll,
|
||||
IPFrom: 0,
|
||||
Type: iplibrary.IPItemTypeAll,
|
||||
IPFrom: nil,
|
||||
})
|
||||
ipList.Add(&IPItem{
|
||||
ipList.Add(&iplibrary.IPItem{
|
||||
Id: 1,
|
||||
IPTo: 0,
|
||||
IPTo: nil,
|
||||
})
|
||||
t.Log("===items map===")
|
||||
logs.PrintAsJSON(ipList.itemsMap, t)
|
||||
logs.PrintAsJSON(ipList.ItemsMap(), t)
|
||||
t.Log("===all items map===")
|
||||
logs.PrintAsJSON(ipList.allItemsMap, t)
|
||||
logs.PrintAsJSON(ipList.AllItemsMap(), t)
|
||||
t.Log("===ip map===")
|
||||
logs.PrintAsJSON(ipList.IPMap())
|
||||
}
|
||||
|
||||
func TestIPList_Add_Range(t *testing.T) {
|
||||
ipList := NewIPList()
|
||||
ipList.Add(&IPItem{
|
||||
var a = assert.NewAssertion(t)
|
||||
|
||||
var ipList = iplibrary.NewIPList()
|
||||
ipList.Add(&iplibrary.IPItem{
|
||||
Id: 1,
|
||||
IPFrom: utils.IP2Long("192.168.1.1"),
|
||||
IPTo: utils.IP2Long("192.168.2.1"),
|
||||
IPFrom: iputils.ToBytes("192.168.1.1"),
|
||||
IPTo: iputils.ToBytes("192.168.2.1"),
|
||||
})
|
||||
ipList.Add(&IPItem{
|
||||
ipList.Add(&iplibrary.IPItem{
|
||||
Id: 2,
|
||||
IPTo: utils.IP2Long("192.168.1.2"),
|
||||
IPTo: iputils.ToBytes("192.168.1.2"),
|
||||
})
|
||||
t.Log(len(ipList.itemsMap), "ips")
|
||||
logs.PrintAsJSON(ipList.itemsMap, t)
|
||||
logs.PrintAsJSON(ipList.allItemsMap, t)
|
||||
}
|
||||
|
||||
func TestIPList_Add_Overflow(t *testing.T) {
|
||||
a := assert.NewAssertion(t)
|
||||
|
||||
ipList := NewIPList()
|
||||
ipList.Add(&IPItem{
|
||||
Id: 1,
|
||||
IPFrom: utils.IP2Long("192.168.1.1"),
|
||||
IPTo: utils.IP2Long("192.169.255.1"),
|
||||
ipList.Add(&iplibrary.IPItem{
|
||||
Id: 3,
|
||||
IPFrom: iputils.ToBytes("192.168.0.1"),
|
||||
IPTo: iputils.ToBytes("192.168.0.2"),
|
||||
})
|
||||
t.Log(len(ipList.itemsMap), "ips")
|
||||
a.IsTrue(len(ipList.itemsMap) <= 65535)
|
||||
|
||||
a.IsTrue(len(ipList.SortedRangeItems()) == 2)
|
||||
|
||||
t.Log(len(ipList.ItemsMap()), "ips")
|
||||
t.Log("===items map===")
|
||||
logs.PrintAsJSON(ipList.ItemsMap(), t)
|
||||
t.Log("===sorted range items===")
|
||||
logs.PrintAsJSON(ipList.SortedRangeItems())
|
||||
t.Log("===all items map===")
|
||||
logs.PrintAsJSON(ipList.AllItemsMap(), t)
|
||||
|
||||
t.Log("===ip map===")
|
||||
logs.PrintAsJSON(ipList.IPMap(), t)
|
||||
}
|
||||
|
||||
func TestNewIPList_Memory(t *testing.T) {
|
||||
list := NewIPList()
|
||||
var list = iplibrary.NewIPList()
|
||||
|
||||
for i := 0; i < 200_0000; i++ {
|
||||
list.Add(&IPItem{
|
||||
IPFrom: 1,
|
||||
IPTo: 2,
|
||||
var count = 100
|
||||
if testutils.IsSingleTesting() {
|
||||
count = 2_000_000
|
||||
}
|
||||
var stat1 = testutils.ReadMemoryStat()
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
list.AddDelay(&iplibrary.IPItem{
|
||||
Id: uint64(i),
|
||||
IPFrom: iputils.ToBytes(testutils.RandIP()),
|
||||
IPTo: iputils.ToBytes(testutils.RandIP()),
|
||||
ExpiredAt: time.Now().Unix(),
|
||||
})
|
||||
}
|
||||
|
||||
t.Log("ok")
|
||||
list.Sort()
|
||||
|
||||
runtime.GC()
|
||||
|
||||
var stat2 = testutils.ReadMemoryStat()
|
||||
t.Log((stat2.HeapInuse-stat1.HeapInuse)>>20, "MB")
|
||||
}
|
||||
|
||||
func TestIPList_Contains(t *testing.T) {
|
||||
var a = assert.NewAssertion(t)
|
||||
|
||||
list := NewIPList()
|
||||
var list = iplibrary.NewIPList()
|
||||
for i := 0; i < 255; i++ {
|
||||
list.AddDelay(&IPItem{
|
||||
list.Add(&iplibrary.IPItem{
|
||||
Id: uint64(i),
|
||||
IPFrom: utils.IP2Long(strconv.Itoa(i) + ".168.0.1"),
|
||||
IPTo: utils.IP2Long(strconv.Itoa(i) + ".168.255.1"),
|
||||
IPFrom: iputils.ToBytes(strconv.Itoa(i) + ".168.0.1"),
|
||||
IPTo: iputils.ToBytes(strconv.Itoa(i) + ".168.255.1"),
|
||||
ExpiredAt: 0,
|
||||
})
|
||||
}
|
||||
for i := 0; i < 255; i++ {
|
||||
list.AddDelay(&IPItem{
|
||||
list.Add(&iplibrary.IPItem{
|
||||
Id: uint64(1000 + i),
|
||||
IPFrom: utils.IP2Long("192.167.2." + strconv.Itoa(i)),
|
||||
IPFrom: iputils.ToBytes("192.167.2." + strconv.Itoa(i)),
|
||||
})
|
||||
}
|
||||
list.Sort()
|
||||
t.Log(len(list.itemsMap), "ip")
|
||||
|
||||
before := time.Now()
|
||||
a.IsTrue(list.Contains(utils.IP2Long("192.168.1.100")))
|
||||
a.IsTrue(list.Contains(utils.IP2Long("192.168.2.100")))
|
||||
a.IsFalse(list.Contains(utils.IP2Long("192.169.3.100")))
|
||||
a.IsFalse(list.Contains(utils.IP2Long("192.167.3.100")))
|
||||
a.IsTrue(list.Contains(utils.IP2Long("192.167.2.100")))
|
||||
list.Add(&iplibrary.IPItem{
|
||||
Id: 10000,
|
||||
IPFrom: iputils.ToBytes("::1"),
|
||||
})
|
||||
list.Add(&iplibrary.IPItem{
|
||||
Id: 10001,
|
||||
IPFrom: iputils.ToBytes("::2"),
|
||||
IPTo: iputils.ToBytes("::5"),
|
||||
})
|
||||
|
||||
t.Log(len(list.ItemsMap()), "ip")
|
||||
|
||||
var before = time.Now()
|
||||
a.IsTrue(list.Contains(iputils.ToBytes("192.168.1.100")))
|
||||
a.IsTrue(list.Contains(iputils.ToBytes("192.168.2.100")))
|
||||
a.IsFalse(list.Contains(iputils.ToBytes("192.169.3.100")))
|
||||
a.IsFalse(list.Contains(iputils.ToBytes("192.167.3.100")))
|
||||
a.IsTrue(list.Contains(iputils.ToBytes("192.167.2.100")))
|
||||
a.IsTrue(list.Contains(iputils.ToBytes("::1")))
|
||||
a.IsTrue(list.Contains(iputils.ToBytes("::3")))
|
||||
a.IsFalse(list.Contains(iputils.ToBytes("::8")))
|
||||
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||
}
|
||||
|
||||
func TestIPList_Contains_Many(t *testing.T) {
|
||||
list := NewIPList()
|
||||
var list = iplibrary.NewIPList()
|
||||
for i := 0; i < 1_000_000; i++ {
|
||||
list.AddDelay(&IPItem{
|
||||
list.AddDelay(&iplibrary.IPItem{
|
||||
Id: uint64(i),
|
||||
IPFrom: utils.IP2Long(strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255))),
|
||||
IPTo: utils.IP2Long(strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255))),
|
||||
IPFrom: iputils.ToBytes(strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255))),
|
||||
IPTo: iputils.ToBytes(strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255))),
|
||||
ExpiredAt: 0,
|
||||
})
|
||||
}
|
||||
list.Sort()
|
||||
t.Log(len(list.itemsMap), "ip")
|
||||
|
||||
before := time.Now()
|
||||
_ = list.Contains(utils.IP2Long("192.168.1.100"))
|
||||
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||
var before = time.Now()
|
||||
list.Sort()
|
||||
t.Log("sort cost:", time.Since(before).Seconds()*1000, "ms")
|
||||
t.Log(len(list.ItemsMap()), "ip")
|
||||
|
||||
before = time.Now()
|
||||
_ = list.Contains(iputils.ToBytes("192.168.1.100"))
|
||||
t.Log("contains cost:", time.Since(before).Seconds()*1000, "ms")
|
||||
}
|
||||
|
||||
func TestIPList_ContainsAll(t *testing.T) {
|
||||
list := NewIPList()
|
||||
list.Add(&IPItem{
|
||||
Id: 1,
|
||||
Type: "all",
|
||||
IPFrom: 0,
|
||||
})
|
||||
b := list.Contains(utils.IP2Long("192.168.1.1"))
|
||||
if b {
|
||||
t.Log(b)
|
||||
} else {
|
||||
t.Fatal("'b' should be true")
|
||||
var a = assert.NewAssertion(t)
|
||||
|
||||
{
|
||||
var list = iplibrary.NewIPList()
|
||||
list.Add(&iplibrary.IPItem{
|
||||
Id: 1,
|
||||
Type: "all",
|
||||
IPFrom: nil,
|
||||
})
|
||||
var b = list.Contains(iputils.ToBytes("192.168.1.1"))
|
||||
a.IsTrue(b)
|
||||
|
||||
list.Delete(1)
|
||||
|
||||
b = list.Contains(iputils.ToBytes("192.168.1.1"))
|
||||
a.IsFalse(b)
|
||||
}
|
||||
|
||||
list.Delete(1)
|
||||
{
|
||||
var list = iplibrary.NewIPList()
|
||||
list.Add(&iplibrary.IPItem{
|
||||
Id: 1,
|
||||
Type: "all",
|
||||
IPFrom: iputils.ToBytes("0.0.0.0"),
|
||||
})
|
||||
var b = list.Contains(iputils.ToBytes("192.168.1.1"))
|
||||
a.IsTrue(b)
|
||||
|
||||
b = list.Contains(utils.IP2Long("192.168.1.1"))
|
||||
if !b {
|
||||
t.Log(b)
|
||||
} else {
|
||||
t.Fatal("'b' should be false")
|
||||
list.Delete(1)
|
||||
|
||||
b = list.Contains(iputils.ToBytes("192.168.1.1"))
|
||||
a.IsFalse(b)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestIPList_ContainsIPStrings(t *testing.T) {
|
||||
var a = assert.NewAssertion(t)
|
||||
|
||||
list := NewIPList()
|
||||
var list = iplibrary.NewIPList()
|
||||
for i := 0; i < 255; i++ {
|
||||
list.Add(&IPItem{
|
||||
list.Add(&iplibrary.IPItem{
|
||||
Id: uint64(i),
|
||||
IPFrom: utils.IP2Long(strconv.Itoa(i) + ".168.0.1"),
|
||||
IPTo: utils.IP2Long(strconv.Itoa(i) + ".168.255.1"),
|
||||
IPFrom: iputils.ToBytes(strconv.Itoa(i) + ".168.0.1"),
|
||||
IPTo: iputils.ToBytes(strconv.Itoa(i) + ".168.255.1"),
|
||||
ExpiredAt: 0,
|
||||
})
|
||||
}
|
||||
t.Log(len(list.itemsMap), "ip")
|
||||
t.Log(len(list.ItemsMap()), "ip")
|
||||
|
||||
{
|
||||
item, ok := list.ContainsIPStrings([]string{"192.168.1.100"})
|
||||
@@ -238,85 +305,191 @@ func TestIPList_ContainsIPStrings(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIPList_Delete(t *testing.T) {
|
||||
list := NewIPList()
|
||||
list.Add(&IPItem{
|
||||
var list = iplibrary.NewIPList()
|
||||
list.Add(&iplibrary.IPItem{
|
||||
Id: 1,
|
||||
IPFrom: utils.IP2Long("192.168.0.1"),
|
||||
IPFrom: iputils.ToBytes("192.168.0.1"),
|
||||
ExpiredAt: 0,
|
||||
})
|
||||
list.Add(&IPItem{
|
||||
list.Add(&iplibrary.IPItem{
|
||||
Id: 2,
|
||||
IPFrom: utils.IP2Long("192.168.0.1"),
|
||||
IPFrom: iputils.ToBytes("192.168.0.1"),
|
||||
ExpiredAt: 0,
|
||||
})
|
||||
t.Log("===BEFORE===")
|
||||
logs.PrintAsJSON(list.itemsMap, t)
|
||||
logs.PrintAsJSON(list.allItemsMap, t)
|
||||
list.Add(&iplibrary.IPItem{
|
||||
Id: 3,
|
||||
IPFrom: iputils.ToBytes("192.168.1.1"),
|
||||
IPTo: iputils.ToBytes("192.168.2.1"),
|
||||
ExpiredAt: 0,
|
||||
})
|
||||
t.Log("===before===")
|
||||
logs.PrintAsJSON(list.ItemsMap(), t)
|
||||
logs.PrintAsJSON(list.AllItemsMap(), t)
|
||||
logs.PrintAsJSON(list.SortedRangeItems())
|
||||
logs.PrintAsJSON(list.IPMap(), t)
|
||||
|
||||
{
|
||||
var found bool
|
||||
for _, item := range list.SortedRangeItems() {
|
||||
if item.Id == 3 {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Fatal("should be found")
|
||||
}
|
||||
}
|
||||
|
||||
list.Delete(1)
|
||||
|
||||
t.Log("===AFTER===")
|
||||
logs.PrintAsJSON(list.itemsMap, t)
|
||||
logs.PrintAsJSON(list.allItemsMap, t)
|
||||
t.Log("===after===")
|
||||
logs.PrintAsJSON(list.ItemsMap(), t)
|
||||
logs.PrintAsJSON(list.AllItemsMap(), t)
|
||||
logs.PrintAsJSON(list.SortedRangeItems())
|
||||
logs.PrintAsJSON(list.IPMap(), t)
|
||||
|
||||
list.Delete(3)
|
||||
|
||||
{
|
||||
var found bool
|
||||
for _, item := range list.SortedRangeItems() {
|
||||
if item.Id == 3 {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if found {
|
||||
t.Fatal("should be not found")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGC(t *testing.T) {
|
||||
list := NewIPList()
|
||||
list.Add(&IPItem{
|
||||
func TestIPList_GC(t *testing.T) {
|
||||
var a = assert.NewAssertion(t)
|
||||
|
||||
var list = iplibrary.NewIPList()
|
||||
list.Add(&iplibrary.IPItem{
|
||||
Id: 1,
|
||||
IPFrom: utils.IP2Long("192.168.1.100"),
|
||||
IPTo: utils.IP2Long("192.168.1.101"),
|
||||
IPFrom: iputils.ToBytes("192.168.1.100"),
|
||||
IPTo: iputils.ToBytes("192.168.1.101"),
|
||||
ExpiredAt: time.Now().Unix() + 1,
|
||||
})
|
||||
list.Add(&IPItem{
|
||||
list.Add(&iplibrary.IPItem{
|
||||
Id: 2,
|
||||
IPFrom: utils.IP2Long("192.168.1.102"),
|
||||
IPTo: utils.IP2Long("192.168.1.103"),
|
||||
IPFrom: iputils.ToBytes("192.168.1.102"),
|
||||
IPTo: iputils.ToBytes("192.168.1.103"),
|
||||
ExpiredAt: 0,
|
||||
})
|
||||
logs.PrintAsJSON(list.itemsMap, t)
|
||||
logs.PrintAsJSON(list.allItemsMap, t)
|
||||
logs.PrintAsJSON(list.ItemsMap(), t)
|
||||
logs.PrintAsJSON(list.AllItemsMap(), t)
|
||||
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
t.Log("===AFTER GC===")
|
||||
logs.PrintAsJSON(list.itemsMap, t)
|
||||
logs.PrintAsJSON(list.sortedItems, t)
|
||||
logs.PrintAsJSON(list.ItemsMap(), t)
|
||||
logs.PrintAsJSON(list.SortedRangeItems(), t)
|
||||
|
||||
a.IsTrue(len(list.ItemsMap()) == 1)
|
||||
a.IsTrue(len(list.SortedRangeItems()) == 1)
|
||||
}
|
||||
|
||||
func TestTooManyLists(t *testing.T) {
|
||||
func TestManyLists(t *testing.T) {
|
||||
debug.SetMaxThreads(20)
|
||||
|
||||
var lists = []*IPList{}
|
||||
var lists = []*iplibrary.IPList{}
|
||||
var locker = &sync.Mutex{}
|
||||
for i := 0; i < 1000; i++ {
|
||||
locker.Lock()
|
||||
lists = append(lists, NewIPList())
|
||||
lists = append(lists, iplibrary.NewIPList())
|
||||
locker.Unlock()
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
if testutils.IsSingleTesting() {
|
||||
time.Sleep(3 * time.Second)
|
||||
}
|
||||
t.Log(runtime.NumGoroutine())
|
||||
t.Log(len(lists), "lists")
|
||||
}
|
||||
|
||||
func BenchmarkIPList_Add(b *testing.B) {
|
||||
runtime.GOMAXPROCS(1)
|
||||
|
||||
var list = iplibrary.NewIPList()
|
||||
for i := 1; i < 200_000; i++ {
|
||||
list.AddDelay(&iplibrary.IPItem{
|
||||
Id: uint64(i),
|
||||
IPFrom: iputils.ToBytes(strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + ".0.1"),
|
||||
IPTo: iputils.ToBytes(strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + ".0.1"),
|
||||
ExpiredAt: time.Now().Unix() + 60,
|
||||
})
|
||||
}
|
||||
|
||||
list.Sort()
|
||||
|
||||
b.Log(len(list.ItemsMap()), "ip")
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
var ip = fmt.Sprintf("%d.%d.%d.%d", rand.Int()%255, rand.Int()%255, rand.Int()%255, rand.Int()%255)
|
||||
list.Add(&iplibrary.IPItem{
|
||||
Type: "",
|
||||
Id: uint64(i % 1_000_000),
|
||||
IPFrom: iputils.ToBytes(ip),
|
||||
IPTo: nil,
|
||||
ExpiredAt: fasttime.Now().Unix() + 3600,
|
||||
EventLevel: "",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkIPList_Contains(b *testing.B) {
|
||||
runtime.GOMAXPROCS(1)
|
||||
|
||||
var list = NewIPList()
|
||||
for i := 1; i < 200_000; i++ {
|
||||
list.AddDelay(&IPItem{
|
||||
var list = iplibrary.NewIPList()
|
||||
for i := 1; i < 1_000_000; i++ {
|
||||
var item = &iplibrary.IPItem{
|
||||
Id: uint64(i),
|
||||
IPFrom: utils.IP2Long(strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + ".0.1"),
|
||||
IPTo: utils.IP2Long(strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + ".0.1"),
|
||||
IPFrom: iputils.ToBytes(strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + ".0.1"),
|
||||
ExpiredAt: time.Now().Unix() + 60,
|
||||
})
|
||||
}
|
||||
if i%100 == 0 {
|
||||
item.IPTo = iputils.ToBytes(strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + ".0.1")
|
||||
}
|
||||
list.Add(item)
|
||||
}
|
||||
list.Sort()
|
||||
|
||||
b.Log(len(list.itemsMap), "ip")
|
||||
//b.Log(len(list.ItemsMap()), "ip")
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = list.Contains(utils.IP2Long("192.168.1.100"))
|
||||
}
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
_ = list.Contains(iputils.ToBytes(testutils.RandIP()))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkIPList_Sort(b *testing.B) {
|
||||
var list = iplibrary.NewIPList()
|
||||
for i := 0; i < 1_000_000; i++ {
|
||||
var item = &iplibrary.IPItem{
|
||||
Id: uint64(i),
|
||||
IPFrom: iputils.ToBytes(strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + ".0.1"),
|
||||
ExpiredAt: time.Now().Unix() + 60,
|
||||
}
|
||||
|
||||
if i%100 == 0 {
|
||||
item.IPTo = iputils.ToBytes(strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + ".0.1")
|
||||
}
|
||||
|
||||
list.AddDelay(item)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
list.Sort()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user