mirror of
https://github.com/joyieldInc/predixy.git
synced 2025-12-24 14:36:42 +08:00
release 1.0.0
This commit is contained in:
parent
755a9bc944
commit
801df5e2b8
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
|||||||
BSD 3-Clause License
|
BSD 3-Clause License
|
||||||
|
|
||||||
Copyright (c) 2017, joyieldInc
|
Copyright (c) 2017, Joyield, Inc. <joyield.com@gmail.com>
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
|||||||
19
Makefile
Normal file
19
Makefile
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
|
||||||
|
.PHONY : default debug clean
|
||||||
|
|
||||||
|
make = make
|
||||||
|
plt = $(shell uname)
|
||||||
|
ifeq ($(plt), FreeBSD)
|
||||||
|
make = gmake
|
||||||
|
else ifeq ($(plt), OpenBSD)
|
||||||
|
make = gmake
|
||||||
|
endif
|
||||||
|
|
||||||
|
default:
|
||||||
|
@$(make) -C src -f Makefile
|
||||||
|
|
||||||
|
debug:
|
||||||
|
@$(make) -C src -f Makefile debug
|
||||||
|
|
||||||
|
clean:
|
||||||
|
@$(make) -C src -f Makefile clean
|
||||||
118
README.md
Normal file
118
README.md
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
# Predixy
|
||||||
|
|
||||||
|
**Predixy** is a high performance and full features proxy for [redis](http://redis.io/)
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
+ High performance and lightweight.
|
||||||
|
+ Multi-threads support.
|
||||||
|
+ Works on Linux, OSX, BSD, Windows([Cygwin](http://www.cygwin.com/)).
|
||||||
|
+ Supports Redis Sentinel, single/multi redis group[s].
|
||||||
|
+ Supports Redis Cluster.
|
||||||
|
+ Supports redis block command, eg:blpop, brpop, brpoplpush.
|
||||||
|
+ Supports scan command, even multi redis instances.
|
||||||
|
+ Multi-databases support, means redis command select is avaliable.
|
||||||
|
+ Supports redis transaction, limit in Redis Sentinel single redis group.
|
||||||
|
+ Supports redis Scripts, script load, eval, evalsha.
|
||||||
|
+ Supports redis Pub/Sub.
|
||||||
|
+ Multi-DataCenters support.
|
||||||
|
+ Extend AUTH, readonly/readwrite/admin permission, keyspace limit.
|
||||||
|
+ Log level sample, async log record.
|
||||||
|
+ Log file auto rotate by time and/or file size.
|
||||||
|
+ Stats info, CPU/Memory/Requests/Responses and so on.
|
||||||
|
+ Latency monitor.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
Predixy can be compiled and used on Linux, OSX, BSD, Windows([Cygwin](http://www.cygwin.com/)).
|
||||||
|
|
||||||
|
It is as simple as:
|
||||||
|
|
||||||
|
$ make
|
||||||
|
|
||||||
|
To build in debug mode:
|
||||||
|
|
||||||
|
$ make debug
|
||||||
|
|
||||||
|
Some other build options:
|
||||||
|
+ CXX=c++compiler, default is g++, you can specify other, eg:CXX=clang++
|
||||||
|
+ EV=epoll|poll|kqueue, default it is auto detect according by platform.
|
||||||
|
+ MT=false, disable multi-threads support.
|
||||||
|
+ TS=true, enable predixy function call time stats, debug only for developer.
|
||||||
|
|
||||||
|
For examples:
|
||||||
|
|
||||||
|
$ make CXX=clang++
|
||||||
|
$ make EV=poll
|
||||||
|
$ make MT=false
|
||||||
|
$ make debug MT=false TS=true
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
See below files:
|
||||||
|
+ predixy.conf, basic config.
|
||||||
|
+ cluster.conf, Redis Cluster backend config.
|
||||||
|
+ sentinel.conf, Redis Sentinel backend config.
|
||||||
|
+ auth.conf, authority control config.
|
||||||
|
+ dc.conf, multi-datacenters config.
|
||||||
|
+ latency.conf, latency monitor config.
|
||||||
|
|
||||||
|
## Running
|
||||||
|
|
||||||
|
$ ./predixy ../conf/predixy.conf
|
||||||
|
|
||||||
|
With default predixy.conf, Predixy will proxy to Redis Cluster 127.0.0.1:6379,
|
||||||
|
In general, 127.0.0.1:6379 is not running in Redis Cluster mode,
|
||||||
|
So you will look mass log output. But you can still test it with redis-cli.
|
||||||
|
|
||||||
|
$ redis-cli -p 7617 info
|
||||||
|
|
||||||
|
More command line arguments:
|
||||||
|
|
||||||
|
$ ./predixy -h
|
||||||
|
|
||||||
|
## Stats
|
||||||
|
|
||||||
|
Like redis, predixy use INFO command to give stats.
|
||||||
|
|
||||||
|
Show predixy running info and latency monitors
|
||||||
|
|
||||||
|
redis> INFO
|
||||||
|
|
||||||
|
Show latency monitors by latency name
|
||||||
|
|
||||||
|
redis> INFO Latency <latency-name>
|
||||||
|
|
||||||
|
A latency monitor example:
|
||||||
|
|
||||||
|
LatencyMonitorName:all
|
||||||
|
latency(us) sum(us) counts
|
||||||
|
<= 100 3769836 91339 91.34%
|
||||||
|
<= 200 777185 5900 97.24%
|
||||||
|
<= 300 287565 1181 98.42%
|
||||||
|
<= 400 185891 537 98.96%
|
||||||
|
<= 500 132773 299 99.26%
|
||||||
|
<= 600 85050 156 99.41%
|
||||||
|
<= 700 85455 133 99.54%
|
||||||
|
<= 800 40088 54 99.60%
|
||||||
|
<= 1000 67788 77 99.68%
|
||||||
|
> 1000 601012 325 100.00%
|
||||||
|
T 60 6032643 100001
|
||||||
|
The last line is total summary, 624 is average latency(us)
|
||||||
|
|
||||||
|
|
||||||
|
Show latency monitors by server address and latency name
|
||||||
|
|
||||||
|
redis> INFO ServerLatency <server-address> [latency-name]
|
||||||
|
|
||||||
|
Reset all stats and latency monitors
|
||||||
|
|
||||||
|
redis> INFO ResetStats
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Copyright (C) 2017 Joyield, Inc. <joyield.com<at>gmail.com>
|
||||||
|
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
License under BSD 3-clause "New" or "Revised" License
|
||||||
71
conf/auth.conf
Normal file
71
conf/auth.conf
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
## Authority control
|
||||||
|
## Authority {
|
||||||
|
## Auth [password] {
|
||||||
|
## Mode read|write|admin
|
||||||
|
## [KeyPrefix Prefix1 Prefix2...]
|
||||||
|
## [ReadKeyPrefix Prefix1 Prefix2...]
|
||||||
|
## [WriteKeyPrefix Prefix1 Prefix2...]
|
||||||
|
## }...
|
||||||
|
## }
|
||||||
|
|
||||||
|
## Example:
|
||||||
|
# Authority {
|
||||||
|
##------------------------------------------------------------------------
|
||||||
|
# Auth {
|
||||||
|
# Mode read
|
||||||
|
# }
|
||||||
|
#### password is empty, this Auth is default auth
|
||||||
|
#### Mode read, client connection is readonly,
|
||||||
|
#### No KeyPrefix or ReadKeyPrefix defined, all key can be visit
|
||||||
|
##------------------------------------------------------------------------
|
||||||
|
# Auth abc {
|
||||||
|
# Mode write
|
||||||
|
# }
|
||||||
|
#### password is "abc", the client must send command Auth abc
|
||||||
|
#### Mode write, client connection can read and write
|
||||||
|
#### No KeyPrefix, ReadKeyPrefix, WriteKeyPrefix define, all key can be visit
|
||||||
|
##------------------------------------------------------------------------
|
||||||
|
# Auth bcd {
|
||||||
|
# Mode admin
|
||||||
|
# }
|
||||||
|
#### password is "abc", the client must send command Auth bcd
|
||||||
|
#### Mode admin, client connection can read and write and admin,
|
||||||
|
#### the CONFIG command need admin permission
|
||||||
|
#### No KeyPrefix, ReadKeyPrefix, WriteKeyPrefix define, all key can be visit
|
||||||
|
##------------------------------------------------------------------------
|
||||||
|
# Auth cde {
|
||||||
|
# Mode read
|
||||||
|
# KeyPrefix User
|
||||||
|
# }
|
||||||
|
#### password is "cde", the client must send command Auth cde
|
||||||
|
#### Mode read, client connection is readonly,
|
||||||
|
#### KeyPrefix User, client can read UserXXX key, eg: GET User.123,
|
||||||
|
#### if client request GET hello, will be deny
|
||||||
|
##------------------------------------------------------------------------
|
||||||
|
# Auth def {
|
||||||
|
# Mode write
|
||||||
|
# ReadKeyPrefix User Stats
|
||||||
|
# WriteKeyPrefix User
|
||||||
|
# }
|
||||||
|
#### password is "cde", the client must send command Auth cde
|
||||||
|
#### Mode read, client connection can read and write, but read and write
|
||||||
|
#### keyspace is diffrent, client can GET User.123 and also
|
||||||
|
#### SET User.123 SomeValue, but SET Stats.123 will be deny
|
||||||
|
##------------------------------------------------------------------------
|
||||||
|
# }
|
||||||
|
## if no Authority spcified, equality below Authority
|
||||||
|
# Authority {
|
||||||
|
# Auth {
|
||||||
|
# Mode admin
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
|
||||||
|
Authority {
|
||||||
|
Auth {
|
||||||
|
Mode write
|
||||||
|
}
|
||||||
|
Auth "#a complex password#" {
|
||||||
|
Mode admin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
31
conf/cluster.conf
Normal file
31
conf/cluster.conf
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
## redis cluster server pool define
|
||||||
|
|
||||||
|
##ClusterServerPool {
|
||||||
|
## [Password xxx] #default no
|
||||||
|
## [MasterReadPriority [0-100]] #default 50
|
||||||
|
## [StaticSlaveReadPriority [0-100]] #default 0
|
||||||
|
## [DynamicSlaveReadPriority [0-100]] #default 0
|
||||||
|
## [RefreshInterval seconds] #default 1
|
||||||
|
## [ServerFailureLimit number] #default 10
|
||||||
|
## [ServerRetryTimeout seconds] #default 1
|
||||||
|
## Servers {
|
||||||
|
## + addr
|
||||||
|
## ...
|
||||||
|
## }
|
||||||
|
##}
|
||||||
|
|
||||||
|
|
||||||
|
## Examples:
|
||||||
|
#ClusterServerPool {
|
||||||
|
# MasterReadPriority 60
|
||||||
|
# StaticSlaveReadPriority 50
|
||||||
|
# DynamicSlaveReadPriority 50
|
||||||
|
# RefreshInterval 1
|
||||||
|
# ServerFailureLimit 10
|
||||||
|
# ServerRetryTimeout 1
|
||||||
|
# Servers {
|
||||||
|
# + 192.168.2.107:2211
|
||||||
|
# + 192.168.2.107:2212
|
||||||
|
# }
|
||||||
|
#}
|
||||||
|
|
||||||
47
conf/dc.conf
Normal file
47
conf/dc.conf
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
## DataCenter
|
||||||
|
## DataCenter {
|
||||||
|
## DC name {
|
||||||
|
## AddrPrefix {
|
||||||
|
## + IpPrefix
|
||||||
|
## ...
|
||||||
|
## }
|
||||||
|
## ReadPolicy {
|
||||||
|
## name priority [weight]
|
||||||
|
## other priority [weight]
|
||||||
|
## }
|
||||||
|
## }
|
||||||
|
## ...
|
||||||
|
## }
|
||||||
|
## Examples:
|
||||||
|
#DataCenter {
|
||||||
|
# DC bj {
|
||||||
|
# AddrPrefix {
|
||||||
|
# + 10.1
|
||||||
|
# }
|
||||||
|
# ReadPolicy {
|
||||||
|
# bj 50
|
||||||
|
# sh 20
|
||||||
|
# sz 10
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# DC sh {
|
||||||
|
# AddrPrefix {
|
||||||
|
# + 10.2
|
||||||
|
# }
|
||||||
|
# ReadPolicy {
|
||||||
|
# sh 50
|
||||||
|
# bj 20 5
|
||||||
|
# sz 20 2
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# DC sz {
|
||||||
|
# AddrPrefix {
|
||||||
|
# + 10.3
|
||||||
|
# }
|
||||||
|
# ReadPolicy {
|
||||||
|
# sz 50
|
||||||
|
# sh 20
|
||||||
|
# bj 10
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
#}
|
||||||
133
conf/latency.conf
Normal file
133
conf/latency.conf
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
## LatencyMonitor record command time eplapsed
|
||||||
|
## redis command INFO will show the latency monitor results
|
||||||
|
##
|
||||||
|
## see predixy info, include latency monitor for predixy
|
||||||
|
## redis> INFO
|
||||||
|
##
|
||||||
|
## see latency monitor for specify latency name
|
||||||
|
## redis> INFO Latency name
|
||||||
|
##
|
||||||
|
## see latency monitor for specify server
|
||||||
|
## redis> INFO ServerLatency ServAddr [name]
|
||||||
|
##
|
||||||
|
## reset all stats info, include latency monitor
|
||||||
|
## redis> INFO ResetStats
|
||||||
|
##
|
||||||
|
## Examples:
|
||||||
|
## LatencyMonitor name {
|
||||||
|
## Commands {
|
||||||
|
## + cmd
|
||||||
|
## [- cmd]
|
||||||
|
## ...
|
||||||
|
## }
|
||||||
|
## TimeSpan {
|
||||||
|
## + TimeElapsedUS
|
||||||
|
## ...
|
||||||
|
## }
|
||||||
|
## }
|
||||||
|
## cmd is redis commands, "all" means all commands
|
||||||
|
|
||||||
|
LatencyMonitor all {
|
||||||
|
Commands {
|
||||||
|
+ all
|
||||||
|
- blpop
|
||||||
|
- brpop
|
||||||
|
- brpoplpush
|
||||||
|
}
|
||||||
|
TimeSpan {
|
||||||
|
+ 100
|
||||||
|
+ 200
|
||||||
|
+ 300
|
||||||
|
+ 400
|
||||||
|
+ 500
|
||||||
|
+ 600
|
||||||
|
+ 700
|
||||||
|
+ 800
|
||||||
|
+ 900
|
||||||
|
+ 1000
|
||||||
|
+ 1200
|
||||||
|
+ 1400
|
||||||
|
+ 1600
|
||||||
|
+ 1700
|
||||||
|
+ 1800
|
||||||
|
+ 2000
|
||||||
|
+ 2500
|
||||||
|
+ 3000
|
||||||
|
+ 3500
|
||||||
|
+ 4000
|
||||||
|
+ 4500
|
||||||
|
+ 5000
|
||||||
|
+ 6000
|
||||||
|
+ 7000
|
||||||
|
+ 8000
|
||||||
|
+ 9000
|
||||||
|
+ 10000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LatencyMonitor get {
|
||||||
|
Commands {
|
||||||
|
+ get
|
||||||
|
}
|
||||||
|
TimeSpan {
|
||||||
|
+ 100
|
||||||
|
+ 200
|
||||||
|
+ 300
|
||||||
|
+ 400
|
||||||
|
+ 500
|
||||||
|
+ 600
|
||||||
|
+ 700
|
||||||
|
+ 800
|
||||||
|
+ 900
|
||||||
|
+ 1000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LatencyMonitor set {
|
||||||
|
Commands {
|
||||||
|
+ set
|
||||||
|
+ setnx
|
||||||
|
+ setex
|
||||||
|
}
|
||||||
|
TimeSpan {
|
||||||
|
+ 100
|
||||||
|
+ 200
|
||||||
|
+ 300
|
||||||
|
+ 400
|
||||||
|
+ 500
|
||||||
|
+ 600
|
||||||
|
+ 700
|
||||||
|
+ 800
|
||||||
|
+ 900
|
||||||
|
+ 1000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LatencyMonitor blist {
|
||||||
|
Commands {
|
||||||
|
+ blpop
|
||||||
|
+ brpop
|
||||||
|
+ brpoplpush
|
||||||
|
}
|
||||||
|
TimeSpan {
|
||||||
|
+ 1000
|
||||||
|
+ 2000
|
||||||
|
+ 3000
|
||||||
|
+ 4000
|
||||||
|
+ 5000
|
||||||
|
+ 6000
|
||||||
|
+ 7000
|
||||||
|
+ 8000
|
||||||
|
+ 9000
|
||||||
|
+ 10000
|
||||||
|
+ 20000
|
||||||
|
+ 30000
|
||||||
|
+ 40000
|
||||||
|
+ 50000
|
||||||
|
+ 60000
|
||||||
|
+ 70000
|
||||||
|
+ 80000
|
||||||
|
+ 90000
|
||||||
|
+ 100000
|
||||||
|
}
|
||||||
|
}
|
||||||
98
conf/predixy.conf
Normal file
98
conf/predixy.conf
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
################################### GENERAL ####################################
|
||||||
|
## Predixy configuration file example
|
||||||
|
|
||||||
|
## Specify a name for this predixy service
|
||||||
|
## redis command INFO can get this
|
||||||
|
Name PredixyExample
|
||||||
|
|
||||||
|
## Specify listen address, support IPV4, IPV6, Unix socket
|
||||||
|
## Examples:
|
||||||
|
# Bind 127.0.0.1:7617
|
||||||
|
# Bind 0.0.0.0:7617
|
||||||
|
# Bind /tmp/predixy
|
||||||
|
|
||||||
|
## Default is 0.0.0.0:7617
|
||||||
|
# Bind 0.0.0.0:7617
|
||||||
|
|
||||||
|
## Worker threads
|
||||||
|
WorkerThreads 1
|
||||||
|
|
||||||
|
## Memory limit, 0 means unlimited
|
||||||
|
|
||||||
|
## Examples:
|
||||||
|
# MaxMemory 100M
|
||||||
|
# MaxMemory 1G
|
||||||
|
# MaxMemory 0
|
||||||
|
|
||||||
|
## MaxMemory can change online by CONFIG SET MaxMemory xxx
|
||||||
|
## Default is 0
|
||||||
|
# MaxMemory 0
|
||||||
|
|
||||||
|
## Close the connection after a client is idle for N seconds (0 to disable)
|
||||||
|
## ClientTimeout can change online by CONFIG SET ClientTimeout N
|
||||||
|
## Default is 0
|
||||||
|
ClientTimeout 300
|
||||||
|
|
||||||
|
|
||||||
|
## IO buffer size
|
||||||
|
## Default is 4096
|
||||||
|
# BufSize 4096
|
||||||
|
|
||||||
|
################################### LOG ########################################
|
||||||
|
## Log file path
|
||||||
|
## Unspecify will log to stdout
|
||||||
|
## Default is Unspecified
|
||||||
|
# Log ./predixy.log
|
||||||
|
|
||||||
|
## LogRotate support
|
||||||
|
|
||||||
|
## 1d rotate log every day
|
||||||
|
## nh rotate log every n hours 1 <= n <= 24
|
||||||
|
## nm rotate log every n minutes 1 <= n <= 1440
|
||||||
|
## nG rotate log evenry nG bytes
|
||||||
|
## nM rotate log evenry nM bytes
|
||||||
|
## time rotate and size rotate can combine eg 1h 2G, means 1h or 2G roate a time
|
||||||
|
|
||||||
|
## Examples:
|
||||||
|
# LogRotate 1d 2G
|
||||||
|
# LogRotate 1d
|
||||||
|
|
||||||
|
## Default is disable LogRotate
|
||||||
|
|
||||||
|
|
||||||
|
## In multi-threads, worker thread log need lock,
|
||||||
|
## AllowMissLog can reduce lock time for improve performance
|
||||||
|
## AllowMissLog can change online by CONFIG SET AllowMissLog true|false
|
||||||
|
## Default is true
|
||||||
|
# AllowMissLog false
|
||||||
|
|
||||||
|
## LogLevelSample, output a log every N
|
||||||
|
## all level sample can change online by CONFIG SET LogXXXSample N
|
||||||
|
LogVerbSample 0
|
||||||
|
LogDebugSample 0
|
||||||
|
LogInfoSample 10000
|
||||||
|
LogNoticeSample 1
|
||||||
|
LogWarnSample 1
|
||||||
|
LogErrorSample 1
|
||||||
|
|
||||||
|
|
||||||
|
################################### AUTHORITY ##################################
|
||||||
|
Include auth.conf
|
||||||
|
|
||||||
|
################################### SERVERS ####################################
|
||||||
|
# Include cluster.conf
|
||||||
|
# Include sentinel.conf
|
||||||
|
Include try.conf
|
||||||
|
|
||||||
|
|
||||||
|
################################### DATACENTER #################################
|
||||||
|
## LocalDC specify current machine dc
|
||||||
|
# LocalDC bj
|
||||||
|
|
||||||
|
## see dc.conf
|
||||||
|
# Include dc.conf
|
||||||
|
|
||||||
|
|
||||||
|
################################### LATENCY ####################################
|
||||||
|
## Latency monitor define, see latency.conf
|
||||||
|
Include latency.conf
|
||||||
47
conf/sentinel.conf
Normal file
47
conf/sentinel.conf
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
## redis sentinel server pool define
|
||||||
|
|
||||||
|
##SentinelServerPool {
|
||||||
|
## [Password xxx] #default no
|
||||||
|
## [Databases number] #default 1
|
||||||
|
## Hash atol|crc16
|
||||||
|
## [HashTag "xx"] #default no
|
||||||
|
## Distribution modula|random
|
||||||
|
## [MasterReadPriority [0-100]] #default 50
|
||||||
|
## [StaticSlaveReadPriority [0-100]] #default 0
|
||||||
|
## [DynamicSlaveReadPriority [0-100]] #default 0
|
||||||
|
## [RefreshInterval seconds] #default 1
|
||||||
|
## [ServerFailureLimit number] #default 10
|
||||||
|
## [ServerRetryTimeout seconds] #default 1
|
||||||
|
## Sentinels {
|
||||||
|
## + addr
|
||||||
|
## ...
|
||||||
|
## }
|
||||||
|
## Group xxx {
|
||||||
|
## [+ addr]
|
||||||
|
## ...
|
||||||
|
## }
|
||||||
|
##}
|
||||||
|
|
||||||
|
|
||||||
|
## Examples:
|
||||||
|
#SentinelServerPool {
|
||||||
|
# Databases 16
|
||||||
|
# Hash crc16
|
||||||
|
# HashTag "{}"
|
||||||
|
# Distribution modula
|
||||||
|
# MasterReadPriority 60
|
||||||
|
# StaticSlaveReadPriority 50
|
||||||
|
# DynamicSlaveReadPriority 50
|
||||||
|
# RefreshInterval 1
|
||||||
|
# ServerFailureLimit 10
|
||||||
|
# ServerRetryTimeout 1
|
||||||
|
# Sentinels {
|
||||||
|
# + 10.2.2.2
|
||||||
|
# + 10.2.2.3
|
||||||
|
# + 10.2.2.4
|
||||||
|
# }
|
||||||
|
# Group shard001 {
|
||||||
|
# }
|
||||||
|
# Group shard002 {
|
||||||
|
# }
|
||||||
|
#}
|
||||||
7
conf/try.conf
Normal file
7
conf/try.conf
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
## This conf is only for test
|
||||||
|
|
||||||
|
ClusterServerPool {
|
||||||
|
Servers {
|
||||||
|
+ 127.0.0.1:6379
|
||||||
|
}
|
||||||
|
}
|
||||||
246
src/AcceptConnection.cpp
Normal file
246
src/AcceptConnection.cpp
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "AcceptConnection.h"
|
||||||
|
#include "Conf.h"
|
||||||
|
#include "Handler.h"
|
||||||
|
|
||||||
|
|
||||||
|
AcceptConnection::AcceptConnection(int fd, sockaddr* addr, socklen_t len):
|
||||||
|
AcceptSocket(fd, addr, len),
|
||||||
|
mAuth(nullptr),
|
||||||
|
mConnectConnection(nullptr),
|
||||||
|
mLastActiveTime(0),
|
||||||
|
mBlockRequest(false)
|
||||||
|
{
|
||||||
|
mClassType = Connection::AcceptType;
|
||||||
|
}
|
||||||
|
|
||||||
|
AcceptConnection::~AcceptConnection()
|
||||||
|
{
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AcceptConnection::close()
|
||||||
|
{
|
||||||
|
AcceptSocket::close();
|
||||||
|
while (!mRequests.empty()) {
|
||||||
|
auto req = mRequests.front();
|
||||||
|
req->detach();
|
||||||
|
mRequests.pop_front();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AcceptConnection::writeEvent(Handler* h)
|
||||||
|
{
|
||||||
|
FuncCallTimer();
|
||||||
|
IOVec bufs[Const::MaxIOVecLen];
|
||||||
|
bool finished = true;
|
||||||
|
while (true) {
|
||||||
|
int len = fill(h, bufs, Const::MaxIOVecLen);
|
||||||
|
if (len == 0) {
|
||||||
|
finished = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
finished = write(h, bufs, len);
|
||||||
|
if (!finished || len < Const::MaxIOVecLen) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return finished;
|
||||||
|
}
|
||||||
|
|
||||||
|
int AcceptConnection::fill(Handler* h, IOVec* bufs, int len)
|
||||||
|
{
|
||||||
|
FuncCallTimer();
|
||||||
|
int cnt = 0;
|
||||||
|
Request* req = mRequests.empty() ? nullptr : mRequests.front();
|
||||||
|
while (req && len > 0) {
|
||||||
|
if (!req->isDone()) {
|
||||||
|
if (!isBlockRequest()) {
|
||||||
|
while (req) {
|
||||||
|
if (req->isDelivered()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
h->handleRequest(req);
|
||||||
|
req = mRequests.next(req);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
auto res = req->getResponse();
|
||||||
|
Request* next = mRequests.next(req);
|
||||||
|
if (res) {
|
||||||
|
logDebug("h %d c %s %d req %ld fill res %ld",
|
||||||
|
h->id(), peer(), fd(), req->id(), res->id());
|
||||||
|
int n = res->fill(bufs, len, req);
|
||||||
|
bufs += n;
|
||||||
|
cnt += n;
|
||||||
|
len -= n;
|
||||||
|
} else if (cnt == 0) {//req no res and req is first in list
|
||||||
|
mRequests.pop_front();
|
||||||
|
logDebug("h %d c %s %d req %ld in front is done and no res",
|
||||||
|
h->id(), peer(), fd(), req->id());
|
||||||
|
}
|
||||||
|
req = next;
|
||||||
|
}
|
||||||
|
return cnt;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AcceptConnection::write(Handler* h, IOVec* bufs, int len)
|
||||||
|
{
|
||||||
|
FuncCallTimer();
|
||||||
|
logDebug("h %d c %s %d writev %d",
|
||||||
|
h->id(), peer(), fd(), len);
|
||||||
|
struct iovec iov[Const::MaxIOVecLen];
|
||||||
|
for (int i = 0; i < len; ++i) {
|
||||||
|
iov[i].iov_base = bufs[i].dat;
|
||||||
|
iov[i].iov_len = bufs[i].len;
|
||||||
|
}
|
||||||
|
int num = writev(iov, len);
|
||||||
|
if (num < 0) {
|
||||||
|
logDebug("h %d c %s %d writev %d fail %s",
|
||||||
|
h->id(), peer(), fd(), len, StrError());
|
||||||
|
return false;
|
||||||
|
} else if (num == 0) {
|
||||||
|
return len == 0;
|
||||||
|
}
|
||||||
|
h->stats().sendClientBytes += num;
|
||||||
|
IOVec* vec = nullptr;
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < len && num > 0; ++i) {
|
||||||
|
vec = bufs + i;
|
||||||
|
int n = vec->len;
|
||||||
|
vec->seg->seek(vec->buf, vec->pos, n <= num ? n : num);
|
||||||
|
if (vec->req && n <= num) {
|
||||||
|
while (!mRequests.empty()) {
|
||||||
|
auto req = mRequests.pop_front();
|
||||||
|
if (vec->req == req) {
|
||||||
|
logVerb("h %d c %s %d req %ld res sent",
|
||||||
|
h->id(), peer(), fd(), req->id());
|
||||||
|
req->clear();
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
logVerb("h %d c %s %d req %ld no res",
|
||||||
|
h->id(), peer(), fd(), req->id());
|
||||||
|
req->clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
num -= n;
|
||||||
|
}
|
||||||
|
return i == len && num == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AcceptConnection::readEvent(Handler* h)
|
||||||
|
{
|
||||||
|
FuncCallTimer();
|
||||||
|
while (true) {
|
||||||
|
auto buf = getBuffer(h, mParser.isIdle());
|
||||||
|
int pos = buf->length();
|
||||||
|
int len = buf->room();
|
||||||
|
int n = read(buf->tail(), len);
|
||||||
|
if (n > 0) {
|
||||||
|
buf->use(n);
|
||||||
|
h->stats().recvClientBytes += n;
|
||||||
|
parse(h, buf, pos);
|
||||||
|
}
|
||||||
|
if (n < len) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AcceptConnection::parse(Handler* h, Buffer* buf, int pos)
|
||||||
|
{
|
||||||
|
FuncCallTimer();
|
||||||
|
bool goOn = true;
|
||||||
|
bool split = h->proxy()->isSplitMultiKey() && !inTransaction();
|
||||||
|
while (pos < buf->length() && goOn) {
|
||||||
|
auto ret = mParser.parse(buf, pos, split);
|
||||||
|
switch (ret) {
|
||||||
|
case RequestParser::Normal:
|
||||||
|
goOn = false;
|
||||||
|
break;
|
||||||
|
case RequestParser::Partial:
|
||||||
|
{
|
||||||
|
Request* req = RequestAlloc::create(this);
|
||||||
|
mRequests.push_back(req);
|
||||||
|
if (!mReqLeader) {
|
||||||
|
mReqLeader = req;
|
||||||
|
}
|
||||||
|
req->set(mParser, mReqLeader);
|
||||||
|
h->handleRequest(req);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case RequestParser::Complete:
|
||||||
|
{
|
||||||
|
Request* req = RequestAlloc::create(this);
|
||||||
|
mRequests.push_back(req);
|
||||||
|
if (mReqLeader) {
|
||||||
|
req->set(mParser, mReqLeader);
|
||||||
|
mReqLeader = nullptr;
|
||||||
|
} else {
|
||||||
|
req->set(mParser);
|
||||||
|
}
|
||||||
|
h->handleRequest(req);
|
||||||
|
mParser.reset();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case RequestParser::CmdError:
|
||||||
|
{
|
||||||
|
Request* req = RequestAlloc::create(this);
|
||||||
|
mRequests.push_back(req);
|
||||||
|
if (inTransaction()) {
|
||||||
|
req->set(mParser);
|
||||||
|
h->handleRequest(req);
|
||||||
|
} else {
|
||||||
|
SegmentStr<RequestParser::MaxCmdLen> cmd(mParser.cmd());
|
||||||
|
ResponsePtr res = ResponseAlloc::create();
|
||||||
|
char err[1024];
|
||||||
|
int len = snprintf(err, sizeof(err), "unknown command '%.*s'",
|
||||||
|
cmd.length(), cmd.data());
|
||||||
|
res->setErr(err, len);
|
||||||
|
h->handleResponse(nullptr, req, res);
|
||||||
|
}
|
||||||
|
mParser.reset();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case RequestParser::ArgError:
|
||||||
|
{
|
||||||
|
Request* req = RequestAlloc::create(this);
|
||||||
|
mRequests.push_back(req);
|
||||||
|
if (inTransaction()) {
|
||||||
|
req->set(mParser);
|
||||||
|
h->handleRequest(req);
|
||||||
|
} else {
|
||||||
|
ResponsePtr res = ResponseAlloc::create();
|
||||||
|
char err[1024];
|
||||||
|
int len = snprintf(err, sizeof(err),
|
||||||
|
"wrong number of arguments for '%s' command",
|
||||||
|
Command::get(mParser.type()).name);
|
||||||
|
res->setErr(err, len);
|
||||||
|
h->handleResponse(nullptr, req, res);
|
||||||
|
}
|
||||||
|
mParser.reset();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
setStatus(ParseError);
|
||||||
|
goOn = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AcceptConnection::send(Handler* h, Request* req, Response* res)
|
||||||
|
{
|
||||||
|
FuncCallTimer();
|
||||||
|
if (mRequests.front()->isDone()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
102
src/AcceptConnection.h
Normal file
102
src/AcceptConnection.h
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_ACCEPT_CONNECTION_H_
|
||||||
|
#define _PREDIXY_ACCEPT_CONNECTION_H_
|
||||||
|
|
||||||
|
#include "Predixy.h"
|
||||||
|
#include "AcceptSocket.h"
|
||||||
|
#include "Connection.h"
|
||||||
|
#include "Transaction.h"
|
||||||
|
#include "Subscribe.h"
|
||||||
|
#include "RequestParser.h"
|
||||||
|
#include "Request.h"
|
||||||
|
#include "Response.h"
|
||||||
|
|
||||||
|
class AcceptConnection :
|
||||||
|
public AcceptSocket,
|
||||||
|
public Connection,
|
||||||
|
public Transaction,
|
||||||
|
public Subscribe,
|
||||||
|
public ListNode<AcceptConnection, SharePtr<AcceptConnection>>,
|
||||||
|
public DequeNode<AcceptConnection, SharePtr<AcceptConnection>>,
|
||||||
|
public RefCntObj<AcceptConnection>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
typedef AcceptConnection Value;
|
||||||
|
typedef ListNode<AcceptConnection, SharePtr<AcceptConnection>> ListNodeType;
|
||||||
|
typedef DequeNode<AcceptConnection, SharePtr<AcceptConnection>> DequeNodeType;
|
||||||
|
public:
|
||||||
|
AcceptConnection(int fd, sockaddr* addr, socklen_t len);
|
||||||
|
~AcceptConnection();
|
||||||
|
void close();
|
||||||
|
bool writeEvent(Handler* h);
|
||||||
|
void readEvent(Handler* h);
|
||||||
|
bool send(Handler* h, Request* req, Response* res);
|
||||||
|
long lastActiveTime() const
|
||||||
|
{
|
||||||
|
return mLastActiveTime;
|
||||||
|
}
|
||||||
|
void setLastActiveTime(long v)
|
||||||
|
{
|
||||||
|
mLastActiveTime = v;
|
||||||
|
}
|
||||||
|
bool empty() const
|
||||||
|
{
|
||||||
|
return mRequests.empty();
|
||||||
|
}
|
||||||
|
ConnectConnection* connectConnection() const
|
||||||
|
{
|
||||||
|
return mConnectConnection;
|
||||||
|
}
|
||||||
|
void attachConnectConnection(ConnectConnection* s)
|
||||||
|
{
|
||||||
|
mConnectConnection = s;
|
||||||
|
}
|
||||||
|
void detachConnectConnection()
|
||||||
|
{
|
||||||
|
mConnectConnection = nullptr;
|
||||||
|
}
|
||||||
|
const Auth* auth() const
|
||||||
|
{
|
||||||
|
return mAuth;
|
||||||
|
}
|
||||||
|
void setAuth(const Auth* auth)
|
||||||
|
{
|
||||||
|
mAuth = auth;
|
||||||
|
}
|
||||||
|
bool isBlockRequest() const
|
||||||
|
{
|
||||||
|
return mBlockRequest;
|
||||||
|
}
|
||||||
|
void setBlockRequest(bool v)
|
||||||
|
{
|
||||||
|
mBlockRequest = v;
|
||||||
|
}
|
||||||
|
void append(Request* req)
|
||||||
|
{
|
||||||
|
mRequests.push_back(req);
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
void parse(Handler* h, Buffer* buf, int pos);
|
||||||
|
int fill(Handler* h, IOVec* bufs, int len);
|
||||||
|
bool write(Handler* h, IOVec* bufs, int len);
|
||||||
|
private:
|
||||||
|
RequestParser mParser;
|
||||||
|
RecvRequestList mRequests;
|
||||||
|
RequestPtr mReqLeader;
|
||||||
|
ResponseList mResponses;
|
||||||
|
const Auth* mAuth;
|
||||||
|
ConnectConnection* mConnectConnection;
|
||||||
|
long mLastActiveTime; //steady us
|
||||||
|
bool mBlockRequest;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef List<AcceptConnection> AcceptConnectionList;
|
||||||
|
typedef Deque<AcceptConnection> AcceptConnectionDeque;
|
||||||
|
typedef Alloc<AcceptConnection, Const::AcceptConnectionAllocCacheSize> AcceptConnectionAlloc;
|
||||||
|
|
||||||
|
#endif
|
||||||
41
src/AcceptSocket.cpp
Normal file
41
src/AcceptSocket.cpp
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "AcceptSocket.h"
|
||||||
|
|
||||||
|
|
||||||
|
AcceptSocket::AcceptSocket(int fd, sockaddr* addr, socklen_t len):
|
||||||
|
Socket(fd)
|
||||||
|
{
|
||||||
|
mClassType = AcceptType;
|
||||||
|
mPeer[0] = '\0';
|
||||||
|
switch (addr->sa_family) {
|
||||||
|
case AF_INET:
|
||||||
|
{
|
||||||
|
char host[INET_ADDRSTRLEN];
|
||||||
|
sockaddr_in* in = (sockaddr_in*)addr;
|
||||||
|
inet_ntop(AF_INET, (void*)&in->sin_addr, host, sizeof(host));
|
||||||
|
int port = ntohs(in->sin_port);
|
||||||
|
snprintf(mPeer, sizeof(mPeer), "%s:%d", host, port);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case AF_INET6:
|
||||||
|
{
|
||||||
|
char host[INET6_ADDRSTRLEN];
|
||||||
|
sockaddr_in6* in = (sockaddr_in6*)addr;
|
||||||
|
inet_ntop(AF_INET6, (void*)&in->sin6_addr, host, sizeof(host));
|
||||||
|
int port = ntohs(in->sin6_port);
|
||||||
|
snprintf(mPeer, sizeof(mPeer), "%s:%d", host, port);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case AF_UNIX:
|
||||||
|
snprintf(mPeer, sizeof(mPeer), "unix");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
snprintf(mPeer, sizeof(mPeer), "unknown");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
25
src/AcceptSocket.h
Normal file
25
src/AcceptSocket.h
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_ACCEPT_SOCKET_H_
|
||||||
|
#define _PREDIXY_ACCEPT_SOCKET_H_
|
||||||
|
|
||||||
|
#include "Socket.h"
|
||||||
|
|
||||||
|
class AcceptSocket : public Socket
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AcceptSocket(int fd, sockaddr* addr, socklen_t len);
|
||||||
|
const char* peer() const
|
||||||
|
{
|
||||||
|
return mPeer;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
char mPeer[32];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
10
src/Alloc.cpp
Normal file
10
src/Alloc.cpp
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Alloc.h"
|
||||||
|
|
||||||
|
long AllocBase::MaxMemory(0);
|
||||||
|
AtomicLong AllocBase::UsedMemory(0);
|
||||||
250
src/Alloc.h
Normal file
250
src/Alloc.h
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_ALLOC_H_
|
||||||
|
#define _PREDIXY_ALLOC_H_
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <thread>
|
||||||
|
#include "Sync.h"
|
||||||
|
#include "Exception.h"
|
||||||
|
#include "Util.h"
|
||||||
|
#include "Logger.h"
|
||||||
|
#include "Timer.h"
|
||||||
|
|
||||||
|
class AllocBase
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DefException(MemLimit);
|
||||||
|
public:
|
||||||
|
static void setMaxMemory(long m)
|
||||||
|
{
|
||||||
|
MaxMemory = m;
|
||||||
|
}
|
||||||
|
static long getMaxMemory()
|
||||||
|
{
|
||||||
|
return MaxMemory;
|
||||||
|
}
|
||||||
|
static long getUsedMemory()
|
||||||
|
{
|
||||||
|
return UsedMemory;
|
||||||
|
}
|
||||||
|
protected:
|
||||||
|
static long MaxMemory;
|
||||||
|
static AtomicLong UsedMemory;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
inline int allocSize()
|
||||||
|
{
|
||||||
|
return sizeof(T);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T, int CacheSize = 64>
|
||||||
|
class Alloc : public AllocBase
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
template<class... Targs>
|
||||||
|
static T* create(Targs&&... args)
|
||||||
|
{
|
||||||
|
FuncCallTimer();
|
||||||
|
T* obj = nullptr;
|
||||||
|
if (Size > 0) {
|
||||||
|
obj = Free[--Size];
|
||||||
|
}
|
||||||
|
if (obj) {
|
||||||
|
try {
|
||||||
|
new ((void*)obj) T(args...);
|
||||||
|
} catch (...) {
|
||||||
|
bool del = true;
|
||||||
|
if (Size < CacheSize) {
|
||||||
|
Free[Size++] = obj;
|
||||||
|
del = false;
|
||||||
|
}
|
||||||
|
if (del) {
|
||||||
|
UsedMemory -= allocSize<T>();
|
||||||
|
::operator delete((void*)obj);
|
||||||
|
}
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
logVerb("alloc create object with old memory %d @%p", allocSize<T>(), obj);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
UsedMemory += allocSize<T>();
|
||||||
|
if (MaxMemory == 0 || UsedMemory <= MaxMemory) {
|
||||||
|
void* p = ::operator new(allocSize<T>());
|
||||||
|
if (p) {
|
||||||
|
try {
|
||||||
|
obj = new (p) T(args...);
|
||||||
|
logVerb("alloc create object with new memory %d @%p", allocSize<T>(), obj);
|
||||||
|
return obj;
|
||||||
|
} catch (...) {
|
||||||
|
UsedMemory -= allocSize<T>();
|
||||||
|
::operator delete(p);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
UsedMemory -= allocSize<T>();
|
||||||
|
Throw(MemLimit, "system memory alloc fail");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
UsedMemory -= allocSize<T>();
|
||||||
|
Throw(MemLimit, "maxmemory used");
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
static void destroy(T* obj)
|
||||||
|
{
|
||||||
|
FuncCallTimer();
|
||||||
|
bool del = true;
|
||||||
|
obj->~T();
|
||||||
|
if (Size < CacheSize) {
|
||||||
|
Free[Size++] = obj;
|
||||||
|
del = false;
|
||||||
|
}
|
||||||
|
if (del) {
|
||||||
|
UsedMemory -= allocSize<T>();
|
||||||
|
::operator delete((void*)obj);
|
||||||
|
}
|
||||||
|
logVerb("alloc destroy object with size %d @%p delete %s", (int)allocSize<T>(), obj, del ? "true" : "false");
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
thread_local static T* Free[CacheSize];
|
||||||
|
thread_local static int Size;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T, int CacheSize>
|
||||||
|
thread_local T* Alloc<T, CacheSize>::Free[CacheSize];
|
||||||
|
template<class T, int CacheSize>
|
||||||
|
thread_local int Alloc<T, CacheSize>::Size = 0;
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
class RefCntObj
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
RefCntObj():
|
||||||
|
mCnt(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
RefCntObj(const RefCntObj&) = delete;
|
||||||
|
RefCntObj& operator=(const RefCntObj&) = delete;
|
||||||
|
int count() const
|
||||||
|
{
|
||||||
|
return mCnt;
|
||||||
|
}
|
||||||
|
void ref()
|
||||||
|
{
|
||||||
|
++mCnt;
|
||||||
|
}
|
||||||
|
void unref()
|
||||||
|
{
|
||||||
|
int n = --mCnt;
|
||||||
|
if (n == 0) {
|
||||||
|
Alloc<T>::destroy(static_cast<T*>(this));
|
||||||
|
} else if (n < 0) {
|
||||||
|
logError("unref object %p with cnt %d", this, n);
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
protected:
|
||||||
|
~RefCntObj()
|
||||||
|
{
|
||||||
|
mCnt = 0;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
AtomicInt mCnt;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
class SharePtr
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SharePtr():
|
||||||
|
mObj(nullptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
SharePtr(T* obj):
|
||||||
|
mObj(obj)
|
||||||
|
{
|
||||||
|
if (obj) {
|
||||||
|
obj->ref();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SharePtr(const SharePtr<T>& sp):
|
||||||
|
mObj(sp.mObj)
|
||||||
|
{
|
||||||
|
if (mObj) {
|
||||||
|
mObj->ref();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SharePtr(SharePtr<T>&& sp):
|
||||||
|
mObj(sp.mObj)
|
||||||
|
{
|
||||||
|
sp.mObj = nullptr;
|
||||||
|
}
|
||||||
|
~SharePtr()
|
||||||
|
{
|
||||||
|
if (mObj) {
|
||||||
|
mObj->unref();
|
||||||
|
mObj = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
operator T*() const
|
||||||
|
{
|
||||||
|
return mObj;
|
||||||
|
}
|
||||||
|
SharePtr& operator=(const SharePtr& sp)
|
||||||
|
{
|
||||||
|
if (this != &sp) {
|
||||||
|
T* obj = mObj;
|
||||||
|
mObj = sp.mObj;
|
||||||
|
if (mObj) {
|
||||||
|
mObj->ref();
|
||||||
|
}
|
||||||
|
if (obj) {
|
||||||
|
obj->unref();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
T& operator*()
|
||||||
|
{
|
||||||
|
return *mObj;
|
||||||
|
}
|
||||||
|
const T& operator*() const
|
||||||
|
{
|
||||||
|
return *mObj;
|
||||||
|
}
|
||||||
|
T* operator->()
|
||||||
|
{
|
||||||
|
return mObj;
|
||||||
|
}
|
||||||
|
const T* operator->() const
|
||||||
|
{
|
||||||
|
return mObj;
|
||||||
|
}
|
||||||
|
bool operator!() const
|
||||||
|
{
|
||||||
|
return !mObj;
|
||||||
|
}
|
||||||
|
operator bool() const
|
||||||
|
{
|
||||||
|
return mObj;
|
||||||
|
}
|
||||||
|
bool operator==(const SharePtr& sp) const
|
||||||
|
{
|
||||||
|
return mObj == sp.mObj;
|
||||||
|
}
|
||||||
|
bool operator!=(const SharePtr& sp) const
|
||||||
|
{
|
||||||
|
return mObj != sp.mObj;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
T* mObj;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
106
src/Auth.cpp
Normal file
106
src/Auth.cpp
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include "Auth.h"
|
||||||
|
#include "Conf.h"
|
||||||
|
#include "Request.h"
|
||||||
|
|
||||||
|
|
||||||
|
Auth::Auth():
|
||||||
|
mMode(Command::Read|Command::Write|Command::Admin),
|
||||||
|
mReadKeyPrefix(nullptr),
|
||||||
|
mWriteKeyPrefix(nullptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Auth::Auth(const AuthConf& conf):
|
||||||
|
mPassword(conf.password.c_str(), conf.password.size()),
|
||||||
|
mMode(conf.mode),
|
||||||
|
mReadKeyPrefix(nullptr),
|
||||||
|
mWriteKeyPrefix(nullptr)
|
||||||
|
{
|
||||||
|
if (!conf.readKeyPrefix.empty()) {
|
||||||
|
mReadKeyPrefix = new KeyPrefixSet(conf.readKeyPrefix.begin(), conf.readKeyPrefix.end());
|
||||||
|
}
|
||||||
|
if (!conf.writeKeyPrefix.empty()) {
|
||||||
|
mWriteKeyPrefix = new KeyPrefixSet(conf.writeKeyPrefix.begin(), conf.writeKeyPrefix.end());
|
||||||
|
}
|
||||||
|
if ((!mReadKeyPrefix || !mWriteKeyPrefix) && !conf.keyPrefix.empty()) {
|
||||||
|
auto kp = new KeyPrefixSet(conf.keyPrefix.begin(), conf.keyPrefix.end());
|
||||||
|
if (!mReadKeyPrefix) {
|
||||||
|
mReadKeyPrefix = kp;
|
||||||
|
}
|
||||||
|
if (!mWriteKeyPrefix) {
|
||||||
|
mWriteKeyPrefix = kp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Auth::~Auth()
|
||||||
|
{
|
||||||
|
if (mReadKeyPrefix) {
|
||||||
|
delete mReadKeyPrefix;
|
||||||
|
}
|
||||||
|
if (mWriteKeyPrefix && mWriteKeyPrefix != mReadKeyPrefix) {
|
||||||
|
delete mWriteKeyPrefix;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Auth::permission(Request* req, const String& key) const
|
||||||
|
{
|
||||||
|
auto& cmd = Command::get(req->type());
|
||||||
|
if (!(mMode & cmd.mode)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const KeyPrefixSet* kp = nullptr;
|
||||||
|
if (cmd.mode & Command::Read) {
|
||||||
|
kp = mReadKeyPrefix;
|
||||||
|
} else if (cmd.mode & Command::Write) {
|
||||||
|
kp = mWriteKeyPrefix;
|
||||||
|
}
|
||||||
|
if (kp) {
|
||||||
|
auto it = kp->upper_bound(key);
|
||||||
|
const String* p = nullptr;
|
||||||
|
if (it == kp->end()) {
|
||||||
|
p = &(*kp->rbegin());
|
||||||
|
} else if (it != kp->begin()) {
|
||||||
|
p = &(*--it);
|
||||||
|
}
|
||||||
|
return p && key.hasPrefix(*p);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Auth Authority::DefaultAuth;
|
||||||
|
|
||||||
|
Authority::Authority():
|
||||||
|
mDefault(&DefaultAuth)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Authority::~Authority()
|
||||||
|
{
|
||||||
|
for (auto& i : mAuthMap) {
|
||||||
|
delete i.second;
|
||||||
|
}
|
||||||
|
mAuthMap.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Authority::add(const AuthConf& ac)
|
||||||
|
{
|
||||||
|
Auth* a = new Auth(ac);
|
||||||
|
auto it = mAuthMap.find(a->password());
|
||||||
|
if (it != mAuthMap.end()) {
|
||||||
|
Auth* p = it->second;
|
||||||
|
mAuthMap.erase(it);
|
||||||
|
delete p;
|
||||||
|
}
|
||||||
|
mAuthMap[a->password()] = a;
|
||||||
|
if (a->password().empty()) {
|
||||||
|
mDefault = a;
|
||||||
|
}
|
||||||
|
}
|
||||||
59
src/Auth.h
Normal file
59
src/Auth.h
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_AUTH_H_
|
||||||
|
#define _PREDIXY_AUTH_H_
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <set>
|
||||||
|
#include <vector>
|
||||||
|
#include "Predixy.h"
|
||||||
|
|
||||||
|
class Auth
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Auth();
|
||||||
|
Auth(const AuthConf& conf);
|
||||||
|
~Auth();
|
||||||
|
const String& password() const
|
||||||
|
{
|
||||||
|
return mPassword;
|
||||||
|
}
|
||||||
|
bool permission(Request* req, const String& key) const;
|
||||||
|
private:
|
||||||
|
String mPassword;
|
||||||
|
int mMode;
|
||||||
|
typedef std::set<String> KeyPrefixSet;
|
||||||
|
KeyPrefixSet* mReadKeyPrefix;
|
||||||
|
KeyPrefixSet* mWriteKeyPrefix;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Authority
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Authority();
|
||||||
|
~Authority();
|
||||||
|
bool hasAuth() const
|
||||||
|
{
|
||||||
|
return !mAuthMap.empty();
|
||||||
|
}
|
||||||
|
Auth* get(const String& pd) const
|
||||||
|
{
|
||||||
|
auto it = mAuthMap.find(pd);
|
||||||
|
return it == mAuthMap.end() ? nullptr : it->second;
|
||||||
|
}
|
||||||
|
Auth* getDefault() const
|
||||||
|
{
|
||||||
|
return mDefault;
|
||||||
|
}
|
||||||
|
void add(const AuthConf& ac);
|
||||||
|
private:
|
||||||
|
std::map<String, Auth*> mAuthMap;
|
||||||
|
Auth* mDefault;
|
||||||
|
static Auth DefaultAuth;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
42
src/Backtrace.h
Normal file
42
src/Backtrace.h
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_BACKTRACE_H_
|
||||||
|
#define _PREDIXY_BACKTRACE_H_
|
||||||
|
|
||||||
|
#include "Logger.h"
|
||||||
|
|
||||||
|
#if _PREDIXY_BACKTRACE_
|
||||||
|
|
||||||
|
#include <execinfo.h>
|
||||||
|
inline void traceInfo()
|
||||||
|
{
|
||||||
|
#define Size 128
|
||||||
|
logError("predixy backtrace");
|
||||||
|
void* buf[Size];
|
||||||
|
int num = ::backtrace(buf, Size);
|
||||||
|
int fd = -1;
|
||||||
|
if (Logger::gInst) {
|
||||||
|
fd = Logger::gInst->logFileFd();
|
||||||
|
if (fd >= 0) {
|
||||||
|
backtrace_symbols_fd(buf, num, fd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fd != STDOUT_FILENO) {
|
||||||
|
backtrace_symbols_fd(buf, num, STDOUT_FILENO);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
inline void traceInfo()
|
||||||
|
{
|
||||||
|
logError("predixy backtrace, but current system unspport backtrace");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
381
src/Buffer.cpp
Normal file
381
src/Buffer.cpp
Normal file
@ -0,0 +1,381 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "String.h"
|
||||||
|
#include "Logger.h"
|
||||||
|
#include "Buffer.h"
|
||||||
|
#include "IOVec.h"
|
||||||
|
|
||||||
|
int Buffer::Size = 4096 - sizeof(Buffer);
|
||||||
|
|
||||||
|
Buffer::Buffer():
|
||||||
|
mLen(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Buffer::Buffer(const Buffer& oth):
|
||||||
|
mLen(oth.mLen)
|
||||||
|
{
|
||||||
|
memcpy(mDat, oth.mDat, mLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
Buffer::~Buffer()
|
||||||
|
{
|
||||||
|
SharePtr<Buffer> p = ListNode<Buffer, SharePtr<Buffer>>::next();
|
||||||
|
ListNode<Buffer, SharePtr<Buffer>>::reset();
|
||||||
|
while (p) {
|
||||||
|
if (p->count() == 1) {
|
||||||
|
p = p->next();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Buffer& Buffer::operator=(const Buffer& oth)
|
||||||
|
{
|
||||||
|
if (this != &oth) {
|
||||||
|
mLen = oth.mLen;
|
||||||
|
memcpy(mDat, oth.mDat, mLen);
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Buffer* Buffer::append(const char* str)
|
||||||
|
{
|
||||||
|
return append(str, strlen(str));
|
||||||
|
}
|
||||||
|
|
||||||
|
Buffer* Buffer::append(const char* dat, int len)
|
||||||
|
{
|
||||||
|
if (len <= 0) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
Buffer* buf = this;
|
||||||
|
while (len > 0) {
|
||||||
|
if (len <= buf->room()) {
|
||||||
|
memcpy(buf->tail(), dat, len);
|
||||||
|
buf->use(len);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
int n = buf->room();
|
||||||
|
memcpy(buf->tail(), dat, n);
|
||||||
|
buf->use(n);
|
||||||
|
dat += n;
|
||||||
|
len -= n;
|
||||||
|
Buffer* nbuf = BufferAlloc::create();
|
||||||
|
buf->concat(nbuf);
|
||||||
|
buf = nbuf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
Buffer* Buffer::fappend(const char* fmt, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap, fmt);
|
||||||
|
char* dat = tail();
|
||||||
|
int len = room();
|
||||||
|
int n = vsnprintf(dat, len, fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
if (n >= len) {
|
||||||
|
if (n > MaxBufFmtAppendLen) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
char buf[MaxBufFmtAppendLen];
|
||||||
|
va_list aq;
|
||||||
|
va_start(aq, fmt);
|
||||||
|
n = vsnprintf(buf, sizeof(buf), fmt, aq);
|
||||||
|
va_end(aq);
|
||||||
|
return append(buf, n);
|
||||||
|
}
|
||||||
|
use(n);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Buffer* Buffer::vfappend(const char* fmt, va_list ap)
|
||||||
|
{
|
||||||
|
char* dat = tail();
|
||||||
|
int len = room();
|
||||||
|
va_list aq;
|
||||||
|
va_copy(aq, ap);
|
||||||
|
int n = vsnprintf(dat, len, fmt, ap);
|
||||||
|
if (n >= len) {
|
||||||
|
if (n > MaxBufFmtAppendLen) {
|
||||||
|
va_end(aq);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
char buf[MaxBufFmtAppendLen];
|
||||||
|
n = vsnprintf(buf, sizeof(buf), fmt, aq);
|
||||||
|
va_end(aq);
|
||||||
|
return append(buf, n);
|
||||||
|
}
|
||||||
|
use(n);
|
||||||
|
va_end(aq);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Segment::Segment()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Segment::Segment(BufferPtr beginBuf, int beginPos, BufferPtr endBuf, int endPos):
|
||||||
|
mBegin(beginBuf, beginPos),
|
||||||
|
mCur(beginBuf, beginPos),
|
||||||
|
mEnd(endBuf, endPos)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Segment::Segment(const Segment& oth):
|
||||||
|
mBegin(oth.mBegin),
|
||||||
|
mCur(oth.mCur),
|
||||||
|
mEnd(oth.mEnd)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Segment::Segment(Segment&& oth):
|
||||||
|
mBegin(oth.mBegin),
|
||||||
|
mCur(oth.mCur),
|
||||||
|
mEnd(oth.mEnd)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Segment::~Segment()
|
||||||
|
{
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
Segment& Segment::operator=(const Segment& oth)
|
||||||
|
{
|
||||||
|
if (this != &oth) {
|
||||||
|
mBegin = oth.mBegin;
|
||||||
|
mCur = oth.mCur;
|
||||||
|
mEnd = oth.mEnd;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Segment::clear()
|
||||||
|
{
|
||||||
|
mBegin.buf = nullptr;
|
||||||
|
mBegin.pos = 0;
|
||||||
|
mCur.buf = nullptr;
|
||||||
|
mCur.pos = 0;
|
||||||
|
mEnd.buf = nullptr;
|
||||||
|
mEnd.pos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Buffer* Segment::set(Buffer* buf, const char* dat, int len)
|
||||||
|
{
|
||||||
|
if (!buf) {
|
||||||
|
buf = BufferAlloc::create();
|
||||||
|
}
|
||||||
|
int pos = buf->length();
|
||||||
|
Buffer* nbuf = buf->append(dat, len);
|
||||||
|
mBegin.buf = buf;
|
||||||
|
mBegin.pos = pos;
|
||||||
|
mCur.buf = buf;
|
||||||
|
mCur.pos = pos;
|
||||||
|
mEnd.buf = nbuf;
|
||||||
|
mEnd.pos = nbuf->length();
|
||||||
|
return nbuf;
|
||||||
|
}
|
||||||
|
|
||||||
|
Buffer* Segment::fset(Buffer* buf, const char* fmt, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap, fmt);
|
||||||
|
try {
|
||||||
|
return vfset(buf, fmt, ap);
|
||||||
|
} catch (...) {
|
||||||
|
va_end(ap);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Buffer* Segment::vfset(Buffer* buf, const char* fmt, va_list ap)
|
||||||
|
{
|
||||||
|
if (!buf) {
|
||||||
|
buf = BufferAlloc::create();
|
||||||
|
}
|
||||||
|
int pos = buf->length();
|
||||||
|
Buffer* nbuf = buf->vfappend(fmt, ap);
|
||||||
|
mBegin.buf = buf;
|
||||||
|
mBegin.pos = pos;
|
||||||
|
mCur = mBegin;
|
||||||
|
mEnd.buf = nbuf;
|
||||||
|
mEnd.pos = nbuf->length();
|
||||||
|
return nbuf;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Segment::length() const
|
||||||
|
{
|
||||||
|
Buffer* buf = mBegin.buf;
|
||||||
|
int pos = mBegin.pos;
|
||||||
|
int len = 0;
|
||||||
|
while (buf) {
|
||||||
|
if (buf == mEnd.buf) {
|
||||||
|
len += mEnd.pos - pos;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
len += buf->length() - pos;
|
||||||
|
}
|
||||||
|
buf = buf->next();
|
||||||
|
pos = 0;
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Segment::get(const char*& dat, int& len) const
|
||||||
|
{
|
||||||
|
if (mCur.buf == mEnd.buf && mCur.pos >= mEnd.pos) {
|
||||||
|
dat = nullptr;
|
||||||
|
len = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
dat = mCur.buf->data() + mCur.pos;
|
||||||
|
len = (mCur.buf == mEnd.buf ? mEnd.pos : mCur.buf->length()) - mCur.pos;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Segment::fill(IOVec* vecs, int len, bool& all) const
|
||||||
|
{
|
||||||
|
int n = 0;
|
||||||
|
BufferPos p = mCur;
|
||||||
|
all = false;
|
||||||
|
while (n < len && p != mEnd) {
|
||||||
|
vecs[n].dat = p.buf->data() + p.pos;
|
||||||
|
vecs[n].len = (p.buf == mEnd.buf ? mEnd.pos : p.buf->length()) - p.pos;
|
||||||
|
vecs[n].seg = const_cast<Segment*>(this);
|
||||||
|
vecs[n].buf = p.buf;
|
||||||
|
vecs[n].pos = p.pos;
|
||||||
|
vecs[n].req = nullptr;
|
||||||
|
++n;
|
||||||
|
if (p.buf == mEnd.buf) {
|
||||||
|
all = true;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
p.buf = p.buf->next();
|
||||||
|
p.pos = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (n < len && n == 0) {
|
||||||
|
all = true;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
//caller must sure cnt < segment length
|
||||||
|
void Segment::cut(int cnt)
|
||||||
|
{
|
||||||
|
if (cnt > 0) {
|
||||||
|
while (cnt > 0 && mBegin.buf) {
|
||||||
|
BufferPtr buf = mBegin.buf;
|
||||||
|
int len = (buf == mEnd.buf ? mEnd.pos : buf->length()) - mBegin.pos;
|
||||||
|
if (len <= cnt) {
|
||||||
|
if (buf == mEnd.buf) {
|
||||||
|
mBegin.buf = nullptr;
|
||||||
|
mBegin.pos = 0;
|
||||||
|
mCur = mEnd = mBegin;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
cnt -= len;
|
||||||
|
mBegin.buf = buf->next();
|
||||||
|
mBegin.pos = 0;
|
||||||
|
if (mCur.buf == buf) {
|
||||||
|
mCur = mBegin;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mBegin.pos += cnt;
|
||||||
|
if (mCur.buf == mBegin.buf && mCur.pos < mBegin.pos) {
|
||||||
|
mCur.pos = mBegin.pos;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//caller must sure cnt < segment length
|
||||||
|
void Segment::use(int cnt)
|
||||||
|
{
|
||||||
|
while (cnt > 0) {
|
||||||
|
if (mCur.buf == mEnd.buf) {
|
||||||
|
mCur.pos += cnt;
|
||||||
|
if (mCur.pos > mEnd.pos) {
|
||||||
|
mCur.pos = mEnd.pos;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
int n = mCur.buf->length() - mCur.pos;
|
||||||
|
if (n <= cnt) {
|
||||||
|
mCur.buf = mCur.buf->next();
|
||||||
|
mCur.pos = 0;
|
||||||
|
} else {
|
||||||
|
mCur.pos += cnt;
|
||||||
|
}
|
||||||
|
cnt -= n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Segment::seek(Buffer* buf, int pos, int cnt)
|
||||||
|
{
|
||||||
|
mCur.buf = buf;
|
||||||
|
mCur.pos = pos;
|
||||||
|
use(cnt);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Segment::dump(char* dat, int len) const
|
||||||
|
{
|
||||||
|
int cnt = 0;
|
||||||
|
BufferPtr buf = mBegin.buf;
|
||||||
|
int pos = mBegin.pos;
|
||||||
|
bool end = false;
|
||||||
|
while (buf && !end) {
|
||||||
|
int num;
|
||||||
|
if (buf == mEnd.buf) {
|
||||||
|
num = mEnd.pos - pos;
|
||||||
|
end = true;
|
||||||
|
} else {
|
||||||
|
num = buf->length() - pos;
|
||||||
|
}
|
||||||
|
if (dat && len > 0) {
|
||||||
|
memcpy(dat, buf->data() + pos, len > num ? num : len);
|
||||||
|
dat += num;
|
||||||
|
len -= num;
|
||||||
|
}
|
||||||
|
cnt += num;
|
||||||
|
buf = buf->next();
|
||||||
|
pos = 0;
|
||||||
|
}
|
||||||
|
return cnt;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Segment::hasPrefix(const char* prefix) const
|
||||||
|
{
|
||||||
|
const char* p = prefix;
|
||||||
|
BufferPtr buf = mBegin.buf;
|
||||||
|
int pos = mBegin.pos;
|
||||||
|
bool end = false;
|
||||||
|
while (buf && !end) {
|
||||||
|
int last = buf->length();
|
||||||
|
if (buf == mEnd.buf) {
|
||||||
|
last = mEnd.pos;
|
||||||
|
end = true;
|
||||||
|
}
|
||||||
|
while (pos < last && *p) {
|
||||||
|
if (buf->data()[pos++] != *p++) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf = buf->next();
|
||||||
|
pos = 0;
|
||||||
|
}
|
||||||
|
return *p == '\0';
|
||||||
|
}
|
||||||
233
src/Buffer.h
Normal file
233
src/Buffer.h
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_BUFFER_H_
|
||||||
|
#define _PREDIXY_BUFFER_H_
|
||||||
|
|
||||||
|
#include "Common.h"
|
||||||
|
#include "List.h"
|
||||||
|
#include "Alloc.h"
|
||||||
|
#include "Timer.h"
|
||||||
|
#include "String.h"
|
||||||
|
|
||||||
|
class Segment;
|
||||||
|
class Buffer;
|
||||||
|
struct IOVec;
|
||||||
|
typedef SharePtr<Buffer> BufferPtr;
|
||||||
|
|
||||||
|
class Buffer :
|
||||||
|
public ListNode<Buffer, SharePtr<Buffer>>,
|
||||||
|
public RefCntObj<Buffer>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static const int MaxBufFmtAppendLen = 8192;
|
||||||
|
public:
|
||||||
|
Buffer& operator=(const Buffer&);
|
||||||
|
Buffer* append(const char* str);
|
||||||
|
Buffer* append(const char* dat, int len);
|
||||||
|
Buffer* fappend(const char* fmt, ...);
|
||||||
|
Buffer* vfappend(const char* fmt, va_list ap);
|
||||||
|
void reset()
|
||||||
|
{
|
||||||
|
mLen = 0;
|
||||||
|
}
|
||||||
|
char* data()
|
||||||
|
{
|
||||||
|
return mDat;
|
||||||
|
}
|
||||||
|
const char* data() const
|
||||||
|
{
|
||||||
|
return mDat;
|
||||||
|
}
|
||||||
|
int length() const
|
||||||
|
{
|
||||||
|
return mLen;
|
||||||
|
}
|
||||||
|
char* tail()
|
||||||
|
{
|
||||||
|
return mDat + mLen;
|
||||||
|
}
|
||||||
|
const char* tail() const
|
||||||
|
{
|
||||||
|
return mDat + mLen;
|
||||||
|
}
|
||||||
|
int room() const
|
||||||
|
{
|
||||||
|
return Size - mLen;
|
||||||
|
}
|
||||||
|
void use(int cnt)
|
||||||
|
{
|
||||||
|
mLen += cnt;
|
||||||
|
}
|
||||||
|
bool full() const
|
||||||
|
{
|
||||||
|
return mLen >= Size;
|
||||||
|
}
|
||||||
|
bool empty() const
|
||||||
|
{
|
||||||
|
return mLen == 0;
|
||||||
|
}
|
||||||
|
static int getSize()
|
||||||
|
{
|
||||||
|
return Size;
|
||||||
|
}
|
||||||
|
static void setSize(int sz)
|
||||||
|
{
|
||||||
|
Size = sz;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
Buffer();
|
||||||
|
Buffer(const Buffer&);
|
||||||
|
~Buffer();
|
||||||
|
private:
|
||||||
|
int mLen;
|
||||||
|
char mDat[0];
|
||||||
|
|
||||||
|
static int Size;
|
||||||
|
friend class Segment;
|
||||||
|
friend class Alloc<Buffer, Const::BufferAllocCacheSize>;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef List<Buffer> BufferList;
|
||||||
|
template<>
|
||||||
|
inline int allocSize<Buffer>()
|
||||||
|
{
|
||||||
|
return Buffer::getSize() + sizeof(Buffer);
|
||||||
|
}
|
||||||
|
typedef Alloc<Buffer, Const::BufferAllocCacheSize> BufferAlloc;
|
||||||
|
|
||||||
|
struct BufferPos
|
||||||
|
{
|
||||||
|
BufferPtr buf;
|
||||||
|
int pos;
|
||||||
|
|
||||||
|
BufferPos():
|
||||||
|
pos(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
BufferPos(BufferPtr b, int p):
|
||||||
|
buf(b),
|
||||||
|
pos(p)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
BufferPos(const BufferPos& oth):
|
||||||
|
buf(oth.buf),
|
||||||
|
pos(oth.pos)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
BufferPos(BufferPos&& oth):
|
||||||
|
buf(oth.buf),
|
||||||
|
pos(oth.pos)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
BufferPos& operator=(const BufferPos& oth)
|
||||||
|
{
|
||||||
|
if (this != &oth) {
|
||||||
|
buf = oth.buf;
|
||||||
|
pos = oth.pos;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
bool operator==(const BufferPos& oth) const
|
||||||
|
{
|
||||||
|
return buf == oth.buf && pos == oth.pos;
|
||||||
|
}
|
||||||
|
bool operator!=(const BufferPos& oth) const
|
||||||
|
{
|
||||||
|
return !operator==(oth);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Segment
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Segment();
|
||||||
|
Segment(BufferPtr beginBuf, int beginPos, BufferPtr endBuf, int endPos);
|
||||||
|
Segment(const Segment& oth);
|
||||||
|
Segment(Segment&& oth);
|
||||||
|
~Segment();
|
||||||
|
Segment& operator=(const Segment& oth);
|
||||||
|
void clear();
|
||||||
|
Buffer* set(Buffer* buf, const char* dat, int len);
|
||||||
|
Buffer* fset(Buffer* buf, const char* fmt, ...);
|
||||||
|
Buffer* vfset(Buffer* buf, const char* fmt, va_list ap);
|
||||||
|
int length() const;
|
||||||
|
void cut(int cnt);
|
||||||
|
void use(int cnt);
|
||||||
|
void seek(Buffer* buf, int pos, int cnt);
|
||||||
|
bool get(const char*& dat, int& len) const;
|
||||||
|
int fill(IOVec* vecs, int len, bool& all) const;
|
||||||
|
int dump(char* dat, int len) const;
|
||||||
|
bool hasPrefix(const char* prefix) const;
|
||||||
|
Buffer* set(Buffer* buf, const char* str)
|
||||||
|
{
|
||||||
|
return set(buf, str, strlen(str));
|
||||||
|
}
|
||||||
|
bool across() const
|
||||||
|
{
|
||||||
|
return mBegin.buf != mEnd.buf;
|
||||||
|
}
|
||||||
|
BufferPos& begin()
|
||||||
|
{
|
||||||
|
return mBegin;
|
||||||
|
}
|
||||||
|
const BufferPos& begin() const
|
||||||
|
{
|
||||||
|
return mBegin;
|
||||||
|
}
|
||||||
|
BufferPos& end()
|
||||||
|
{
|
||||||
|
return mEnd;
|
||||||
|
}
|
||||||
|
const BufferPos& end () const
|
||||||
|
{
|
||||||
|
return mEnd;
|
||||||
|
}
|
||||||
|
void rewind()
|
||||||
|
{
|
||||||
|
mCur = mBegin;
|
||||||
|
}
|
||||||
|
bool empty() const
|
||||||
|
{
|
||||||
|
return mCur.buf == mEnd.buf && mCur.pos == mEnd.pos;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
BufferPos mBegin;
|
||||||
|
BufferPos mCur;
|
||||||
|
BufferPos mEnd;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef List<Segment> SegmentList;
|
||||||
|
|
||||||
|
template<int Size>
|
||||||
|
class SegmentStr : public String
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SegmentStr(const Segment& seg)
|
||||||
|
{
|
||||||
|
set(seg);
|
||||||
|
}
|
||||||
|
void set(const Segment& seg)
|
||||||
|
{
|
||||||
|
FuncCallTimer();
|
||||||
|
if (seg.across()) {
|
||||||
|
mDat = mBuf;
|
||||||
|
int len = seg.dump(mBuf, Size);
|
||||||
|
mLen = len < Size ? len : Size;
|
||||||
|
} else {
|
||||||
|
mDat = seg.begin().buf->data() + seg.begin().pos;
|
||||||
|
mLen = seg.end().pos - seg.begin().pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool complete() const
|
||||||
|
{
|
||||||
|
return mDat != mBuf || mLen < Size;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
char mBuf[Size];
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
180
src/ClusterNodesParser.cpp
Normal file
180
src/ClusterNodesParser.cpp
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include "ClusterNodesParser.h"
|
||||||
|
|
||||||
|
|
||||||
|
ClusterNodesParser::ClusterNodesParser():
|
||||||
|
mState(Idle),
|
||||||
|
mLen(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
ClusterNodesParser::~ClusterNodesParser()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClusterNodesParser::set(const Segment& s)
|
||||||
|
{
|
||||||
|
mNodes = s;
|
||||||
|
mNodes.rewind();
|
||||||
|
}
|
||||||
|
|
||||||
|
ClusterNodesParser::Status ClusterNodesParser::parse()
|
||||||
|
{
|
||||||
|
if (mState == SlotBegin || mState == SlotEnd) {
|
||||||
|
mSlotBegin = mSlotEnd = -1;
|
||||||
|
mState = SlotBegin;
|
||||||
|
}
|
||||||
|
const char* dat;
|
||||||
|
int len;
|
||||||
|
while (mNodes.get(dat, len)) {
|
||||||
|
int n = 0;
|
||||||
|
bool node = false;
|
||||||
|
while (n < len && !node) {
|
||||||
|
char c = dat[n++];
|
||||||
|
switch (mState) {
|
||||||
|
case Idle:
|
||||||
|
if (c == '$') {
|
||||||
|
mState = Len;
|
||||||
|
} else {
|
||||||
|
return Error;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Len:
|
||||||
|
if (c >= '0' && c <= '9') {
|
||||||
|
mLen = mLen * 10 + (c - '0');
|
||||||
|
} else if (c == '\r') {
|
||||||
|
mState = LenLF;
|
||||||
|
} else {
|
||||||
|
return Error;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case LenLF:
|
||||||
|
if (c == '\n') {
|
||||||
|
mState = NodeStart;
|
||||||
|
} else {
|
||||||
|
return Error;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NodeStart:
|
||||||
|
if (c == '\r') {
|
||||||
|
return Finished;
|
||||||
|
}
|
||||||
|
mState = Field;
|
||||||
|
mRole = Server::Unknown;
|
||||||
|
mFieldCnt = 0;
|
||||||
|
mNodeId.clear();
|
||||||
|
mAddr.clear();
|
||||||
|
mFlags.clear();
|
||||||
|
mMaster.clear();
|
||||||
|
mSlotBegin = -1;
|
||||||
|
mSlotEnd = -1;
|
||||||
|
//break;***NO break***, continue to Field
|
||||||
|
case Field:
|
||||||
|
if (c == ' ') {
|
||||||
|
if (mFieldCnt == Flags) {
|
||||||
|
if (strstr(mFlags, "master")) {
|
||||||
|
mRole = Server::Master;
|
||||||
|
} else if (strstr(mFlags, "slave")) {
|
||||||
|
mRole = Server::Slave;
|
||||||
|
}
|
||||||
|
if (strstr(mFlags, "noaddr")) {
|
||||||
|
mAddr.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (++mFieldCnt == Slot) {
|
||||||
|
mState = SlotBegin;
|
||||||
|
}
|
||||||
|
} else if (c == '\n') {
|
||||||
|
node = true;
|
||||||
|
mState = NodeStart;
|
||||||
|
} else {
|
||||||
|
switch (mFieldCnt) {
|
||||||
|
case NodeId:
|
||||||
|
if (!mNodeId.append(c)) {
|
||||||
|
return Error;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Addr:
|
||||||
|
if (!mAddr.append(c)) {
|
||||||
|
return Error;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Flags:
|
||||||
|
if (!mFlags.append(c)) {
|
||||||
|
return Error;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Master:
|
||||||
|
if (!mMaster.append(c)) {
|
||||||
|
return Error;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SlotBegin:
|
||||||
|
if (c >= '0' && c <= '9') {
|
||||||
|
if (mSlotBegin < 0) {
|
||||||
|
mSlotBegin = c - '0';
|
||||||
|
} else {
|
||||||
|
mSlotBegin = mSlotBegin * 10 + (c - '0');
|
||||||
|
}
|
||||||
|
} else if (c == '-') {
|
||||||
|
mState = SlotEnd;
|
||||||
|
mSlotEnd = 0;
|
||||||
|
} else if (c == '[') {
|
||||||
|
mState = SlotMove;
|
||||||
|
} else if (c == ' ') {
|
||||||
|
mSlotEnd = mSlotBegin + 1;
|
||||||
|
node = true;
|
||||||
|
} else if (c == '\n') {
|
||||||
|
mSlotEnd = mSlotBegin + 1;
|
||||||
|
node = true;
|
||||||
|
mState = NodeStart;
|
||||||
|
} else {
|
||||||
|
return Error;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SlotEnd:
|
||||||
|
if (c >= '0' && c <= '9') {
|
||||||
|
mSlotEnd = mSlotEnd * 10 + (c - '0');
|
||||||
|
} else if (c == ' ') {
|
||||||
|
++mSlotEnd;
|
||||||
|
node = true;
|
||||||
|
mState = SlotBegin;
|
||||||
|
} else if (c == '\n') {
|
||||||
|
++mSlotEnd;
|
||||||
|
node = true;
|
||||||
|
mState = NodeStart;
|
||||||
|
} else {
|
||||||
|
return Error;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SlotMove:
|
||||||
|
if (c == ' ') {
|
||||||
|
mState = SlotBegin;
|
||||||
|
mSlotBegin = mSlotEnd = -1;
|
||||||
|
} else if (c == '\n') {
|
||||||
|
node = true;
|
||||||
|
mState = NodeStart;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return Error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mNodes.use(n);
|
||||||
|
if (node) {
|
||||||
|
return Node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Error;
|
||||||
|
}
|
||||||
100
src/ClusterNodesParser.h
Normal file
100
src/ClusterNodesParser.h
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _CLUSTER_NODES_PARSER_H_
|
||||||
|
#define _CLUSTER_NODES_PARSER_H_
|
||||||
|
|
||||||
|
#include "Buffer.h"
|
||||||
|
#include "String.h"
|
||||||
|
#include "Server.h"
|
||||||
|
|
||||||
|
class ClusterNodesParser
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum Status
|
||||||
|
{
|
||||||
|
Error,
|
||||||
|
Node,
|
||||||
|
Finished
|
||||||
|
};
|
||||||
|
public:
|
||||||
|
ClusterNodesParser();
|
||||||
|
~ClusterNodesParser();
|
||||||
|
void set(const Segment& s);
|
||||||
|
Status parse();
|
||||||
|
void rewind()
|
||||||
|
{
|
||||||
|
mNodes.rewind();
|
||||||
|
}
|
||||||
|
Server::Role role() const
|
||||||
|
{
|
||||||
|
return mRole;
|
||||||
|
}
|
||||||
|
const String& nodeId() const
|
||||||
|
{
|
||||||
|
return mNodeId;
|
||||||
|
}
|
||||||
|
const String& addr() const
|
||||||
|
{
|
||||||
|
return mAddr;
|
||||||
|
}
|
||||||
|
const String& flags() const
|
||||||
|
{
|
||||||
|
return mFlags;
|
||||||
|
}
|
||||||
|
const String& master() const
|
||||||
|
{
|
||||||
|
return mMaster;
|
||||||
|
}
|
||||||
|
bool getSlot(int& begin, int& end) const
|
||||||
|
{
|
||||||
|
begin = mSlotBegin;
|
||||||
|
end = mSlotEnd;
|
||||||
|
return begin >= 0 && begin < end;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
enum State
|
||||||
|
{
|
||||||
|
Idle,
|
||||||
|
Len,
|
||||||
|
LenLF,
|
||||||
|
NodeStart,
|
||||||
|
Field,
|
||||||
|
SlotBegin,
|
||||||
|
SlotEnd,
|
||||||
|
SlotMove,
|
||||||
|
NodeLF
|
||||||
|
};
|
||||||
|
enum FieldType
|
||||||
|
{
|
||||||
|
NodeId,
|
||||||
|
Addr,
|
||||||
|
Flags,
|
||||||
|
Master,
|
||||||
|
PingSent,
|
||||||
|
PongRecv,
|
||||||
|
ConfigEpoch,
|
||||||
|
LinkState,
|
||||||
|
Slot
|
||||||
|
};
|
||||||
|
static const int NodeIdLen = 48;
|
||||||
|
static const int AddrLen = 32;
|
||||||
|
static const int FlagsLen = 48;
|
||||||
|
private:
|
||||||
|
Segment mNodes;
|
||||||
|
State mState;
|
||||||
|
int mLen;
|
||||||
|
Server::Role mRole;
|
||||||
|
int mFieldCnt;
|
||||||
|
SString<NodeIdLen> mNodeId;
|
||||||
|
SString<AddrLen> mAddr;
|
||||||
|
SString<FlagsLen> mFlags;
|
||||||
|
SString<NodeIdLen> mMaster;
|
||||||
|
int mSlotBegin;
|
||||||
|
int mSlotEnd;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
231
src/ClusterServerPool.cpp
Normal file
231
src/ClusterServerPool.cpp
Normal file
@ -0,0 +1,231 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <time.h>
|
||||||
|
#include "ClusterServerPool.h"
|
||||||
|
#include "ClusterNodesParser.h"
|
||||||
|
#include "ServerGroup.h"
|
||||||
|
#include "Handler.h"
|
||||||
|
|
||||||
|
const char* ClusterServerPool::HashTag = "{}";
|
||||||
|
|
||||||
|
ClusterServerPool::ClusterServerPool(Proxy* p):
|
||||||
|
ServerPoolTmpl(p, Cluster),
|
||||||
|
mHash(Hash::Crc16)
|
||||||
|
{
|
||||||
|
mServPool.reserve(Const::MaxServNum);
|
||||||
|
mGroupPool.reserve(Const::MaxServGroupNum);
|
||||||
|
for (int i = 0; i < Const::RedisClusterSlots; ++i) {
|
||||||
|
mSlots[i] = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ClusterServerPool::~ClusterServerPool()
|
||||||
|
{
|
||||||
|
for (auto i : mServPool) {
|
||||||
|
delete i;
|
||||||
|
}
|
||||||
|
for (auto i : mGroupPool) {
|
||||||
|
delete i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Server* ClusterServerPool::getServer(Handler* h, Request* req) const
|
||||||
|
{
|
||||||
|
FuncCallTimer();
|
||||||
|
switch (req->type()) {
|
||||||
|
case Command::ClusterNodes:
|
||||||
|
case Command::Randomkey:
|
||||||
|
return randServer(h, mServPool);
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
SegmentStr<Const::MaxKeyLen> key(req->key());
|
||||||
|
int i = mHash.hash(key.data(), key.length(), HashTag);
|
||||||
|
i &= Const::RedisClusterSlotsMask;
|
||||||
|
ServerGroup* g = mSlots[i];
|
||||||
|
if (!g) {
|
||||||
|
return randServer(h, mServPool);
|
||||||
|
}
|
||||||
|
return g->getServer(h, req);
|
||||||
|
}
|
||||||
|
|
||||||
|
Server* ClusterServerPool::redirect(const String& addr, Server* old) const
|
||||||
|
{
|
||||||
|
Server* serv = nullptr;
|
||||||
|
int size = mServPool.size();
|
||||||
|
for (int i = 0; i < size; ++i) {
|
||||||
|
Server* s = mServPool[i];
|
||||||
|
if (s->addr() == addr) {
|
||||||
|
serv = s;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return serv;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClusterServerPool::init(const ClusterServerPoolConf& conf)
|
||||||
|
{
|
||||||
|
ServerPool::init(conf);
|
||||||
|
mServPool.resize(conf.servers.size());
|
||||||
|
int i = 0;
|
||||||
|
for (auto& sc : conf.servers) {
|
||||||
|
Server* s = new Server(this, sc.addr.c_str(), true);
|
||||||
|
s->setPassword(sc.password.empty() ? conf.password : sc.password);
|
||||||
|
mServPool[i++] = s;
|
||||||
|
mServs[s->addr()] = s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClusterServerPool::refreshRequest(Handler* h)
|
||||||
|
{
|
||||||
|
logDebug("h %d update redis cluster pool", h->id());
|
||||||
|
RequestPtr req = RequestAlloc::create(Request::ClusterNodes);
|
||||||
|
h->handleRequest(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClusterServerPool::handleResponse(Handler* h, ConnectConnection* s, Request* req, Response* res)
|
||||||
|
{
|
||||||
|
ClusterNodesParser p;
|
||||||
|
p.set(res->body());
|
||||||
|
for (auto serv : mServPool) {
|
||||||
|
serv->setUpdating(true);
|
||||||
|
}
|
||||||
|
while (true) {
|
||||||
|
ClusterNodesParser::Status st = p.parse();
|
||||||
|
if (st == ClusterNodesParser::Node) {
|
||||||
|
logDebug("redis cluster update parse node %s %s %s %s",
|
||||||
|
p.nodeId().data(),
|
||||||
|
p.addr().data(),
|
||||||
|
p.flags().data(),
|
||||||
|
p.master().data());
|
||||||
|
if (p.addr().empty()) {
|
||||||
|
logWarn("redis cluster nodes get node invalid %s %s %s %s",
|
||||||
|
p.nodeId().data(),
|
||||||
|
p.addr().data(),
|
||||||
|
p.flags().data(),
|
||||||
|
p.master().data());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto it = mServs.find(p.addr());
|
||||||
|
Server* serv = it == mServs.end() ? nullptr : it->second;
|
||||||
|
if (!serv) {
|
||||||
|
const char* flags = p.flags().data();
|
||||||
|
if (p.role() == Server::Unknown ||
|
||||||
|
strstr(flags, "fail") || strstr(flags, "handshake")) {
|
||||||
|
logWarn("redis cluster nodes get node abnormal %s %s %s %s",
|
||||||
|
p.nodeId().data(),
|
||||||
|
p.addr().data(),
|
||||||
|
p.flags().data(),
|
||||||
|
p.master().data());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (mServPool.size() == mServPool.capacity()) {
|
||||||
|
logWarn("cluster server pool had too many servers %d, will ignore new server %s",
|
||||||
|
(int)mServPool.size(), p.addr().data());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
serv = new Server(this, p.addr(), false);
|
||||||
|
serv->setPassword(password());
|
||||||
|
mServPool.push_back(serv);
|
||||||
|
mServs[serv->addr()] = serv;
|
||||||
|
logNotice("redis cluster create new server %s %s %s %s %s",
|
||||||
|
p.nodeId().data(),
|
||||||
|
p.addr().data(),
|
||||||
|
p.flags().data(),
|
||||||
|
p.master().data(),
|
||||||
|
serv->dcName().data());
|
||||||
|
} else {
|
||||||
|
serv->setOnline(true);
|
||||||
|
serv->setUpdating(false);
|
||||||
|
}
|
||||||
|
serv->setRole(p.role());
|
||||||
|
serv->setName(p.nodeId());
|
||||||
|
serv->setMasterName(p.master());
|
||||||
|
if (p.role() == Server::Master) {
|
||||||
|
ServerGroup* g = getGroup(p.nodeId());
|
||||||
|
if (!g) {
|
||||||
|
if (mGroupPool.size() == mGroupPool.capacity()) {
|
||||||
|
logNotice("redis cluster too many group %s %s %s %s",
|
||||||
|
p.nodeId().data(),
|
||||||
|
p.addr().data(),
|
||||||
|
p.flags().data(),
|
||||||
|
p.master().data());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
g = new ServerGroup(this, p.nodeId());
|
||||||
|
mGroupPool.push_back(g);
|
||||||
|
mGroups[g->name()] = g;
|
||||||
|
logNotice("redis cluster create new group %s %s %s %s",
|
||||||
|
p.nodeId().data(),
|
||||||
|
p.addr().data(),
|
||||||
|
p.flags().data(),
|
||||||
|
p.master().data());
|
||||||
|
}
|
||||||
|
g->add(serv);
|
||||||
|
int begin, end;
|
||||||
|
if (p.getSlot(begin, end)) {
|
||||||
|
while (begin < end) {
|
||||||
|
mSlots[begin++] = g;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (st == ClusterNodesParser::Finished) {
|
||||||
|
logDebug("redis cluster nodes update finish");
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
logError("redis cluster nodes parse error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto serv : mServPool) {
|
||||||
|
if (serv->updating()) {
|
||||||
|
serv->setOnline(false);
|
||||||
|
serv->setUpdating(false);
|
||||||
|
if (ServerGroup* g = serv->group()) {
|
||||||
|
g->remove(serv);
|
||||||
|
serv->setGroup(nullptr);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (serv->role() == Server::Master) {
|
||||||
|
ServerGroup* g = getGroup(serv->name());
|
||||||
|
if (serv->group() && serv->group() != g) {
|
||||||
|
serv->group()->remove(serv);
|
||||||
|
}
|
||||||
|
if (g) {
|
||||||
|
g->add(serv);
|
||||||
|
serv->setGroup(g);
|
||||||
|
} else {
|
||||||
|
logWarn("redis cluster update can't find group for master %.*s %.*s",
|
||||||
|
serv->name().length(), serv->name().data(),
|
||||||
|
serv->addr().length(), serv->addr().data());
|
||||||
|
}
|
||||||
|
} else if (serv->role() == Server::Slave) {
|
||||||
|
ServerGroup* g = getGroup(serv->masterName());
|
||||||
|
if (serv->group() && serv->group() != g) {
|
||||||
|
serv->group()->remove(serv);
|
||||||
|
serv->setGroup(nullptr);
|
||||||
|
}
|
||||||
|
if (g) {
|
||||||
|
g->add(serv);
|
||||||
|
serv->setGroup(g);
|
||||||
|
} else {
|
||||||
|
logWarn("redis cluster update can't find master %.*s for slave %.*s %.*s",
|
||||||
|
serv->masterName().length(), serv->masterName().data(),
|
||||||
|
serv->name().length(), serv->name().data(),
|
||||||
|
serv->addr().length(), serv->addr().data());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logWarn("redis cluster update server %s %s role unknown",
|
||||||
|
serv->name().data(), serv->addr().data());
|
||||||
|
if (ServerGroup* g = serv->group()) {
|
||||||
|
g->remove(serv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
49
src/ClusterServerPool.h
Normal file
49
src/ClusterServerPool.h
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_CLUSTER_SERVER_POOL_H_
|
||||||
|
#define _PREDIXY_CLUSTER_SERVER_POOL_H_
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <map>
|
||||||
|
#include "ServerPool.h"
|
||||||
|
|
||||||
|
class ClusterServerPool : public ServerPoolTmpl<ClusterServerPool>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static const char* HashTag;
|
||||||
|
public:
|
||||||
|
ClusterServerPool(Proxy* p);
|
||||||
|
~ClusterServerPool();
|
||||||
|
void init(const ClusterServerPoolConf& conf);
|
||||||
|
Server* redirect(const String& addr, Server* old) const;
|
||||||
|
const std::vector<Server*>& servers() const
|
||||||
|
{
|
||||||
|
return mServPool;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
Server* getServer(Handler* h, Request* req) const;
|
||||||
|
void refreshRequest(Handler* h);
|
||||||
|
void handleResponse(Handler* h, ConnectConnection* s, Request* req, Response* res);
|
||||||
|
ServerGroup* getGroup(const String& nodeid) const
|
||||||
|
{
|
||||||
|
auto it = mGroups.find(nodeid);
|
||||||
|
return it == mGroups.end() ? nullptr : it->second;
|
||||||
|
}
|
||||||
|
Server* iter(int& cursor) const
|
||||||
|
{
|
||||||
|
return ServerPool::iter(mServPool, cursor);
|
||||||
|
}
|
||||||
|
friend class ServerPoolTmpl<ClusterServerPool>;
|
||||||
|
private:
|
||||||
|
Hash mHash;
|
||||||
|
std::vector<Server*> mServPool;
|
||||||
|
std::map<String, ServerGroup*> mGroups;
|
||||||
|
ServerGroup* mSlots[Const::RedisClusterSlots];
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
189
src/Command.cpp
Normal file
189
src/Command.cpp
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <strings.h>
|
||||||
|
#include <map>
|
||||||
|
#include "String.h"
|
||||||
|
#include "Command.h"
|
||||||
|
|
||||||
|
const Command Command::CmdPool[Sentinel] = {
|
||||||
|
{None, "", 0, 0, Read},
|
||||||
|
{Ping, "ping", 1, 2, Read},
|
||||||
|
{PingServ, "ping", 1, 2, Inner},
|
||||||
|
{Echo, "echo", 2, 2, Read},
|
||||||
|
{Auth, "auth", 2, 2, Read},
|
||||||
|
{AuthServ, "auth", 2, 2, Inner},
|
||||||
|
{Select, "select", 2, 2, Read},
|
||||||
|
{SelectServ, "select", 2, 2, Inner},
|
||||||
|
{SentinelSentinels, "sentinel sentinels",3, 3, Inner},
|
||||||
|
{SentinelGetMaster, "sentinel get-m-a..",3, 3, Inner},
|
||||||
|
{SentinelSlaves, "sentinel slaves", 3, 3, Inner},
|
||||||
|
{Cmd, "command", 1, 1, Read},
|
||||||
|
{Info, "info", 1, 4, Read},
|
||||||
|
{Config, "config", 3, 4, Admin},
|
||||||
|
{Cluster, "cluster", 2, 2, Inner},
|
||||||
|
{ClusterNodes, "cluster nodes", 2, 2, SubCmd|Inner},
|
||||||
|
{Asking, "asking", 1, 1, Inner},
|
||||||
|
{Readonly, "readonly", 1, 1, Inner},
|
||||||
|
{Watch, "watch", 2, MaxArgs, Write|Private},
|
||||||
|
{Unwatch, "unwatch", 1, 1, Write},
|
||||||
|
{UnwatchServ, "unwatch", 1, 1, Inner},
|
||||||
|
{Multi, "multi", 1, 1, Write|Private|NoKey},
|
||||||
|
{Exec, "exec", 1, 1, Write|NoKey},
|
||||||
|
{Discard, "discard", 1, 1, Write|NoKey},
|
||||||
|
{DiscardServ, "discard", 1, 1, Inner|NoKey},
|
||||||
|
{Eval, "eval", 4, MaxArgs, Write|KeyAt3},
|
||||||
|
{Evalsha, "evalsha", 4, MaxArgs, Write|KeyAt3},
|
||||||
|
{Script, "script", 2, MaxArgs, Write},
|
||||||
|
{ScriptLoad, "script", 3, 3, Write|SubCmd},
|
||||||
|
{Del, "del", 2, MaxArgs, Write|MultiKey},
|
||||||
|
{Dump, "dump", 2, 2, Read},
|
||||||
|
{Exists, "exists", 2, MaxArgs, Read|MultiKey},
|
||||||
|
{Expire, "expire", 3, 3, Write},
|
||||||
|
{Expireat, "expireat", 3, 3, Write},
|
||||||
|
{Move, "move", 3, 3, Write},
|
||||||
|
{Persist, "persist", 2, 2, Write},
|
||||||
|
{Pexpire, "pexpire", 3, 3, Write},
|
||||||
|
{Pexpireat, "pexpireat", 3, 3, Write},
|
||||||
|
{Pttl, "pttl", 2, 2, Read},
|
||||||
|
{Randomkey, "randomkey", 1, 1, Read|NoKey},
|
||||||
|
{Rename, "rename", 3, 3, Write},
|
||||||
|
{Renamenx, "renamenx", 3, 3, Write},
|
||||||
|
{Restore, "restore", 4, 5, Write},
|
||||||
|
{Sort, "sort", 2, MaxArgs, Read},
|
||||||
|
{Touch, "touch", 2, MaxArgs, Write|MultiKey},
|
||||||
|
{Ttl, "ttl", 2, 2, Read},
|
||||||
|
{TypeCmd, "type", 2, 2, Read},
|
||||||
|
{Unlink, "unlink", 2, MaxArgs, Write|MultiKey},
|
||||||
|
{Scan, "scan", 2, 6, Read},
|
||||||
|
{Append, "append", 3, 3, Write},
|
||||||
|
{Bitcount, "bitcount", 2, 4, Read},
|
||||||
|
{Bitfield, "bitfield", 2, MaxArgs, Write},
|
||||||
|
{Bitop, "bitop", 4, MaxArgs, Write},
|
||||||
|
{Bitpos, "bitpos", 3, 5, Read},
|
||||||
|
{Decr, "decr", 2, 2, Write},
|
||||||
|
{Decrby, "decrby", 3, 3, Write},
|
||||||
|
{Get, "get", 2, 2, Read},
|
||||||
|
{Getbit, "getbit", 3, 3, Read},
|
||||||
|
{Getrange, "getrange", 4, 4, Read},
|
||||||
|
{Getset, "getset", 3, 3, Write},
|
||||||
|
{Incr, "incr", 2, 2, Write},
|
||||||
|
{Incrby, "incrby", 3, 3, Write},
|
||||||
|
{Incrbyfloat, "incrbyfloat", 3, 3, Write},
|
||||||
|
{Mget, "mget", 2, MaxArgs, Read|MultiKey},
|
||||||
|
{Mset, "mset", 3, MaxArgs, Write|MultiKeyVal},
|
||||||
|
{Msetnx, "msetnx", 3, MaxArgs, Write|MultiKeyVal},
|
||||||
|
{Psetex, "psetex", 4, 4, Write},
|
||||||
|
{Set, "set", 3, 8, Write},
|
||||||
|
{Setbit, "setbit", 4, 4, Write},
|
||||||
|
{Setex, "setex", 4, 4, Write},
|
||||||
|
{Setnx, "setnx", 3, 3, Write},
|
||||||
|
{Setrange, "setrange", 4, 4, Write},
|
||||||
|
{Strlen, "strlen", 2, 2, Read},
|
||||||
|
{Hdel, "hdel", 3, MaxArgs, Write},
|
||||||
|
{Hexists, "hexists", 3, 3, Write},
|
||||||
|
{Hget, "hget", 3, 3, Write},
|
||||||
|
{Hgetall, "hgetall", 2, 2, Read},
|
||||||
|
{Hincrby, "hincrby", 4, 4, Write},
|
||||||
|
{Hincrbyfloat, "hincrbyfloat", 4, 4, Write},
|
||||||
|
{Hkeys, "hkeys", 2, 2, Read},
|
||||||
|
{Hlen, "hlen", 2, 2, Read},
|
||||||
|
{Hmget, "hmget", 3, MaxArgs, Read},
|
||||||
|
{Hmset, "hmset", 4, MaxArgs, Write},
|
||||||
|
{Hscan, "hscan", 3, 7, Read},
|
||||||
|
{Hset, "hset", 4, 4, Write},
|
||||||
|
{Hsetnx, "hsetnx", 4, 4, Write},
|
||||||
|
{Hstrlen, "hstrlen", 3, 3, Read},
|
||||||
|
{Hvals, "hvals", 2, 2, Read},
|
||||||
|
{Blpop, "blpop", 3, MaxArgs, Write|Private},
|
||||||
|
{Brpop, "brpop", 3, MaxArgs, Write|Private},
|
||||||
|
{Brpoplpush, "brpoplpush", 4, 4, Write|Private},
|
||||||
|
{Lindex, "lindex", 3, 3, Read},
|
||||||
|
{Linsert, "linsert", 5, 5, Write},
|
||||||
|
{Llen, "llen", 2, 2, Read},
|
||||||
|
{Lpop, "lpop", 2, 2, Write},
|
||||||
|
{Lpush, "lpush", 3, MaxArgs, Write},
|
||||||
|
{Lpushx, "lpushx", 3, 3, Write},
|
||||||
|
{Lrange, "lrange", 4, 4, Write},
|
||||||
|
{Lrem, "lrem", 4, 4, Write},
|
||||||
|
{Lset, "lset", 4, 4, Write},
|
||||||
|
{Ltrim, "ltrim", 4, 4, Write},
|
||||||
|
{Rpop, "rpop", 2, 2, Write},
|
||||||
|
{Rpoplpush, "rpoplpush", 3, 3, Write},
|
||||||
|
{Rpush, "rpush", 3, MaxArgs, Write},
|
||||||
|
{Rpushx, "rpushx", 3, 3, Write},
|
||||||
|
{Sadd, "sadd", 3, MaxArgs, Write},
|
||||||
|
{Scard, "scard", 2, 2, Read},
|
||||||
|
{Sdiff, "sdiff", 2, MaxArgs, Read},
|
||||||
|
{Sdiffstore, "sdiffstore", 3, MaxArgs, Write},
|
||||||
|
{Sinter, "sinter", 2, MaxArgs, Read},
|
||||||
|
{Sinterstore, "sinterstore", 3, MaxArgs, Write},
|
||||||
|
{Sismember, "sismember", 3, 3, Read},
|
||||||
|
{Smembers, "smembers", 2, 2, Read},
|
||||||
|
{Smove, "smove", 4, 4, Write},
|
||||||
|
{Spop, "spop", 2, 3, Write},
|
||||||
|
{Srandmember, "srandmember", 2, 3, Read},
|
||||||
|
{Srem, "srem", 3, MaxArgs, Write},
|
||||||
|
{Sscan, "sscan", 3, 7, Read},
|
||||||
|
{Sunion, "sunion", 2, MaxArgs, Read},
|
||||||
|
{Sunionstore, "sunionstore", 3, MaxArgs, Write},
|
||||||
|
{Zadd, "zadd", 4, MaxArgs, Write},
|
||||||
|
{Zcard, "zcard", 2, 2, Read},
|
||||||
|
{Zcount, "zcount", 4, 4, Read},
|
||||||
|
{Zincrby, "zincrby", 4, 4, Write},
|
||||||
|
{Zinterstore, "zinterstore", 4, MaxArgs, Write},
|
||||||
|
{Zlexcount, "zlexcount", 4, 4, Read},
|
||||||
|
{Zrange, "zrange", 4, 5, Read},
|
||||||
|
{Zrangebylex, "zrangebylex", 4, 7, Read},
|
||||||
|
{Zrangebyscore, "zrangebyscore", 4, 8, Read},
|
||||||
|
{Zrank, "zrank", 3, 3, Read},
|
||||||
|
{Zrem, "zrem", 3, MaxArgs, Write},
|
||||||
|
{Zremrangebylex, "zremrangebylex", 4, 4, Write},
|
||||||
|
{Zremrangebyrank, "zremrangebyrank", 4, 4, Write},
|
||||||
|
{Zremrangebyscore, "zremrangebyscore", 4, 4, Write},
|
||||||
|
{Zrevrange, "zrevrange", 4, 5, Read},
|
||||||
|
{Zrevrangebylex, "zrevrangebylex", 4, 7, Read},
|
||||||
|
{Zrevrangebyscore, "zrevrangebyscore", 4, 8, Read},
|
||||||
|
{Zrevrank, "zrevrank", 3, 3, Read},
|
||||||
|
{Zscan, "zscan", 3, 7, Read},
|
||||||
|
{Zscore, "zscore", 3, 3, Read},
|
||||||
|
{Zunionstore, "zunionstore", 4, MaxArgs, Write},
|
||||||
|
{Pfadd, "pfadd", 3, MaxArgs, Write},
|
||||||
|
{Pfcount, "pfcount", 2, MaxArgs, Read},
|
||||||
|
{Pfmerge, "pfmerge", 3, MaxArgs, Write},
|
||||||
|
{Geoadd, "geoadd", 5, MaxArgs, Write},
|
||||||
|
{Geodist, "geodist", 4, 5, Read},
|
||||||
|
{Geohash, "geohash", 3, MaxArgs, Read},
|
||||||
|
{Geopos, "geopos", 3, MaxArgs, Read},
|
||||||
|
{Georadius, "georadius", 6, 16, Read},
|
||||||
|
{Georadiusbymember, "georadiusbymember",5, 15, Read},
|
||||||
|
{Psubscribe, "psubscribe", 2, MaxArgs, Write|SMultiKey|Private},
|
||||||
|
{Publish, "publish", 3, 3, Write},
|
||||||
|
{Pubsub, "pubsub", 2, MaxArgs, Read},
|
||||||
|
{Punsubscribe, "punsubscribe", 1, MaxArgs, Write},
|
||||||
|
{Subscribe, "subscribe", 2, MaxArgs, Write|SMultiKey|Private},
|
||||||
|
{Unsubscribe, "unsubscribe", 1, MaxArgs, Write},
|
||||||
|
{SubMsg, "\000SubMsg", 0, 0, Admin}
|
||||||
|
};
|
||||||
|
|
||||||
|
Command::CommandMap Command::CmdMap;
|
||||||
|
|
||||||
|
void Command::init()
|
||||||
|
{
|
||||||
|
int type = 0;
|
||||||
|
for (auto& i : CmdPool) {
|
||||||
|
if (i.type != type) {
|
||||||
|
Throw(InitFail, "command %s unmatch the index in commands table", i.name);
|
||||||
|
}
|
||||||
|
++type;
|
||||||
|
if (i.mode & (Command::Inner|Command::SubCmd)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
CmdMap[i.name] = &i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
251
src/Command.h
Normal file
251
src/Command.h
Normal file
@ -0,0 +1,251 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_COMMAND_H_
|
||||||
|
#define _PREDIXY_COMMAND_H_
|
||||||
|
|
||||||
|
#include "Exception.h"
|
||||||
|
|
||||||
|
class Command
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DefException(InitFail);
|
||||||
|
enum Type
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Ping,
|
||||||
|
PingServ,
|
||||||
|
Echo,
|
||||||
|
Auth,
|
||||||
|
AuthServ,
|
||||||
|
Select,
|
||||||
|
SelectServ,
|
||||||
|
|
||||||
|
SentinelSentinels,
|
||||||
|
SentinelGetMaster,
|
||||||
|
SentinelSlaves,
|
||||||
|
|
||||||
|
Cmd,
|
||||||
|
Info,
|
||||||
|
Config,
|
||||||
|
|
||||||
|
Cluster,
|
||||||
|
ClusterNodes,
|
||||||
|
Asking,
|
||||||
|
Readonly,
|
||||||
|
|
||||||
|
Watch,
|
||||||
|
Unwatch,
|
||||||
|
UnwatchServ,
|
||||||
|
Multi,
|
||||||
|
Exec,
|
||||||
|
Discard,
|
||||||
|
DiscardServ,
|
||||||
|
|
||||||
|
Eval,
|
||||||
|
Evalsha,
|
||||||
|
Script,
|
||||||
|
ScriptLoad,
|
||||||
|
|
||||||
|
Del,
|
||||||
|
Dump,
|
||||||
|
Exists,
|
||||||
|
Expire,
|
||||||
|
Expireat,
|
||||||
|
Move,
|
||||||
|
Persist,
|
||||||
|
Pexpire,
|
||||||
|
Pexpireat,
|
||||||
|
Pttl,
|
||||||
|
Randomkey,
|
||||||
|
Rename,
|
||||||
|
Renamenx,
|
||||||
|
Restore,
|
||||||
|
Sort,
|
||||||
|
Touch,
|
||||||
|
Ttl,
|
||||||
|
TypeCmd,
|
||||||
|
Unlink,
|
||||||
|
Scan,
|
||||||
|
Append,
|
||||||
|
Bitcount,
|
||||||
|
Bitfield,
|
||||||
|
Bitop,
|
||||||
|
Bitpos,
|
||||||
|
Decr,
|
||||||
|
Decrby,
|
||||||
|
Get,
|
||||||
|
Getbit,
|
||||||
|
Getrange,
|
||||||
|
Getset,
|
||||||
|
Incr,
|
||||||
|
Incrby,
|
||||||
|
Incrbyfloat,
|
||||||
|
Mget,
|
||||||
|
Mset,
|
||||||
|
Msetnx,
|
||||||
|
Psetex,
|
||||||
|
Set,
|
||||||
|
Setbit,
|
||||||
|
Setex,
|
||||||
|
Setnx,
|
||||||
|
Setrange,
|
||||||
|
Strlen,
|
||||||
|
|
||||||
|
Hdel,
|
||||||
|
Hexists,
|
||||||
|
Hget,
|
||||||
|
Hgetall,
|
||||||
|
Hincrby,
|
||||||
|
Hincrbyfloat,
|
||||||
|
Hkeys,
|
||||||
|
Hlen,
|
||||||
|
Hmget,
|
||||||
|
Hmset,
|
||||||
|
Hscan,
|
||||||
|
Hset,
|
||||||
|
Hsetnx,
|
||||||
|
Hstrlen,
|
||||||
|
Hvals,
|
||||||
|
|
||||||
|
Blpop,
|
||||||
|
Brpop,
|
||||||
|
Brpoplpush,
|
||||||
|
Lindex,
|
||||||
|
Linsert,
|
||||||
|
Llen,
|
||||||
|
Lpop,
|
||||||
|
Lpush,
|
||||||
|
Lpushx,
|
||||||
|
Lrange,
|
||||||
|
Lrem,
|
||||||
|
Lset,
|
||||||
|
Ltrim,
|
||||||
|
Rpop,
|
||||||
|
Rpoplpush,
|
||||||
|
Rpush,
|
||||||
|
Rpushx,
|
||||||
|
|
||||||
|
Sadd,
|
||||||
|
Scard,
|
||||||
|
Sdiff,
|
||||||
|
Sdiffstore,
|
||||||
|
Sinter,
|
||||||
|
Sinterstore,
|
||||||
|
Sismember,
|
||||||
|
Smembers,
|
||||||
|
Smove,
|
||||||
|
Spop,
|
||||||
|
Srandmember,
|
||||||
|
Srem,
|
||||||
|
Sscan,
|
||||||
|
Sunion,
|
||||||
|
Sunionstore,
|
||||||
|
|
||||||
|
Zadd,
|
||||||
|
Zcard,
|
||||||
|
Zcount,
|
||||||
|
Zincrby,
|
||||||
|
Zinterstore,
|
||||||
|
Zlexcount,
|
||||||
|
Zrange,
|
||||||
|
Zrangebylex,
|
||||||
|
Zrangebyscore,
|
||||||
|
Zrank,
|
||||||
|
Zrem,
|
||||||
|
Zremrangebylex,
|
||||||
|
Zremrangebyrank,
|
||||||
|
Zremrangebyscore,
|
||||||
|
Zrevrange,
|
||||||
|
Zrevrangebylex,
|
||||||
|
Zrevrangebyscore,
|
||||||
|
Zrevrank,
|
||||||
|
Zscan,
|
||||||
|
Zscore,
|
||||||
|
Zunionstore,
|
||||||
|
|
||||||
|
Pfadd,
|
||||||
|
Pfcount,
|
||||||
|
Pfmerge,
|
||||||
|
|
||||||
|
Geoadd,
|
||||||
|
Geodist,
|
||||||
|
Geohash,
|
||||||
|
Geopos,
|
||||||
|
Georadius,
|
||||||
|
Georadiusbymember,
|
||||||
|
|
||||||
|
Psubscribe,
|
||||||
|
Publish,
|
||||||
|
Pubsub,
|
||||||
|
Punsubscribe,
|
||||||
|
Subscribe,
|
||||||
|
Unsubscribe,
|
||||||
|
SubMsg,
|
||||||
|
|
||||||
|
Sentinel
|
||||||
|
};
|
||||||
|
enum Mode
|
||||||
|
{
|
||||||
|
Unknown = 0,
|
||||||
|
Read = 1<<0,
|
||||||
|
Write = 1<<1,
|
||||||
|
Admin = 1<<2,
|
||||||
|
Private = 1<<3, //require private connection
|
||||||
|
NoKey = 1<<4,
|
||||||
|
MultiKey = 1<<5,
|
||||||
|
SMultiKey = 1<<6,
|
||||||
|
MultiKeyVal = 1<<7,
|
||||||
|
KeyAt3 = 1<<8,
|
||||||
|
SubCmd = 1<<9,
|
||||||
|
Inner = 1<<10 //proxy use only
|
||||||
|
};
|
||||||
|
static const int AuthMask = Read|Write|Admin;
|
||||||
|
static const int KeyMask = NoKey|MultiKey|SMultiKey|MultiKeyVal|KeyAt3;
|
||||||
|
public:
|
||||||
|
Type type;
|
||||||
|
const char* name;
|
||||||
|
int minArgs;
|
||||||
|
int maxArgs;
|
||||||
|
int mode;
|
||||||
|
|
||||||
|
bool isMultiKey() const
|
||||||
|
{
|
||||||
|
return mode & MultiKey;
|
||||||
|
}
|
||||||
|
bool isSMultiKey() const
|
||||||
|
{
|
||||||
|
return mode & SMultiKey;
|
||||||
|
}
|
||||||
|
bool isMultiKeyVal() const
|
||||||
|
{
|
||||||
|
return mode & MultiKeyVal;
|
||||||
|
}
|
||||||
|
static void init();
|
||||||
|
static const Command& get(Type type)
|
||||||
|
{
|
||||||
|
return CmdPool[type];
|
||||||
|
}
|
||||||
|
static const Command* iter(int& cursor)
|
||||||
|
{
|
||||||
|
if (cursor < Sentinel) {
|
||||||
|
return &CmdPool[cursor++];
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
static const Command* find(const String& cmd)
|
||||||
|
{
|
||||||
|
auto it = CmdMap.find(cmd);
|
||||||
|
return it == CmdMap.end() ? nullptr : it->second;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
static const int MaxArgs = 100000000;
|
||||||
|
static const Command CmdPool[Sentinel];
|
||||||
|
typedef std::map<String, const Command*, StringCaseCmp> CommandMap;
|
||||||
|
static CommandMap CmdMap;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
41
src/Common.h
Normal file
41
src/Common.h
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_COMMON_H_
|
||||||
|
#define _PREDIXY_COMMON_H_
|
||||||
|
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
|
#define _PREDIXY_NAME_ "predixy"
|
||||||
|
#define _PREDIXY_VERSION_ "1.0.0"
|
||||||
|
|
||||||
|
namespace Const
|
||||||
|
{
|
||||||
|
static const long MaxMemoryLower = 10 << 20; //10MB
|
||||||
|
static const int MaxBufListNodeNum = 64;
|
||||||
|
static const int MinBufSize = 1;
|
||||||
|
static const int MinBufSpaceLeft = 1;//64;
|
||||||
|
static const int RedisClusterSlots = 16384;
|
||||||
|
static const int RedisClusterSlotsMask = 16383;
|
||||||
|
static const int MaxServNameLen = 64; //nodeid in redis cluster; master name in sentinel
|
||||||
|
static const int MaxServNum = 2048;
|
||||||
|
static const int ServGroupBits = 10;
|
||||||
|
static const int MaxServGroupNum = 1<<ServGroupBits;
|
||||||
|
static const int ServGroupMask = ((1<<ServGroupBits) - 1);
|
||||||
|
static const int MaxServInGroup = 64;
|
||||||
|
static const int MaxAddrLen = 128;
|
||||||
|
static const int MaxDcLen = 32;
|
||||||
|
static const int MaxIOVecLen = IOV_MAX;
|
||||||
|
static const int MaxKeyLen = 512;
|
||||||
|
static const int BufferAllocCacheSize = 64;
|
||||||
|
static const int RequestAllocCacheSize = 32;
|
||||||
|
static const int ResponseAllocCacheSize = 32;
|
||||||
|
static const int AcceptConnectionAllocCacheSize = 32;
|
||||||
|
static const int ConnectConnectionAllocCacheSize = 4;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
604
src/Conf.cpp
Normal file
604
src/Conf.cpp
Normal file
@ -0,0 +1,604 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
#include "LogFileSink.h"
|
||||||
|
#include "ServerPool.h"
|
||||||
|
#include "Conf.h"
|
||||||
|
|
||||||
|
using std::string;
|
||||||
|
|
||||||
|
// [password@]addr role
|
||||||
|
bool ServerConf::parse(ServerConf& s, const char* str)
|
||||||
|
{
|
||||||
|
const char* p = strchr(str, '@');
|
||||||
|
if (p) {
|
||||||
|
s.password.assign(str, p);
|
||||||
|
str = p + 1;
|
||||||
|
} else {
|
||||||
|
s.password.clear();
|
||||||
|
}
|
||||||
|
for (p = str; *p != '\0'; ++p) {
|
||||||
|
if (isspace(*p)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.addr.assign(str, p);
|
||||||
|
return !s.addr.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
Conf::Conf():
|
||||||
|
mBind("0.0.0.0:7617"),
|
||||||
|
mWorkerThreads(1),
|
||||||
|
mMaxMemory(0),
|
||||||
|
mClientTimeout(0),
|
||||||
|
mBufSize(0),
|
||||||
|
mLogRotateSecs(0),
|
||||||
|
mLogRotateBytes(0),
|
||||||
|
mAllowMissLog(true),
|
||||||
|
mServerPoolType(ServerPool::Unknown)
|
||||||
|
{
|
||||||
|
mLogSample[LogLevel::Verb] = 0;
|
||||||
|
mLogSample[LogLevel::Debug] = 0;
|
||||||
|
mLogSample[LogLevel::Info] = 0;
|
||||||
|
mLogSample[LogLevel::Notice] = 1;
|
||||||
|
mLogSample[LogLevel::Warn] = 1;
|
||||||
|
mLogSample[LogLevel::Error] = 1;
|
||||||
|
mSentinelServerPool.refreshInterval = 1;
|
||||||
|
mClusterServerPool.refreshInterval = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Conf::~Conf()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static void printHelp(const char* name)
|
||||||
|
{
|
||||||
|
fprintf(stderr,
|
||||||
|
"Usage: \n"
|
||||||
|
" %s <conf-file> [options]\n"
|
||||||
|
" %s -h or --help\n"
|
||||||
|
" %s -v or --version\n"
|
||||||
|
"\n"
|
||||||
|
"Options:\n"
|
||||||
|
" --Name=name set current service name\n"
|
||||||
|
" --Bind=addr set bind address, eg:127.0.0.1:7617, /tmp/predixy\n"
|
||||||
|
" --WorkerThreads=N set worker threads\n"
|
||||||
|
" --LocalDC=dc set local dc\n"
|
||||||
|
,name, name, name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void printVersion(const char* name)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s %s-%s\n", name, _PREDIXY_NAME_, _PREDIXY_VERSION_);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define GetVal(s, p) \
|
||||||
|
(strncasecmp(s, p, sizeof(p) - 1) == 0 ? s + sizeof(p) - 1 : nullptr)
|
||||||
|
|
||||||
|
bool Conf::init(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
if (argc < 2) {
|
||||||
|
printHelp(argv[0]);
|
||||||
|
Throw(InvalidStartArg, "start %s arguments invalid", argv[0]);
|
||||||
|
} else if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0) {
|
||||||
|
printHelp(argv[0]);
|
||||||
|
return false;
|
||||||
|
} else if (strcmp(argv[1], "-v") == 0 || strcmp(argv[1], "--version") == 0){
|
||||||
|
printVersion(argv[0]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ConfParser p;
|
||||||
|
ConfParser::Node* n = p.load(argv[1]);
|
||||||
|
setGlobal(n);
|
||||||
|
for (int i = 2; i < argc; ++i) {
|
||||||
|
if (char* v = GetVal(argv[i], "--Name=")) {
|
||||||
|
mName = v;
|
||||||
|
} else if (char* v = GetVal(argv[i], "--Bind=")) {
|
||||||
|
mBind = v;
|
||||||
|
} else if (char* v = GetVal(argv[i], "--WorkerThreads=")) {
|
||||||
|
mWorkerThreads = atoi(v);
|
||||||
|
} else if (char* v = GetVal(argv[i], "--LocalDC=")) {
|
||||||
|
mLocalDC = v;
|
||||||
|
} else {
|
||||||
|
printHelp(argv[0]);
|
||||||
|
Throw(InvalidStartArg, "invalid argument \"%s\"", argv[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
check();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Conf::setGlobal(const ConfParser::Node* node)
|
||||||
|
{
|
||||||
|
const ConfParser::Node* authority = nullptr;
|
||||||
|
const ConfParser::Node* clusterServerPool = nullptr;
|
||||||
|
const ConfParser::Node* sentinelServerPool = nullptr;
|
||||||
|
const ConfParser::Node* dataCenter = nullptr;
|
||||||
|
for (auto p = node; p; p = p->next) {
|
||||||
|
if (setStr(mName, "Name", p)) {
|
||||||
|
} else if (setStr(mBind, "Bind", p)) {
|
||||||
|
} else if (setStr(mLocalDC, "LocalDC", p)) {
|
||||||
|
#ifdef _PREDIXY_SINGLE_THREAD_
|
||||||
|
} else if (setInt(mWorkerThreads, "WorkerThreads", p, 1, 1)) {
|
||||||
|
#else
|
||||||
|
} else if (setInt(mWorkerThreads, "WorkerThreads", p, 1)) {
|
||||||
|
#endif
|
||||||
|
} else if (setMemory(mMaxMemory, "MaxMemory", p)) {
|
||||||
|
} else if (setLong(mClientTimeout, "ClientTimeout", p, 0)) {
|
||||||
|
mClientTimeout *= 1000000;
|
||||||
|
} else if (setInt(mBufSize, "BufSize", p, Const::MinBufSize + sizeof(Buffer))) {
|
||||||
|
mBufSize -= sizeof(Buffer);
|
||||||
|
} else if (setStr(mLog, "Log", p)) {
|
||||||
|
} else if (strcasecmp(p->key.c_str(), "LogRotate") == 0) {
|
||||||
|
if (!LogFileSink::parseRotate(p->val.c_str(), mLogRotateSecs, mLogRotateBytes)) {
|
||||||
|
Throw(InvalidValue, "%s:%d invalid LogRotate \"%s\"",
|
||||||
|
p->file, p->line, p->val.c_str());
|
||||||
|
}
|
||||||
|
} else if (setBool(mAllowMissLog, "AllowMissLog", p)) {
|
||||||
|
} else if (setInt(mLogSample[LogLevel::Verb], "LogVerbSample", p)) {
|
||||||
|
} else if (setInt(mLogSample[LogLevel::Debug], "LogDebugSample", p)) {
|
||||||
|
} else if (setInt(mLogSample[LogLevel::Info], "LogInfoSample", p)) {
|
||||||
|
} else if (setInt(mLogSample[LogLevel::Notice], "LogNoticeSample", p)) {
|
||||||
|
} else if (setInt(mLogSample[LogLevel::Warn], "LogWarnSample", p)) {
|
||||||
|
} else if (setInt(mLogSample[LogLevel::Error], "LogErrorSample", p)) {
|
||||||
|
} else if (strcasecmp(p->key.c_str(), "LatencyMonitor") == 0) {
|
||||||
|
mLatencyMonitors.push_back(LatencyMonitorConf{});
|
||||||
|
setLatencyMonitor(mLatencyMonitors.back(), p);
|
||||||
|
} else if (strcasecmp(p->key.c_str(), "Authority") == 0) {
|
||||||
|
authority = p;
|
||||||
|
} else if (strcasecmp(p->key.c_str(), "ClusterServerPool") == 0) {
|
||||||
|
clusterServerPool = p;
|
||||||
|
} else if (strcasecmp(p->key.c_str(), "SentinelServerPool") == 0) {
|
||||||
|
sentinelServerPool = p;
|
||||||
|
} else if (strcasecmp(p->key.c_str(), "DataCenter") == 0) {
|
||||||
|
dataCenter = p;
|
||||||
|
} else {
|
||||||
|
Throw(UnknownKey, "%s:%d unknown key %s", p->file, p->line, p->key.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (authority) {
|
||||||
|
setAuthority(authority);
|
||||||
|
}
|
||||||
|
if (clusterServerPool && sentinelServerPool) {
|
||||||
|
Throw(LogicError, "Can't define ClusterServerPool and SentinelServerPool at the same time");
|
||||||
|
} else if (clusterServerPool) {
|
||||||
|
setClusterServerPool(clusterServerPool);
|
||||||
|
mServerPoolType = ServerPool::Cluster;
|
||||||
|
} else if (sentinelServerPool) {
|
||||||
|
setSentinelServerPool(sentinelServerPool);
|
||||||
|
mServerPoolType = ServerPool::Sentinel;
|
||||||
|
} else {
|
||||||
|
Throw(LogicError, "Must define a server pool");
|
||||||
|
}
|
||||||
|
if (dataCenter) {
|
||||||
|
setDataCenter(dataCenter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setKeyPrefix(std::vector<std::string>& dat, const std::string& v)
|
||||||
|
{
|
||||||
|
const char* p = v.c_str();
|
||||||
|
const char* s = nullptr;
|
||||||
|
do {
|
||||||
|
if (*p == ' ' || *p == '\t' || *p == '\0') {
|
||||||
|
if (s) {
|
||||||
|
dat.push_back(std::string(s, p - s));
|
||||||
|
s = nullptr;
|
||||||
|
}
|
||||||
|
} else if (!s) {
|
||||||
|
s = p;
|
||||||
|
}
|
||||||
|
} while (*p++);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Conf::setAuthority(const ConfParser::Node* node)
|
||||||
|
{
|
||||||
|
if (!node->sub) {
|
||||||
|
Throw(InvalidValue, "%s:%d Authority require scope value", node->file, node->line);
|
||||||
|
}
|
||||||
|
for (auto p = node->sub; p; p = p->next) {
|
||||||
|
if (strcasecmp(p->key.c_str(), "Auth") != 0) {
|
||||||
|
Throw(InvalidValue, "%s:%d Authority allow only Auth element", p->file, p->line);
|
||||||
|
}
|
||||||
|
if (!p->sub) {
|
||||||
|
Throw(InvalidValue, "%s:%d Auth require scope value", p->file, p->line);
|
||||||
|
}
|
||||||
|
mAuthConfs.push_back(AuthConf{});
|
||||||
|
auto& c = mAuthConfs.back();
|
||||||
|
c.password = p->val;
|
||||||
|
for (auto n = p->sub; n; n = n->next) {
|
||||||
|
const std::string& k = n->key;
|
||||||
|
const std::string& v = n->val;
|
||||||
|
if (strcasecmp(k.c_str(), "Mode") == 0) {
|
||||||
|
if (strcasecmp(v.c_str(), "read") == 0) {
|
||||||
|
c.mode = Command::Read;
|
||||||
|
} else if (strcasecmp(v.c_str(), "write") == 0) {
|
||||||
|
c.mode = Command::Write|Command::Read;
|
||||||
|
} else if (strcasecmp(v.c_str(), "admin") == 0) {
|
||||||
|
c.mode = Command::Write|Command::Read|Command::Admin;
|
||||||
|
} else {
|
||||||
|
Throw(InvalidValue, "%s:%d Auth Mode must be read or write", n->file, n->line);
|
||||||
|
}
|
||||||
|
} else if (strcasecmp(k.c_str(), "KeyPrefix") == 0) {
|
||||||
|
setKeyPrefix(c.keyPrefix, v);
|
||||||
|
} else if (strcasecmp(k.c_str(), "ReadKeyPrefix") == 0) {
|
||||||
|
setKeyPrefix(c.readKeyPrefix, v);
|
||||||
|
} else if (strcasecmp(k.c_str(), "WriteKeyPrefix") == 0) {
|
||||||
|
setKeyPrefix(c.writeKeyPrefix, v);
|
||||||
|
} else {
|
||||||
|
Throw(UnknownKey, "%s:%d unknown key %s", n->file, n->line, k.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Conf::setServerPool(ServerPoolConf& sp, const ConfParser::Node* p)
|
||||||
|
{
|
||||||
|
if (setStr(sp.password, "Password", p)) {
|
||||||
|
return true;
|
||||||
|
} else if (setInt(sp.masterReadPriority, "MasterReadPriority", p, 0, 100)) {
|
||||||
|
return true;
|
||||||
|
} else if (setInt(sp.staticSlaveReadPriority, "StaticSlaveReadPriority", p, 0, 100)) {
|
||||||
|
return true;
|
||||||
|
} else if (setInt(sp.dynamicSlaveReadPriority, "DynamicSlaveReadPriority", p, 0, 100)) {
|
||||||
|
return true;
|
||||||
|
} else if (setInt(sp.refreshInterval, "RefreshInterval", p, 1)) {
|
||||||
|
return true;
|
||||||
|
} else if (setInt(sp.serverFailureLimit, "ServerFailureLimit", p, 1)) {
|
||||||
|
return true;
|
||||||
|
} else if (setInt(sp.serverRetryTimeout, "ServerRetryTimeout", p, 1)) {
|
||||||
|
return true;
|
||||||
|
} else if (setInt(sp.databases, "Databases", p, 1, 128)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Conf::setClusterServerPool(const ConfParser::Node* node)
|
||||||
|
{
|
||||||
|
if (!node->sub) {
|
||||||
|
Throw(InvalidValue, "%s:%d ClusterServerPool require scope value", node->file, node->line);
|
||||||
|
}
|
||||||
|
for (auto p = node->sub; p; p = p->next) {
|
||||||
|
if (setServerPool(mClusterServerPool, p)) {
|
||||||
|
} else if (setServers(mClusterServerPool.servers, "Servers", p)) {
|
||||||
|
} else {
|
||||||
|
Throw(UnknownKey, "%s:%d unknown key %s",
|
||||||
|
p->file, p->line, p->key.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mClusterServerPool.databases != 1) {
|
||||||
|
Throw(InvalidValue, "ClusterServerPool Databases must be 1");
|
||||||
|
}
|
||||||
|
if (mClusterServerPool.servers.empty()) {
|
||||||
|
Throw(LogicError, "ClusterServerPool no server");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Conf::setSentinelServerPool(const ConfParser::Node* node)
|
||||||
|
{
|
||||||
|
if (!node->sub) {
|
||||||
|
Throw(InvalidValue, "%s:%d SentinelServerPool require scope value", node->file, node->line);
|
||||||
|
}
|
||||||
|
mSentinelServerPool.hashTag[0] = '\0';
|
||||||
|
mSentinelServerPool.hashTag[1] = '\0';
|
||||||
|
for (auto p = node->sub; p; p = p->next) {
|
||||||
|
if (setServerPool(mSentinelServerPool, p)) {
|
||||||
|
} else if (strcasecmp(p->key.c_str(), "Distribution") == 0) {
|
||||||
|
mSentinelServerPool.dist = Distribution::parse(p->val.c_str());
|
||||||
|
if (mSentinelServerPool.dist == Distribution::None) {
|
||||||
|
Throw(InvalidValue, "%s:%d unknown Distribution", p->file, p->line);
|
||||||
|
}
|
||||||
|
} else if (strcasecmp(p->key.c_str(), "Hash") == 0) {
|
||||||
|
mSentinelServerPool.hash = Hash::parse(p->val.c_str());
|
||||||
|
if (mSentinelServerPool.hash == Hash::None) {
|
||||||
|
Throw(InvalidValue, "%s:%d unknown Hash", p->file, p->line);
|
||||||
|
}
|
||||||
|
} else if (strcasecmp(p->key.c_str(), "HashTag") == 0) {
|
||||||
|
if (p->val.empty()) {
|
||||||
|
mSentinelServerPool.hashTag[0] = '\0';
|
||||||
|
mSentinelServerPool.hashTag[1] = '\0';
|
||||||
|
} else if (p->val.size() == 2) {
|
||||||
|
mSentinelServerPool.hashTag[0] = p->val[0];
|
||||||
|
mSentinelServerPool.hashTag[1] = p->val[1];
|
||||||
|
} else {
|
||||||
|
Throw(InvalidValue, "%s:%d HashTag invalid", p->file, p->line);
|
||||||
|
}
|
||||||
|
} else if (setServers(mSentinelServerPool.sentinels, "Sentinels", p)) {
|
||||||
|
} else if (strcasecmp(p->key.c_str(), "Group") == 0) {
|
||||||
|
mSentinelServerPool.groups.push_back(ServerGroupConf{p->val});
|
||||||
|
if (p->sub) {
|
||||||
|
auto& g = mSentinelServerPool.groups.back();
|
||||||
|
setServers(g.servers, "Group", p);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Throw(UnknownKey, "%s:%d unknown key %s",
|
||||||
|
p->file, p->line, p->key.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mSentinelServerPool.sentinels.empty()) {
|
||||||
|
Throw(LogicError, "SentinelServerPool no sentinel server");
|
||||||
|
}
|
||||||
|
if (mSentinelServerPool.groups.empty()) {
|
||||||
|
Throw(LogicError, "SentinelServerPool no server group");
|
||||||
|
}
|
||||||
|
if (mSentinelServerPool.groups.size() > 1) {
|
||||||
|
if (mSentinelServerPool.dist == Distribution::None) {
|
||||||
|
Throw(LogicError, "SentinelServerPool must define Dsitribution in multi groups");
|
||||||
|
}
|
||||||
|
if (mSentinelServerPool.hash == Hash::None) {
|
||||||
|
Throw(LogicError, "SentinelServerPool must define Hash in multi groups");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Conf::setDataCenter(const ConfParser::Node* node)
|
||||||
|
{
|
||||||
|
if (!node->sub) {
|
||||||
|
Throw(InvalidValue, "%s:%d DataCenter require scope value", node->file, node->line);
|
||||||
|
}
|
||||||
|
for (auto p = node->sub; p; p = p->next) {
|
||||||
|
if (strcasecmp(p->key.c_str(), "DC") != 0) {
|
||||||
|
Throw(InvalidValue, "%s:%d DataCenter allow only DC element", p->file, p->line);
|
||||||
|
}
|
||||||
|
mDCConfs.push_back(DCConf{});
|
||||||
|
auto& dc = mDCConfs.back();
|
||||||
|
dc.name = p->val;
|
||||||
|
setDC(dc, p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Conf::setDC(DCConf& dc, const ConfParser::Node* node)
|
||||||
|
{
|
||||||
|
if (!node->sub) {
|
||||||
|
Throw(InvalidValue, "%s:%d DC require scope value",
|
||||||
|
node->file, node->line);
|
||||||
|
}
|
||||||
|
for (auto p = node->sub; p; p = p->next) {
|
||||||
|
if (strcasecmp(p->key.c_str(), "AddrPrefix") == 0) {
|
||||||
|
if (!p->sub) {
|
||||||
|
Throw(InvalidValue, "%s:%d AddrPrefix require scope value",
|
||||||
|
p->file, p->line);
|
||||||
|
}
|
||||||
|
for (auto n = p->sub; n; n = n->next) {
|
||||||
|
if (strcmp(n->key.c_str(), "+") == 0) {
|
||||||
|
dc.addrPrefix.push_back(n->val);
|
||||||
|
} else {
|
||||||
|
Throw(InvalidValue, "%s:%d invalid AddrPrefix",
|
||||||
|
n->file, n->line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (strcasecmp(p->key.c_str(), "ReadPolicy") == 0) {
|
||||||
|
if (!p->sub) {
|
||||||
|
Throw(InvalidValue, "%s:%d ReadPolicy require scope value",
|
||||||
|
p->file, p->line);
|
||||||
|
}
|
||||||
|
for (auto n = p->sub; n; n = n->next) {
|
||||||
|
dc.readPolicy.push_back(ReadPolicyConf{});
|
||||||
|
setReadPolicy(dc.readPolicy.back(), n);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Throw(UnknownKey, "%s:%d unknown key %s",
|
||||||
|
p->file, p->line, p->key.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Conf::setReadPolicy(ReadPolicyConf& c, const ConfParser::Node* n)
|
||||||
|
{
|
||||||
|
c.name = n->key;
|
||||||
|
int num = sscanf(n->val.c_str(), "%d %d", &c.priority, &c.weight);
|
||||||
|
if (num == 1) {
|
||||||
|
c.weight = 1;
|
||||||
|
} else if (num != 2) {
|
||||||
|
Throw(InvalidValue, "%s:%d invalid ReadPolicy \"%s\"",
|
||||||
|
n->file, n->line, n->val.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Conf::setLatencyMonitor(LatencyMonitorConf& m, const ConfParser::Node* node)
|
||||||
|
{
|
||||||
|
if (!node->sub) {
|
||||||
|
Throw(InvalidValue, "%s:%d LatencyMonitor require scope value",
|
||||||
|
node->file, node->line);
|
||||||
|
}
|
||||||
|
m.name = node->val;
|
||||||
|
for (auto p = node->sub; p; p = p->next) {
|
||||||
|
if (strcasecmp(p->key.c_str(), "Commands") == 0) {
|
||||||
|
for (auto n = p->sub; n; n = n->next) {
|
||||||
|
bool add = true;
|
||||||
|
if (strcmp(n->key.c_str(), "+") == 0) {
|
||||||
|
} else if (strcmp(n->key.c_str(), "-") == 0) {
|
||||||
|
add = false;
|
||||||
|
} else {
|
||||||
|
Throw(InvalidValue, "%s:%d invalid Command assign",
|
||||||
|
n->file, n->line);
|
||||||
|
}
|
||||||
|
const char* v = n->val.c_str();
|
||||||
|
if (strcasecmp(v, "all") == 0) {
|
||||||
|
add ? m.cmds.set() : m.cmds.reset();
|
||||||
|
} else if (auto c = Command::find(v)) {
|
||||||
|
add ? m.cmds.set(c->type) : m.cmds.reset(c->type);
|
||||||
|
} else {
|
||||||
|
Throw(InvalidValue, "%s:%d unknown Command \"%s\"",
|
||||||
|
n->file, n->line, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (strcasecmp(p->key.c_str(), "TimeSpan") == 0) {
|
||||||
|
long span = 0;
|
||||||
|
for (auto n = p->sub; n; n = n->next) {
|
||||||
|
if (setLong(span, "+", n, span + 1)) {
|
||||||
|
m.timeSpan.push_back(span);
|
||||||
|
} else {
|
||||||
|
Throw(InvalidValue, "%s:%d invalid TimeSpan",
|
||||||
|
n->file, n->line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Conf::check()
|
||||||
|
{
|
||||||
|
#ifdef _PREDIXY_SINGLE_THREAD_
|
||||||
|
if (mWorkerThreads != 1) {
|
||||||
|
Throw(InvalidValue, "WorkerThreads must be 1 in single thread mode");
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if (mWorkerThreads < 1) {
|
||||||
|
Throw(InvalidValue, "WorkerThreads must >= 1");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (mLocalDC.empty()) {
|
||||||
|
if (!mDCConfs.empty()) {
|
||||||
|
Throw(LogicError, "conf define DataCenter but not define LocalDC");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bool found = false;
|
||||||
|
for (auto& dc : mDCConfs) {
|
||||||
|
if (dc.name == mLocalDC) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
Throw(LogicError, "LocalDC \"%s\" no exists in DataCenter",
|
||||||
|
mLocalDC.c_str());
|
||||||
|
}
|
||||||
|
for (auto& dc : mDCConfs) {
|
||||||
|
for (auto& rp : dc.readPolicy) {
|
||||||
|
found = false;
|
||||||
|
for (auto& i :mDCConfs) {
|
||||||
|
if (rp.name == i.name) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
Throw(LogicError, "DC \"%s\" ReadPolicy \"%s\" no eixsts in DataCenter", dc.name.c_str(), rp.name.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Conf::setStr(std::string& attr, const char* name, const ConfParser::Node* n)
|
||||||
|
{
|
||||||
|
if (strcasecmp(name, n->key.c_str()) == 0) {
|
||||||
|
attr = n->val;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Conf::setInt(int& attr, const char* name, const ConfParser::Node* n, int lower, int upper)
|
||||||
|
{
|
||||||
|
if (strcasecmp(name, n->key.c_str()) == 0) {
|
||||||
|
if (sscanf(n->val.c_str(), "%d", &attr) != 1) {
|
||||||
|
Throw(InvalidValue, "%s:%d %s %s is not an integer",
|
||||||
|
n->file, n->line, name, n->val.c_str());
|
||||||
|
}
|
||||||
|
if (attr < lower || attr > upper) {
|
||||||
|
Throw(InvalidValue, "%s:%d %s %s not in range [%d, %d]",
|
||||||
|
n->file, n->line, name, n->val.c_str(), lower, upper);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Conf::setLong(long& attr, const char* name, const ConfParser::Node* n, long lower, long upper)
|
||||||
|
{
|
||||||
|
if (strcasecmp(name, n->key.c_str()) == 0) {
|
||||||
|
if (sscanf(n->val.c_str(), "%ld", &attr) != 1) {
|
||||||
|
Throw(InvalidValue, "%s:%d %s %s is not an long integer",
|
||||||
|
n->file, n->line, name, n->val.c_str());
|
||||||
|
}
|
||||||
|
if (attr < lower || attr > upper) {
|
||||||
|
Throw(InvalidValue, "%s:%d %s %s not in range [%ld, %ld]",
|
||||||
|
n->file, n->line, name, n->val.c_str(), lower, upper);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Conf::setBool(bool& attr, const char* name, const ConfParser::Node* n)
|
||||||
|
{
|
||||||
|
if (strcasecmp(name, n->key.c_str()) == 0) {
|
||||||
|
if (strcasecmp(n->val.c_str(), "true") == 0) {
|
||||||
|
attr = true;
|
||||||
|
} else if (strcasecmp(n->val.c_str(), "false") == 0) {
|
||||||
|
attr = false;
|
||||||
|
} else {
|
||||||
|
Throw(InvalidValue, "%s:%d %s expect true or false",
|
||||||
|
n->file, n->line, name);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Conf::parseMemory(long& m, const char* str)
|
||||||
|
{
|
||||||
|
char u[4];
|
||||||
|
int c = sscanf(str, "%ld%3s", &m, u);
|
||||||
|
if (c == 2 && m > 0) {
|
||||||
|
if (strcasecmp(u, "B") == 0) {
|
||||||
|
} else if (strcasecmp(u, "K") == 0 || strcasecmp(u, "KB") == 0) {
|
||||||
|
m <<= 10;
|
||||||
|
} else if (strcasecmp(u, "M") == 0 || strcasecmp(u, "MB") == 0) {
|
||||||
|
m <<= 20;
|
||||||
|
} else if (strcasecmp(u, "G") == 0 || strcasecmp(u, "GB") == 0) {
|
||||||
|
m <<= 30;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (c != 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return m >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Conf::setMemory(long& m, const char* name, const ConfParser::Node* n)
|
||||||
|
{
|
||||||
|
if (strcasecmp(name, n->key.c_str()) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!parseMemory(m, n->val.c_str())) {
|
||||||
|
Throw(InvalidValue, "%s:%d %s invalid memory value \"%s\"",
|
||||||
|
n->file, n->line, name, n->val.c_str());
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Conf::setServers(std::vector<ServerConf>& servs, const char* name, const ConfParser::Node* p)
|
||||||
|
{
|
||||||
|
if (strcasecmp(p->key.c_str(), name) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!p->sub) {
|
||||||
|
Throw(InvalidValue, "%s:%d %s require scope value", name, p->file, p->line);
|
||||||
|
}
|
||||||
|
for (auto n = p->sub; n; n = n->next) {
|
||||||
|
if (strcasecmp(n->key.c_str(), "+") == 0) {
|
||||||
|
ServerConf s;
|
||||||
|
if (ServerConf::parse(s, n->val.c_str())) {
|
||||||
|
servs.push_back(s);
|
||||||
|
} else {
|
||||||
|
Throw(InvalidValue, "%s:%d invalid server define %s",
|
||||||
|
n->file, n->line, n->val.c_str());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Throw(InvalidValue, "%s:%d invalid server define", n->file, n->line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
221
src/Conf.h
Normal file
221
src/Conf.h
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_CONF_H_
|
||||||
|
#define _PREDIXY_CONF_H_
|
||||||
|
|
||||||
|
#include <limits.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <strings.h>
|
||||||
|
#include <string>
|
||||||
|
#include <map>
|
||||||
|
#include <set>
|
||||||
|
#include <vector>
|
||||||
|
#include <bitset>
|
||||||
|
#include "Predixy.h"
|
||||||
|
#include "Distribution.h"
|
||||||
|
#include "ConfParser.h"
|
||||||
|
#include "Auth.h"
|
||||||
|
|
||||||
|
struct AuthConf
|
||||||
|
{
|
||||||
|
std::string password;
|
||||||
|
int mode; //Command::Mode
|
||||||
|
std::vector<std::string> keyPrefix;
|
||||||
|
std::vector<std::string> readKeyPrefix;
|
||||||
|
std::vector<std::string> writeKeyPrefix;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ServerConf
|
||||||
|
{
|
||||||
|
std::string password;
|
||||||
|
std::string addr;
|
||||||
|
|
||||||
|
static bool parse(ServerConf& s, const char* str);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ServerGroupConf
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
std::vector<ServerConf> servers;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ServerPoolConf
|
||||||
|
{
|
||||||
|
std::string password;
|
||||||
|
int masterReadPriority = 50;
|
||||||
|
int staticSlaveReadPriority = 0;
|
||||||
|
int dynamicSlaveReadPriority = 0;
|
||||||
|
int refreshInterval = 1; //seconds
|
||||||
|
int serverFailureLimit = 10;
|
||||||
|
int serverRetryTimeout = 1; //seconds
|
||||||
|
int databases = 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ClusterServerPoolConf : public ServerPoolConf
|
||||||
|
{
|
||||||
|
std::vector<ServerConf> servers;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SentinelServerPoolConf : public ServerPoolConf
|
||||||
|
{
|
||||||
|
Distribution dist = Distribution::None;
|
||||||
|
Hash hash = Hash::None;
|
||||||
|
char hashTag[2];
|
||||||
|
std::vector<ServerConf> sentinels;
|
||||||
|
std::vector<ServerGroupConf> groups;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ReadPolicyConf
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
int priority;
|
||||||
|
int weight;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DCConf
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
std::vector<std::string> addrPrefix;
|
||||||
|
std::vector<ReadPolicyConf> readPolicy;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LatencyMonitorConf
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
std::bitset<Command::Sentinel> cmds;
|
||||||
|
std::vector<long> timeSpan;//us
|
||||||
|
};
|
||||||
|
|
||||||
|
class Conf
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DefException(InvalidStartArg);
|
||||||
|
DefException(UnknownKey);
|
||||||
|
DefException(InvalidValue);
|
||||||
|
DefException(LogicError);
|
||||||
|
public:
|
||||||
|
Conf();
|
||||||
|
~Conf();
|
||||||
|
bool init(int argc, char* argv[]);
|
||||||
|
const char* name() const
|
||||||
|
{
|
||||||
|
return mName.c_str();
|
||||||
|
}
|
||||||
|
const char* bind() const
|
||||||
|
{
|
||||||
|
return mBind.c_str();
|
||||||
|
}
|
||||||
|
int workerThreads() const
|
||||||
|
{
|
||||||
|
return mWorkerThreads;
|
||||||
|
}
|
||||||
|
long maxMemory() const
|
||||||
|
{
|
||||||
|
return mMaxMemory;
|
||||||
|
}
|
||||||
|
void setClientTimeout(long v)
|
||||||
|
{
|
||||||
|
mClientTimeout = v;
|
||||||
|
}
|
||||||
|
long clientTimeout() const
|
||||||
|
{
|
||||||
|
return mClientTimeout;
|
||||||
|
}
|
||||||
|
int bufSize() const
|
||||||
|
{
|
||||||
|
return mBufSize;
|
||||||
|
}
|
||||||
|
const std::vector<AuthConf>& authConfs() const
|
||||||
|
{
|
||||||
|
return mAuthConfs;
|
||||||
|
}
|
||||||
|
const char* log() const
|
||||||
|
{
|
||||||
|
return mLog.c_str();
|
||||||
|
}
|
||||||
|
int logRotateSecs() const
|
||||||
|
{
|
||||||
|
return mLogRotateSecs;
|
||||||
|
}
|
||||||
|
long logRotateBytes() const
|
||||||
|
{
|
||||||
|
return mLogRotateBytes;
|
||||||
|
}
|
||||||
|
bool allowMissLog() const
|
||||||
|
{
|
||||||
|
return mAllowMissLog;
|
||||||
|
}
|
||||||
|
int logSample(LogLevel::Type lvl) const
|
||||||
|
{
|
||||||
|
return mLogSample[lvl];
|
||||||
|
}
|
||||||
|
int serverPoolType() const
|
||||||
|
{
|
||||||
|
return mServerPoolType;
|
||||||
|
}
|
||||||
|
const ClusterServerPoolConf& clusterServerPool() const
|
||||||
|
{
|
||||||
|
return mClusterServerPool;
|
||||||
|
}
|
||||||
|
const SentinelServerPoolConf& sentinelServerPool() const
|
||||||
|
{
|
||||||
|
return mSentinelServerPool;
|
||||||
|
}
|
||||||
|
const std::string& localDC() const
|
||||||
|
{
|
||||||
|
return mLocalDC;
|
||||||
|
}
|
||||||
|
const std::vector<DCConf>& dcConfs() const
|
||||||
|
{
|
||||||
|
return mDCConfs;
|
||||||
|
}
|
||||||
|
const std::vector<LatencyMonitorConf>& latencyMonitors() const
|
||||||
|
{
|
||||||
|
return mLatencyMonitors;
|
||||||
|
}
|
||||||
|
public:
|
||||||
|
static bool parseMemory(long& m, const char* str);
|
||||||
|
private:
|
||||||
|
void setGlobal(const ConfParser::Node* node);
|
||||||
|
void setAuthority(const ConfParser::Node* node);
|
||||||
|
void setClusterServerPool(const ConfParser::Node* node);
|
||||||
|
void setSentinelServerPool(const ConfParser::Node* node);
|
||||||
|
void setDataCenter(const ConfParser::Node* node);
|
||||||
|
void check();
|
||||||
|
bool setServerPool(ServerPoolConf& sp, const ConfParser::Node* n);
|
||||||
|
bool setStr(std::string& attr, const char* name, const ConfParser::Node* n);
|
||||||
|
bool setInt(int& attr, const char* name, const ConfParser::Node* n, int lower = INT_MIN, int upper = INT_MAX);
|
||||||
|
bool setLong(long& attr, const char* name, const ConfParser::Node* n, long lower = LONG_MIN, long upper = LONG_MAX);
|
||||||
|
bool setBool(bool& attr, const char* name, const ConfParser::Node* n);
|
||||||
|
bool setMemory(long& mem, const char* name, const ConfParser::Node* n);
|
||||||
|
bool setServers(std::vector<ServerConf>& servs, const char* name, const ConfParser::Node* n);
|
||||||
|
void setDC(DCConf& dc, const ConfParser::Node* n);
|
||||||
|
void setReadPolicy(ReadPolicyConf& c, const ConfParser::Node* n);
|
||||||
|
void setLatencyMonitor(LatencyMonitorConf& m, const ConfParser::Node* n);
|
||||||
|
private:
|
||||||
|
std::string mName;
|
||||||
|
std::string mBind;
|
||||||
|
int mWorkerThreads;
|
||||||
|
long mMaxMemory;
|
||||||
|
long mClientTimeout; //us
|
||||||
|
int mBufSize;
|
||||||
|
std::string mLog;
|
||||||
|
int mLogRotateSecs;
|
||||||
|
long mLogRotateBytes;
|
||||||
|
bool mAllowMissLog;
|
||||||
|
int mLogSample[LogLevel::Sentinel];
|
||||||
|
std::vector<AuthConf> mAuthConfs;
|
||||||
|
int mServerPoolType;
|
||||||
|
ClusterServerPoolConf mClusterServerPool;
|
||||||
|
SentinelServerPoolConf mSentinelServerPool;
|
||||||
|
std::vector<DCConf> mDCConfs;
|
||||||
|
std::string mLocalDC;
|
||||||
|
std::vector<LatencyMonitorConf> mLatencyMonitors;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
308
src/ConfParser.cpp
Normal file
308
src/ConfParser.cpp
Normal file
@ -0,0 +1,308 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <strings.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <memory>
|
||||||
|
#include "ConfParser.h"
|
||||||
|
|
||||||
|
ConfParser::ConfParser(int maxNodeDepth):
|
||||||
|
mMaxNodeDepth(maxNodeDepth)
|
||||||
|
{
|
||||||
|
mRoot.next = mRoot.sub = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfParser::~ConfParser()
|
||||||
|
{
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfParser::clear()
|
||||||
|
{
|
||||||
|
if (mRoot.next) {
|
||||||
|
delete mRoot.next;
|
||||||
|
}
|
||||||
|
if (mRoot.sub) {
|
||||||
|
delete mRoot.sub;
|
||||||
|
}
|
||||||
|
mRoot.next = nullptr;
|
||||||
|
mRoot.sub = nullptr;
|
||||||
|
for (auto s : mStrings) {
|
||||||
|
delete s;
|
||||||
|
}
|
||||||
|
mStrings.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string* ConfParser::getFile(const std::string& name, const char* parent)
|
||||||
|
{
|
||||||
|
if (name.empty()) {
|
||||||
|
Throw(EmptyFileName, "filename is empty");
|
||||||
|
}
|
||||||
|
if (name[0] == '/') {
|
||||||
|
return new std::string(name);
|
||||||
|
}
|
||||||
|
std::string ret;
|
||||||
|
char buf[PATH_MAX];
|
||||||
|
if (parent && *parent) {
|
||||||
|
int len = strlen(parent);
|
||||||
|
if (len < (int)sizeof(buf)) {
|
||||||
|
int n = len - 1;
|
||||||
|
while (n >= 0) {
|
||||||
|
if (parent[n] == '/') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
--n;
|
||||||
|
}
|
||||||
|
if (n >= 0) {
|
||||||
|
ret.assign(parent, 0, n + 1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Throw(FileNameTooLong, "parent file name %s too long", parent);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (char* path = getcwd(buf, sizeof(buf))) {
|
||||||
|
ret = path;
|
||||||
|
ret += '/';
|
||||||
|
} else {
|
||||||
|
Throw(GetCwdFail, "get current directory fail %s", StrError());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ret += name;
|
||||||
|
std::string* r = new std::string();
|
||||||
|
r->swap(ret);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct File
|
||||||
|
{
|
||||||
|
const char* name;
|
||||||
|
std::shared_ptr<std::ifstream> stream;
|
||||||
|
int line;
|
||||||
|
};
|
||||||
|
|
||||||
|
ConfParser::Node* ConfParser::load(const char* file)
|
||||||
|
{
|
||||||
|
clear();
|
||||||
|
std::string* name = getFile(file, nullptr);
|
||||||
|
mStrings.push_back(name);
|
||||||
|
std::shared_ptr<std::ifstream> s(new std::ifstream(name->c_str()));
|
||||||
|
if (!s->good()) {
|
||||||
|
Throw(OpenFileFail, "open file %s fail %s", name->c_str(), StrError());
|
||||||
|
}
|
||||||
|
std::vector<File> files;
|
||||||
|
files.push_back(File{name->c_str(), s, 0});
|
||||||
|
std::string line;
|
||||||
|
std::string key;
|
||||||
|
std::string val;
|
||||||
|
std::vector<Node*> stack;
|
||||||
|
Node* node = &mRoot;
|
||||||
|
while (!files.empty()) {
|
||||||
|
File* f = &files.back();
|
||||||
|
while (std::getline(*f->stream, line)) {
|
||||||
|
f->line += 1;
|
||||||
|
Status st = parse(line, key, val);
|
||||||
|
switch (st) {
|
||||||
|
case None:
|
||||||
|
break;
|
||||||
|
case KeyVal:
|
||||||
|
if (strcasecmp(key.c_str(), "Include") == 0) {
|
||||||
|
name = getFile(val, f->name);
|
||||||
|
mStrings.push_back(name);
|
||||||
|
for (auto& pre : files) {
|
||||||
|
if (strcmp(pre.name, name->c_str()) == 0) {
|
||||||
|
Throw(IncludeFileDuplicate, "include file %s duplicate in %s:%d",
|
||||||
|
name->c_str(), f->name, f->line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s = decltype(s)(new std::ifstream(name->c_str()));
|
||||||
|
if (!s->good()) {
|
||||||
|
Throw(OpenFileFail, "open file %s fail %s", name->c_str(), StrError());
|
||||||
|
}
|
||||||
|
files.push_back(File{name->c_str(), s, 0});
|
||||||
|
f = &files.back();
|
||||||
|
} else {
|
||||||
|
auto n = new Node{f->name, f->line, key, val, nullptr, nullptr};
|
||||||
|
if (node) {
|
||||||
|
node->next = n;
|
||||||
|
} else if (!stack.empty()) {
|
||||||
|
stack.back()->sub = n;
|
||||||
|
}
|
||||||
|
node = n;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case BeginScope:
|
||||||
|
if ((int)stack.size() == mMaxNodeDepth) {
|
||||||
|
Throw(NodeDepthTooLong, "node depth limit %d in %s:%d",
|
||||||
|
mMaxNodeDepth, f->name, f->line);
|
||||||
|
} else {
|
||||||
|
auto n = new Node{f->name, f->line, key, val, nullptr, nullptr};
|
||||||
|
if (node) {
|
||||||
|
node->next = n;
|
||||||
|
} else if (!stack.empty()) {
|
||||||
|
stack.back()->sub = n;
|
||||||
|
}
|
||||||
|
stack.push_back(n);
|
||||||
|
node = nullptr;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EndScope:
|
||||||
|
if (stack.empty()) {
|
||||||
|
Throw(ParseError, "parse error unmatched end scope in %s:%d", f->name, f->line);
|
||||||
|
} else {
|
||||||
|
node = stack.back();
|
||||||
|
stack.resize(stack.size() - 1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Error:
|
||||||
|
default:
|
||||||
|
Throw(ParseError, "parse error in %s:%d", f->name, f->line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (f->stream->eof()) {
|
||||||
|
files.resize(files.size() - 1);
|
||||||
|
} else if (f->stream->bad()) {
|
||||||
|
Throw(FileReadError, "read file %s error", f->name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!stack.empty()) {
|
||||||
|
Throw(ParseError, "parse error some scope no end %s:%d",
|
||||||
|
stack.back()->file, stack.back()->line);
|
||||||
|
}
|
||||||
|
return mRoot.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfParser::Status ConfParser::parse(std::string& line, std::string& key, std::string& val)
|
||||||
|
{
|
||||||
|
key.clear();
|
||||||
|
val.clear();
|
||||||
|
State s = KeyReady;
|
||||||
|
std::string::size_type i = 0;
|
||||||
|
std::string::size_type pos = 0;
|
||||||
|
bool escape = false;
|
||||||
|
for (i = 0; i < line.size(); ++i) {
|
||||||
|
char c = line[i];
|
||||||
|
switch (s) {
|
||||||
|
case KeyReady:
|
||||||
|
if (c == '#' || c == '\n') {
|
||||||
|
goto Done;
|
||||||
|
} else if (c != ' ' && c != '\t' && c != '\r') {
|
||||||
|
pos = i;
|
||||||
|
s = KeyBody;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case KeyBody:
|
||||||
|
if (c == '#' || c == '\n') {
|
||||||
|
line.resize(i);
|
||||||
|
goto Done;
|
||||||
|
} else if (c == ' ' || c == '\t') {
|
||||||
|
key.assign(line, pos, i - pos);
|
||||||
|
s = ValReady;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ValReady:
|
||||||
|
if (c == '#' || c == '\n') {
|
||||||
|
goto Done;
|
||||||
|
} else if (c != ' ' && c != '\t' && c != '\r') {
|
||||||
|
pos = i;
|
||||||
|
if (c == '"') {
|
||||||
|
val.resize(line.size() - i);
|
||||||
|
val.resize(0);
|
||||||
|
s = SValBody;
|
||||||
|
} else {
|
||||||
|
s = VValBody;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SValBody:
|
||||||
|
if (c == '\\') {
|
||||||
|
if (escape) {
|
||||||
|
escape = false;
|
||||||
|
val += c;
|
||||||
|
} else {
|
||||||
|
escape = true;
|
||||||
|
}
|
||||||
|
} else if (c == '"') {
|
||||||
|
if (escape) {
|
||||||
|
escape = false;
|
||||||
|
val += c;
|
||||||
|
} else {
|
||||||
|
s = ScopeReady;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val += c;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case VValBody:
|
||||||
|
if (c == '#' || c == '\n') {
|
||||||
|
line.resize(i);
|
||||||
|
goto Done;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ScopeReady:
|
||||||
|
if (c == '#' || c == '\n') {
|
||||||
|
goto Done;
|
||||||
|
} else if (c == '{') {
|
||||||
|
s = ScopeBody;
|
||||||
|
} else if (c != ' ' && c != '\t' && c != '\r') {
|
||||||
|
return Error;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ScopeBody:
|
||||||
|
if (c == '#' || c == '\n') {
|
||||||
|
goto Done;
|
||||||
|
} else if (c != ' ' && c != '\t' && c != '\r') {
|
||||||
|
return Error;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Done:
|
||||||
|
switch (s) {
|
||||||
|
case KeyReady:
|
||||||
|
break;
|
||||||
|
case KeyBody:
|
||||||
|
key.assign(line, pos, line.size() - pos);
|
||||||
|
if (key.size() == 1 && key[0] == '}') {
|
||||||
|
return EndScope;
|
||||||
|
}
|
||||||
|
return KeyVal;
|
||||||
|
case ValReady:
|
||||||
|
if (key.size() == 1 && key[0] == '}') {
|
||||||
|
return EndScope;
|
||||||
|
}
|
||||||
|
return KeyVal;
|
||||||
|
case SValBody:
|
||||||
|
return KeyVal;
|
||||||
|
case VValBody:
|
||||||
|
val.assign(line, pos, line.size() - pos);
|
||||||
|
if (val.back() == '{') {
|
||||||
|
val.resize(val.size() - 1);
|
||||||
|
int vsp = 0;
|
||||||
|
for (auto it = val.rbegin(); it != val.rend(); ++it) {
|
||||||
|
if (isspace(*it)) {
|
||||||
|
++vsp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val.resize(val.size() - vsp);
|
||||||
|
return BeginScope;
|
||||||
|
} else {
|
||||||
|
return KeyVal;
|
||||||
|
}
|
||||||
|
case ScopeReady:
|
||||||
|
return KeyVal;
|
||||||
|
case ScopeBody:
|
||||||
|
return BeginScope;
|
||||||
|
default:
|
||||||
|
return Error;
|
||||||
|
}
|
||||||
|
return None;
|
||||||
|
}
|
||||||
86
src/ConfParser.h
Normal file
86
src/ConfParser.h
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_CONF_PARSER_H_
|
||||||
|
#define _PREDIXY_CONF_PARSER_H_
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <string>
|
||||||
|
#include <set>
|
||||||
|
#include <vector>
|
||||||
|
#include <fstream>
|
||||||
|
#include "Exception.h"
|
||||||
|
#include "Util.h"
|
||||||
|
|
||||||
|
class ConfParser
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DefException(EmptyFileName);
|
||||||
|
DefException(FileNameTooLong);
|
||||||
|
DefException(GetCwdFail);
|
||||||
|
DefException(OpenFileFail);
|
||||||
|
DefException(IncludeFileDuplicate);
|
||||||
|
DefException(NodeDepthTooLong);
|
||||||
|
DefException(FileReadError);
|
||||||
|
DefException(ParseError);
|
||||||
|
struct Node
|
||||||
|
{
|
||||||
|
const char* file;
|
||||||
|
int line;
|
||||||
|
std::string key;
|
||||||
|
std::string val;
|
||||||
|
Node* next;
|
||||||
|
Node* sub;
|
||||||
|
|
||||||
|
~Node()
|
||||||
|
{
|
||||||
|
if (sub) {
|
||||||
|
delete sub;
|
||||||
|
}
|
||||||
|
Node* n = next;
|
||||||
|
while (n) {
|
||||||
|
Node* nn = n->next;
|
||||||
|
n->next = nullptr;
|
||||||
|
delete n;
|
||||||
|
n = nn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum Status
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
KeyVal,
|
||||||
|
BeginScope,
|
||||||
|
EndScope,
|
||||||
|
Error
|
||||||
|
};
|
||||||
|
enum State
|
||||||
|
{
|
||||||
|
KeyReady,
|
||||||
|
KeyBody,
|
||||||
|
ValReady,
|
||||||
|
SValBody,
|
||||||
|
VValBody,
|
||||||
|
ScopeReady,
|
||||||
|
ScopeBody
|
||||||
|
};
|
||||||
|
public:
|
||||||
|
ConfParser(int maxNodeDepth = 10);
|
||||||
|
~ConfParser();
|
||||||
|
void clear();
|
||||||
|
Node* load(const char* file);
|
||||||
|
private:
|
||||||
|
static std::string* getFile(const std::string& name, const char* parent);
|
||||||
|
Status parse(std::string& line, std::string& key, std::string& val);
|
||||||
|
private:
|
||||||
|
Node mRoot;
|
||||||
|
int mMaxNodeDepth;
|
||||||
|
std::vector<std::string*> mStrings;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
242
src/ConnectConnection.cpp
Normal file
242
src/ConnectConnection.cpp
Normal file
@ -0,0 +1,242 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ConnectConnection.h"
|
||||||
|
#include "Handler.h"
|
||||||
|
#include "Subscribe.h"
|
||||||
|
|
||||||
|
|
||||||
|
ConnectConnection::ConnectConnection(Server* serv, bool shared):
|
||||||
|
ConnectSocket(serv->addr().data(), SOCK_STREAM),
|
||||||
|
mServ(serv),
|
||||||
|
mAcceptConnection(nullptr),
|
||||||
|
mShared(shared),
|
||||||
|
mAuthed(false),
|
||||||
|
mReadonly(false)
|
||||||
|
{
|
||||||
|
mClassType = Connection::ConnectType;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConnectConnection::~ConnectConnection()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ConnectConnection::writeEvent(Handler* h)
|
||||||
|
{
|
||||||
|
FuncCallTimer();
|
||||||
|
if (connectStatus() == Connecting) {
|
||||||
|
logInfo("h %d s %s %d connect succ",
|
||||||
|
h->id(), peer(), fd());
|
||||||
|
setConnectStatus(Connected);
|
||||||
|
}
|
||||||
|
IOVec bufs[Const::MaxIOVecLen];
|
||||||
|
bool finished = true;
|
||||||
|
while (true) {
|
||||||
|
int len = fill(h, bufs, Const::MaxIOVecLen);
|
||||||
|
if (len == 0) {
|
||||||
|
finished = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
finished = write(h, bufs, len);
|
||||||
|
if (!finished || len < Const::MaxIOVecLen) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return finished;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ConnectConnection::fill(Handler* h, IOVec* bufs, int len)
|
||||||
|
{
|
||||||
|
FuncCallTimer();
|
||||||
|
int cnt = 0;
|
||||||
|
for (auto req = mSendRequests.front(); req; req = mSendRequests.next(req)) {
|
||||||
|
int n = req->fill(bufs, len);
|
||||||
|
bufs += n;
|
||||||
|
cnt += n;
|
||||||
|
len -= n;
|
||||||
|
if (len == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cnt;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ConnectConnection::write(Handler* h, IOVec* bufs, int len)
|
||||||
|
{
|
||||||
|
FuncCallTimer();
|
||||||
|
logDebug("h %d s %s %d writev %d",
|
||||||
|
h->id(), peer(), fd(), len);
|
||||||
|
struct iovec iov[Const::MaxIOVecLen];
|
||||||
|
for (int i = 0; i < len; ++i) {
|
||||||
|
iov[i].iov_base = bufs[i].dat;
|
||||||
|
iov[i].iov_len = bufs[i].len;
|
||||||
|
}
|
||||||
|
int num = writev(iov, len);
|
||||||
|
if (num < 0) {
|
||||||
|
logWarn("h %d s %s %d writev %d fail %s",
|
||||||
|
h->id(), peer(), fd(), len, StrError());
|
||||||
|
return false;
|
||||||
|
} else if (num == 0) {
|
||||||
|
return len == 0;
|
||||||
|
}
|
||||||
|
h->addServerWriteStats(mServ, num);
|
||||||
|
IOVec* vec = nullptr;
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < len && num > 0; ++i) {
|
||||||
|
vec = bufs + i;
|
||||||
|
int n = vec->len;
|
||||||
|
vec->seg->seek(vec->buf, vec->pos, n <= num ? n : num);
|
||||||
|
if (vec->req && n <= num) {
|
||||||
|
while (!mSendRequests.empty()) {
|
||||||
|
RequestPtr req = mSendRequests.front();
|
||||||
|
mSendRequests.pop_front();
|
||||||
|
if (vec->req == req) {
|
||||||
|
mSentRequests.push_back(req);
|
||||||
|
logVerb("h %d s %s %d req %ld sent",
|
||||||
|
h->id(), peer(), fd(), req->id());
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
logNotice("h %d s %s %d req %ld is empty",
|
||||||
|
h->id(), peer(), fd(), req->id());
|
||||||
|
h->handleResponse(this, req, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
num -= n;
|
||||||
|
}
|
||||||
|
return i == len && num == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConnectConnection::readEvent(Handler* h)
|
||||||
|
{
|
||||||
|
FuncCallTimer();
|
||||||
|
while (true) {
|
||||||
|
Buffer* buf = getBuffer(h, mParser.isIdle());
|
||||||
|
int pos = buf->length();
|
||||||
|
int len = buf->room();
|
||||||
|
int n = read(buf->tail(), len);
|
||||||
|
if (n > 0) {
|
||||||
|
logVerb("h %d s %s %d read bytes %d",
|
||||||
|
h->id(), peer(), fd(), n);
|
||||||
|
buf->use(n);
|
||||||
|
h->addServerReadStats(mServ, n);
|
||||||
|
parse(h, buf, pos);
|
||||||
|
if (n < len) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!good()) {
|
||||||
|
logWarn("h %d s %s %d read error %s",
|
||||||
|
h->id(), peer(), fd(), StrError());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConnectConnection::parse(Handler* h, Buffer* buf, int pos)
|
||||||
|
{
|
||||||
|
FuncCallTimer();
|
||||||
|
bool goOn = true;
|
||||||
|
while (pos < buf->length() && goOn) {
|
||||||
|
auto ret = mParser.parse(buf, pos);
|
||||||
|
switch (ret) {
|
||||||
|
case ResponseParser::Normal:
|
||||||
|
case ResponseParser::Partial:
|
||||||
|
goOn = false;
|
||||||
|
break;
|
||||||
|
case ResponseParser::Complete:
|
||||||
|
handleResponse(h);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
setStatus(ParseError);
|
||||||
|
goOn = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConnectConnection::handleResponse(Handler* h)
|
||||||
|
{
|
||||||
|
FuncCallTimer();
|
||||||
|
if (mAcceptConnection) {
|
||||||
|
if (mAcceptConnection->inSub(true)) {
|
||||||
|
int chs;
|
||||||
|
switch (SubscribeParser::parse(mParser.response(), chs)) {
|
||||||
|
case SubscribeParser::Subscribe:
|
||||||
|
case SubscribeParser::Psubscribe:
|
||||||
|
mAcceptConnection->decrPendSub();
|
||||||
|
//NO break; let it continue to setSub
|
||||||
|
case SubscribeParser::Unsubscribe:
|
||||||
|
case SubscribeParser::Punsubscribe:
|
||||||
|
if (chs < 0) {
|
||||||
|
setStatus(LogicError);
|
||||||
|
logError("h %d s %s %d parse subscribe response error",
|
||||||
|
h->id(), peer(), fd());
|
||||||
|
} else {
|
||||||
|
mAcceptConnection->setSub(chs);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SubscribeParser::Message:
|
||||||
|
case SubscribeParser::Pmessage:
|
||||||
|
{
|
||||||
|
RequestPtr req = RequestAlloc::create(mAcceptConnection);
|
||||||
|
req->setType(Command::SubMsg);
|
||||||
|
mAcceptConnection->append(req);
|
||||||
|
mSentRequests.push_front(req);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (Request* req = mSentRequests.front()) {
|
||||||
|
switch (req->type()) {
|
||||||
|
case Command::Psubscribe:
|
||||||
|
case Command::Subscribe:
|
||||||
|
mAcceptConnection->decrPendSub();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Request* req = mSentRequests.front()) {
|
||||||
|
ResponsePtr res = ResponseAlloc::create();
|
||||||
|
res->set(mParser);
|
||||||
|
mParser.reset();
|
||||||
|
logDebug("h %d s %s %d create res %ld match req %ld",
|
||||||
|
h->id(), peer(), fd(), res->id(), req->id());
|
||||||
|
h->handleResponse(this, req, res);
|
||||||
|
mSentRequests.pop_front();
|
||||||
|
} else {
|
||||||
|
logNotice("h %d s %s %d recv res but no req",
|
||||||
|
h->id(), peer(), fd());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConnectConnection::send(Handler* h, Request* req)
|
||||||
|
{
|
||||||
|
FuncCallTimer();
|
||||||
|
mSendRequests.push_back(req);
|
||||||
|
logDebug("h %d s %s %d pend req %ld",
|
||||||
|
h->id(), peer(), fd(), req->id());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConnectConnection::close(Handler* h)
|
||||||
|
{
|
||||||
|
SendRequestList* reqs[2] = {&mSentRequests, &mSendRequests};
|
||||||
|
for (int i = 0; i < 2; ++i) {
|
||||||
|
while (!reqs[i]->empty()) {
|
||||||
|
auto req = reqs[i]->front();
|
||||||
|
h->directResponse(req, Response::ServerConnectionClose, this);
|
||||||
|
reqs[i]->pop_front();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ConnectSocket::close();
|
||||||
|
mParser.reset();
|
||||||
|
}
|
||||||
|
|
||||||
94
src/ConnectConnection.h
Normal file
94
src/ConnectConnection.h
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_CONNECT_CONNECTION_H_
|
||||||
|
#define _PREDIXY_CONNECT_CONNECTION_H_
|
||||||
|
|
||||||
|
#include "Predixy.h"
|
||||||
|
#include "ConnectSocket.h"
|
||||||
|
#include "Connection.h"
|
||||||
|
#include "Request.h"
|
||||||
|
#include "ResponseParser.h"
|
||||||
|
|
||||||
|
class ConnectConnection :
|
||||||
|
public ConnectSocket,
|
||||||
|
public Connection,
|
||||||
|
public ListNode<ConnectConnection>,
|
||||||
|
public DequeNode<ConnectConnection>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
typedef ConnectConnection Value;
|
||||||
|
typedef ListNode<ConnectConnection> ListNodeType;
|
||||||
|
typedef DequeNode<ConnectConnection> DequeNodeType;
|
||||||
|
public:
|
||||||
|
ConnectConnection(Server* s, bool shared);
|
||||||
|
~ConnectConnection();
|
||||||
|
bool writeEvent(Handler* h);
|
||||||
|
void readEvent(Handler* h);
|
||||||
|
void send(Handler* h, Request* req);
|
||||||
|
void close(Handler* h);
|
||||||
|
Server* server() const
|
||||||
|
{
|
||||||
|
return mServ;
|
||||||
|
}
|
||||||
|
bool isShared() const
|
||||||
|
{
|
||||||
|
return mShared;
|
||||||
|
}
|
||||||
|
bool isAuth() const
|
||||||
|
{
|
||||||
|
return mAuthed;
|
||||||
|
}
|
||||||
|
void setAuth(bool v)
|
||||||
|
{
|
||||||
|
mAuthed = v;
|
||||||
|
}
|
||||||
|
bool readonly() const
|
||||||
|
{
|
||||||
|
return mReadonly;
|
||||||
|
}
|
||||||
|
void setReadonly(bool v)
|
||||||
|
{
|
||||||
|
mReadonly = v;
|
||||||
|
}
|
||||||
|
void attachAcceptConnection(AcceptConnection* c)
|
||||||
|
{
|
||||||
|
mAcceptConnection = c;
|
||||||
|
}
|
||||||
|
void detachAcceptConnection()
|
||||||
|
{
|
||||||
|
mAcceptConnection = nullptr;
|
||||||
|
}
|
||||||
|
AcceptConnection* acceptConnection() const
|
||||||
|
{
|
||||||
|
return mAcceptConnection;
|
||||||
|
}
|
||||||
|
int pendRequestCount() const
|
||||||
|
{
|
||||||
|
return mSendRequests.size() + mSentRequests.size();
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
void parse(Handler* h, Buffer* buf, int pos);
|
||||||
|
void handleResponse(Handler* h);
|
||||||
|
void write();
|
||||||
|
int fill(Handler* h, IOVec* bufs, int len);
|
||||||
|
bool write(Handler* h, IOVec* bufs, int len);
|
||||||
|
private:
|
||||||
|
Server* mServ;
|
||||||
|
AcceptConnection* mAcceptConnection;
|
||||||
|
ResponseParser mParser;
|
||||||
|
SendRequestList mSendRequests;
|
||||||
|
SendRequestList mSentRequests;
|
||||||
|
bool mShared;
|
||||||
|
bool mAuthed;
|
||||||
|
bool mReadonly;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef List<ConnectConnection> ConnectConnectionList;
|
||||||
|
typedef Deque<ConnectConnection> ConnectConnectionDeque;
|
||||||
|
typedef Alloc<ConnectConnection, Const::ConnectConnectionAllocCacheSize> ConnectConnectionAlloc;
|
||||||
|
|
||||||
|
#endif
|
||||||
205
src/ConnectConnectionPool.cpp
Normal file
205
src/ConnectConnectionPool.cpp
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Proxy.h"
|
||||||
|
#include "ConnectConnectionPool.h"
|
||||||
|
|
||||||
|
ConnectConnectionPool::ConnectConnectionPool(Handler* h, Server* s, int dbnum):
|
||||||
|
mHandler(h),
|
||||||
|
mServ(s),
|
||||||
|
mPendRequests(0),
|
||||||
|
mShareConns(dbnum),
|
||||||
|
mPrivateConns(dbnum),
|
||||||
|
mLatencyMonitors(h->latencyMonitors())
|
||||||
|
{
|
||||||
|
resetStats();
|
||||||
|
}
|
||||||
|
|
||||||
|
ConnectConnectionPool::~ConnectConnectionPool()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
ConnectConnection* ConnectConnectionPool::getShareConnection(int db)
|
||||||
|
{
|
||||||
|
FuncCallTimer();
|
||||||
|
if (db >= (int)mShareConns.size()) {
|
||||||
|
logWarn("h %d get share connection for db %d >= %d",
|
||||||
|
mHandler->id(), db, (int)mShareConns.size());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
ConnectConnection* c = mShareConns[db];
|
||||||
|
if (!c) {
|
||||||
|
c = ConnectConnectionAlloc::create(mServ, true);
|
||||||
|
c->setDb(db);
|
||||||
|
++mStats.connections;
|
||||||
|
mShareConns[db] = c;
|
||||||
|
logNotice("h %d create server connection %s %d",
|
||||||
|
mHandler->id(), c->peer(), c->fd());
|
||||||
|
} else if (c->fd() < 0) {
|
||||||
|
if (mServ->fail()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
c->reopen();
|
||||||
|
logNotice("h %d reopen server connection %s %d",
|
||||||
|
mHandler->id(), c->peer(), c->fd());
|
||||||
|
} else {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
if (!init(c)) {
|
||||||
|
c->close(mHandler);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConnectConnection* ConnectConnectionPool::getPrivateConnection(int db)
|
||||||
|
{
|
||||||
|
FuncCallTimer();
|
||||||
|
if (db >= (int)mPrivateConns.size()) {
|
||||||
|
logWarn("h %d get private connection for db %d >= %d",
|
||||||
|
mHandler->id(), db, (int)mPrivateConns.size());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
auto& ccl = mPrivateConns[db];
|
||||||
|
ConnectConnection* c = ccl.pop_front();
|
||||||
|
bool needInit = false;
|
||||||
|
if (!c) {
|
||||||
|
if (mServ->fail()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
c = ConnectConnectionAlloc::create(mServ, false);
|
||||||
|
c->setDb(db);
|
||||||
|
++mStats.connections;
|
||||||
|
needInit = true;
|
||||||
|
logNotice("h %d create private server connection %s %d",
|
||||||
|
mHandler->id(), c->peer(), c->fd());
|
||||||
|
}
|
||||||
|
if (c->fd() < 0) {
|
||||||
|
if (mServ->fail()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
c->reopen();
|
||||||
|
needInit = true;
|
||||||
|
logNotice("h %d reopen server connection %s %d",
|
||||||
|
mHandler->id(), c->peer(), c->fd());
|
||||||
|
}
|
||||||
|
if (needInit && !init(c)) {
|
||||||
|
c->close(mHandler);
|
||||||
|
ccl.push_back(c);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConnectConnectionPool::putPrivateConnection(ConnectConnection* s)
|
||||||
|
{
|
||||||
|
logDebug("h %d put private connection s %s %d",
|
||||||
|
mHandler->id(), s->peer(), s->fd());
|
||||||
|
unsigned db = s->db();
|
||||||
|
if (db < mPrivateConns.size()) {
|
||||||
|
mPrivateConns[db].push_back(s);
|
||||||
|
} else {
|
||||||
|
logWarn("h %d s %s %d put to pool with db %d invalid",
|
||||||
|
mHandler->id(), s->peer(), s->fd(), s->db());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConnectConnectionPool::putTransactionConnection(ConnectConnection* s, bool inWatch, bool inMulti)
|
||||||
|
{
|
||||||
|
if (s->good()) {
|
||||||
|
if (inMulti) {
|
||||||
|
RequestPtr req = RequestAlloc::create(Request::DiscardServ);
|
||||||
|
mHandler->handleRequest(req, s);
|
||||||
|
logDebug("h %d s %s %d discard req %ld",
|
||||||
|
mHandler->id(), s->peer(), s->fd(), req->id());
|
||||||
|
} else if (inWatch) {
|
||||||
|
RequestPtr req = RequestAlloc::create(Request::UnwatchServ);
|
||||||
|
mHandler->handleRequest(req, s);
|
||||||
|
logDebug("h %d s %s %d unwatch req %ld",
|
||||||
|
mHandler->id(), s->peer(), s->fd(), req->id());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
putPrivateConnection(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ConnectConnectionPool::init(ConnectConnection* c)
|
||||||
|
{
|
||||||
|
if (!c->setNonBlock()) {
|
||||||
|
logWarn("h %d s %s %d set non block fail",
|
||||||
|
mHandler->id(), c->peer(), c->fd());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!c->setTcpNoDelay()) {
|
||||||
|
logWarn("h %d s %s %d settcpnodelay fail %s",
|
||||||
|
mHandler->id(), c->peer(), c->fd(), StrError());
|
||||||
|
}
|
||||||
|
auto m = mHandler->eventLoop();
|
||||||
|
if (!m->addSocket(c, Multiplexor::ReadEvent|Multiplexor::WriteEvent)) {
|
||||||
|
logWarn("h %d s %s %d add to eventloop fail",
|
||||||
|
mHandler->id(), c->peer(), c->fd());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
++mStats.connect;
|
||||||
|
if (!c->connect()) {
|
||||||
|
logWarn("h %d s %s %d connect fail",
|
||||||
|
mHandler->id(), c->peer(), c->fd());
|
||||||
|
m->delSocket(c);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (mServ->password().empty()) {
|
||||||
|
c->setAuth(true);
|
||||||
|
} else {
|
||||||
|
c->setAuth(false);
|
||||||
|
RequestPtr req = RequestAlloc::create();
|
||||||
|
req->setAuth(mServ->password());
|
||||||
|
mHandler->handleRequest(req, c);
|
||||||
|
logDebug("h %d s %s %d auth req %ld",
|
||||||
|
mHandler->id(), c->peer(), c->fd(), req->id());
|
||||||
|
}
|
||||||
|
auto sp = mHandler->proxy()->serverPool();
|
||||||
|
if (sp->type() == ServerPool::Cluster) {
|
||||||
|
RequestPtr req = RequestAlloc::create(Request::Readonly);
|
||||||
|
mHandler->handleRequest(req, c);
|
||||||
|
logDebug("h %d s %s %d readonly req %ld",
|
||||||
|
mHandler->id(), c->peer(), c->fd(), req->id());
|
||||||
|
}
|
||||||
|
int db = c->db();
|
||||||
|
if (db != 0) {
|
||||||
|
RequestPtr req = RequestAlloc::create();
|
||||||
|
req->setSelect(db);
|
||||||
|
mHandler->handleRequest(req, c);
|
||||||
|
logDebug("h %d s %s %d select %d req %ld",
|
||||||
|
mHandler->id(), c->peer(), c->fd(), db, req->id());
|
||||||
|
}
|
||||||
|
RequestPtr req = RequestAlloc::create(Request::PingServ);
|
||||||
|
mHandler->handleRequest(req, c);
|
||||||
|
logDebug("h %d s %s %d ping req %ld",
|
||||||
|
mHandler->id(), c->peer(), c->fd(), req->id());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConnectConnectionPool::check()
|
||||||
|
{
|
||||||
|
FuncCallTimer();
|
||||||
|
if (!mServ->fail() || !mServ->online()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mServ->activate()) {
|
||||||
|
auto c = mShareConns.empty() ? nullptr : mShareConns[0];
|
||||||
|
if (!c) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (c->fd() >= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
c->reopen();
|
||||||
|
logNotice("h %d check server reopen connection %s %d",
|
||||||
|
mHandler->id(), c->peer(), c->fd());
|
||||||
|
if (!init(c)) {
|
||||||
|
c->close(mHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
81
src/ConnectConnectionPool.h
Normal file
81
src/ConnectConnectionPool.h
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_CONNECT_CONNECTION_POOL_H_
|
||||||
|
#define _PREDIXY_CONNECT_CONNECTION_POOL_H_
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include "ConnectConnection.h"
|
||||||
|
#include "Server.h"
|
||||||
|
#include "Handler.h"
|
||||||
|
#include "Request.h"
|
||||||
|
#include "Predixy.h"
|
||||||
|
#include "Stats.h"
|
||||||
|
#include "LatencyMonitor.h"
|
||||||
|
|
||||||
|
class ConnectConnectionPool
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ConnectConnectionPool(Handler* h, Server* s, int dbnum);
|
||||||
|
~ConnectConnectionPool();
|
||||||
|
ConnectConnection* getShareConnection(int db=0);
|
||||||
|
ConnectConnection* getPrivateConnection(int db=0);
|
||||||
|
void putPrivateConnection(ConnectConnection* s);
|
||||||
|
void putTransactionConnection(ConnectConnection* s, bool inWatch, bool inMulti);
|
||||||
|
void check();
|
||||||
|
Server* server() const
|
||||||
|
{
|
||||||
|
return mServ;
|
||||||
|
}
|
||||||
|
int pendRequests()
|
||||||
|
{
|
||||||
|
return mPendRequests;
|
||||||
|
}
|
||||||
|
int incrPendRequests()
|
||||||
|
{
|
||||||
|
return ++mPendRequests;
|
||||||
|
}
|
||||||
|
int decrPendRequests()
|
||||||
|
{
|
||||||
|
return --mPendRequests;
|
||||||
|
}
|
||||||
|
ServerStats& stats()
|
||||||
|
{
|
||||||
|
return mStats;
|
||||||
|
}
|
||||||
|
const ServerStats& stats() const
|
||||||
|
{
|
||||||
|
return mStats;
|
||||||
|
}
|
||||||
|
std::vector<LatencyMonitor>& latencyMonitors()
|
||||||
|
{
|
||||||
|
return mLatencyMonitors;
|
||||||
|
}
|
||||||
|
const std::vector<LatencyMonitor>& latencyMonitors() const
|
||||||
|
{
|
||||||
|
return mLatencyMonitors;
|
||||||
|
}
|
||||||
|
void resetStats()
|
||||||
|
{
|
||||||
|
mStats.reset();
|
||||||
|
for (auto& m : mLatencyMonitors) {
|
||||||
|
m.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
bool init(ConnectConnection* c);
|
||||||
|
private:
|
||||||
|
Handler* mHandler;
|
||||||
|
Server* mServ;
|
||||||
|
int mPendRequests;
|
||||||
|
std::vector<ConnectConnection*> mShareConns;
|
||||||
|
std::vector<ConnectConnectionList> mPrivateConns;
|
||||||
|
ServerStats mStats;
|
||||||
|
std::vector<LatencyMonitor> mLatencyMonitors;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
66
src/ConnectSocket.cpp
Normal file
66
src/ConnectSocket.cpp
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ConnectSocket.h"
|
||||||
|
|
||||||
|
|
||||||
|
ConnectSocket::ConnectSocket(const char* peer, int type, int protocol):
|
||||||
|
mPeer(peer),
|
||||||
|
mType(type),
|
||||||
|
mProtocol(protocol)
|
||||||
|
{
|
||||||
|
mClassType = ConnectType;
|
||||||
|
mPeerAddrLen = sizeof(mPeerAddr);
|
||||||
|
getFirstAddr(peer, type, protocol, (sockaddr*)&mPeerAddr, &mPeerAddrLen);
|
||||||
|
sockaddr* in = (sockaddr*)&mPeerAddr;
|
||||||
|
int fd = Socket::socket(in->sa_family, type, protocol);
|
||||||
|
attach(fd);
|
||||||
|
mStatus = Unconnected;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ConnectSocket::connect()
|
||||||
|
{
|
||||||
|
if (mStatus == Connecting || mStatus == Connected) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool retry;
|
||||||
|
do {
|
||||||
|
retry = false;
|
||||||
|
int ret = ::connect(fd(), (const sockaddr*)&mPeerAddr, mPeerAddrLen);
|
||||||
|
if (ret == 0) {
|
||||||
|
mStatus = Connected;
|
||||||
|
} else {
|
||||||
|
if (errno == EINPROGRESS || errno == EALREADY) {
|
||||||
|
mStatus = Connecting;
|
||||||
|
} else if (errno == EISCONN) {
|
||||||
|
mStatus = Connected;
|
||||||
|
} else if (errno == EINTR) {
|
||||||
|
retry = true;
|
||||||
|
} else {
|
||||||
|
mStatus = Unconnected;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (retry);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConnectSocket::reopen()
|
||||||
|
{
|
||||||
|
if (fd() >= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sockaddr* in = (sockaddr*)&mPeerAddr;
|
||||||
|
int fd = Socket::socket(in->sa_family, mType, mProtocol);
|
||||||
|
attach(fd);
|
||||||
|
mStatus = Unconnected;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConnectSocket::close()
|
||||||
|
{
|
||||||
|
mStatus = Disconnected;
|
||||||
|
Socket::close();
|
||||||
|
}
|
||||||
58
src/ConnectSocket.h
Normal file
58
src/ConnectSocket.h
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_CONNECT_SOCKET_H_
|
||||||
|
#define _PREDIXY_CONNECT_SOCKET_H_
|
||||||
|
|
||||||
|
#include "Socket.h"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class ConnectSocket : public Socket
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum ConnectStatus
|
||||||
|
{
|
||||||
|
Unconnected,
|
||||||
|
Connecting,
|
||||||
|
Connected,
|
||||||
|
Disconnected
|
||||||
|
};
|
||||||
|
public:
|
||||||
|
ConnectSocket(const char* peer, int type, int protocol = 0);
|
||||||
|
bool connect();
|
||||||
|
void reopen();
|
||||||
|
void close();
|
||||||
|
const char* peer() const
|
||||||
|
{
|
||||||
|
return mPeer.c_str();
|
||||||
|
}
|
||||||
|
ConnectStatus connectStatus() const
|
||||||
|
{
|
||||||
|
return good() ? mStatus : Disconnected;
|
||||||
|
}
|
||||||
|
bool isConnecting() const
|
||||||
|
{
|
||||||
|
return mStatus == Connecting;
|
||||||
|
}
|
||||||
|
void setConnected()
|
||||||
|
{
|
||||||
|
mStatus = Connected;
|
||||||
|
}
|
||||||
|
void setConnectStatus(ConnectStatus st)
|
||||||
|
{
|
||||||
|
mStatus = st;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
std::string mPeer;
|
||||||
|
sockaddr_storage mPeerAddr;
|
||||||
|
socklen_t mPeerAddrLen;
|
||||||
|
int mType;
|
||||||
|
int mProtocol;
|
||||||
|
ConnectStatus mStatus;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
38
src/Connection.cpp
Normal file
38
src/Connection.cpp
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Connection.h"
|
||||||
|
|
||||||
|
Connection::Connection():
|
||||||
|
mPostEvts(0),
|
||||||
|
mBufCnt(0),
|
||||||
|
mDb(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
BufferPtr Connection::getBuffer(Handler* h, bool allowNew)
|
||||||
|
{
|
||||||
|
FuncCallTimer();
|
||||||
|
if (allowNew && mBuf) {
|
||||||
|
if (mBufCnt >= Const::MaxBufListNodeNum) {
|
||||||
|
mBuf = nullptr;
|
||||||
|
mBufCnt = 0;
|
||||||
|
} else if (mBuf->room() < Const::MinBufSpaceLeft) {
|
||||||
|
mBuf = nullptr;
|
||||||
|
mBufCnt = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!mBuf || mBuf->full()) {
|
||||||
|
BufferPtr buf = Alloc<Buffer>::create();
|
||||||
|
if (mBuf) {
|
||||||
|
mBuf->concat(buf);
|
||||||
|
}
|
||||||
|
mBuf = buf;
|
||||||
|
++mBufCnt;
|
||||||
|
}
|
||||||
|
return mBuf;
|
||||||
|
}
|
||||||
|
|
||||||
61
src/Connection.h
Normal file
61
src/Connection.h
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_CONNECTION_H_
|
||||||
|
#define _PREDIXY_CONNECTION_H_
|
||||||
|
|
||||||
|
#include "Socket.h"
|
||||||
|
#include "List.h"
|
||||||
|
#include "Common.h"
|
||||||
|
#include "Buffer.h"
|
||||||
|
|
||||||
|
class Handler;
|
||||||
|
|
||||||
|
class Connection
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum ClassType
|
||||||
|
{
|
||||||
|
AcceptType = Socket::CustomType,
|
||||||
|
ConnectType
|
||||||
|
};
|
||||||
|
enum StatusEnum
|
||||||
|
{
|
||||||
|
ParseError = Socket::CustomStatus,
|
||||||
|
LogicError
|
||||||
|
};
|
||||||
|
public:
|
||||||
|
Connection();
|
||||||
|
int getPostEvent() const
|
||||||
|
{
|
||||||
|
return mPostEvts;
|
||||||
|
}
|
||||||
|
void addPostEvent(int evts)
|
||||||
|
{
|
||||||
|
mPostEvts |= evts;
|
||||||
|
}
|
||||||
|
void setPostEvent(int evts)
|
||||||
|
{
|
||||||
|
mPostEvts = evts;
|
||||||
|
}
|
||||||
|
int db() const
|
||||||
|
{
|
||||||
|
return mDb;
|
||||||
|
}
|
||||||
|
void setDb(int db)
|
||||||
|
{
|
||||||
|
mDb = db;
|
||||||
|
}
|
||||||
|
protected:
|
||||||
|
BufferPtr getBuffer(Handler* h, bool allowNew);
|
||||||
|
private:
|
||||||
|
int mPostEvts;
|
||||||
|
BufferPtr mBuf;
|
||||||
|
int mBufCnt;
|
||||||
|
int mDb;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
56
src/Crc16.cpp
Normal file
56
src/Crc16.cpp
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "HashFunc.h"
|
||||||
|
|
||||||
|
static const uint16_t crc16tab[256]= {
|
||||||
|
0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7,
|
||||||
|
0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef,
|
||||||
|
0x1231,0x0210,0x3273,0x2252,0x52b5,0x4294,0x72f7,0x62d6,
|
||||||
|
0x9339,0x8318,0xb37b,0xa35a,0xd3bd,0xc39c,0xf3ff,0xe3de,
|
||||||
|
0x2462,0x3443,0x0420,0x1401,0x64e6,0x74c7,0x44a4,0x5485,
|
||||||
|
0xa56a,0xb54b,0x8528,0x9509,0xe5ee,0xf5cf,0xc5ac,0xd58d,
|
||||||
|
0x3653,0x2672,0x1611,0x0630,0x76d7,0x66f6,0x5695,0x46b4,
|
||||||
|
0xb75b,0xa77a,0x9719,0x8738,0xf7df,0xe7fe,0xd79d,0xc7bc,
|
||||||
|
0x48c4,0x58e5,0x6886,0x78a7,0x0840,0x1861,0x2802,0x3823,
|
||||||
|
0xc9cc,0xd9ed,0xe98e,0xf9af,0x8948,0x9969,0xa90a,0xb92b,
|
||||||
|
0x5af5,0x4ad4,0x7ab7,0x6a96,0x1a71,0x0a50,0x3a33,0x2a12,
|
||||||
|
0xdbfd,0xcbdc,0xfbbf,0xeb9e,0x9b79,0x8b58,0xbb3b,0xab1a,
|
||||||
|
0x6ca6,0x7c87,0x4ce4,0x5cc5,0x2c22,0x3c03,0x0c60,0x1c41,
|
||||||
|
0xedae,0xfd8f,0xcdec,0xddcd,0xad2a,0xbd0b,0x8d68,0x9d49,
|
||||||
|
0x7e97,0x6eb6,0x5ed5,0x4ef4,0x3e13,0x2e32,0x1e51,0x0e70,
|
||||||
|
0xff9f,0xefbe,0xdfdd,0xcffc,0xbf1b,0xaf3a,0x9f59,0x8f78,
|
||||||
|
0x9188,0x81a9,0xb1ca,0xa1eb,0xd10c,0xc12d,0xf14e,0xe16f,
|
||||||
|
0x1080,0x00a1,0x30c2,0x20e3,0x5004,0x4025,0x7046,0x6067,
|
||||||
|
0x83b9,0x9398,0xa3fb,0xb3da,0xc33d,0xd31c,0xe37f,0xf35e,
|
||||||
|
0x02b1,0x1290,0x22f3,0x32d2,0x4235,0x5214,0x6277,0x7256,
|
||||||
|
0xb5ea,0xa5cb,0x95a8,0x8589,0xf56e,0xe54f,0xd52c,0xc50d,
|
||||||
|
0x34e2,0x24c3,0x14a0,0x0481,0x7466,0x6447,0x5424,0x4405,
|
||||||
|
0xa7db,0xb7fa,0x8799,0x97b8,0xe75f,0xf77e,0xc71d,0xd73c,
|
||||||
|
0x26d3,0x36f2,0x0691,0x16b0,0x6657,0x7676,0x4615,0x5634,
|
||||||
|
0xd94c,0xc96d,0xf90e,0xe92f,0x99c8,0x89e9,0xb98a,0xa9ab,
|
||||||
|
0x5844,0x4865,0x7806,0x6827,0x18c0,0x08e1,0x3882,0x28a3,
|
||||||
|
0xcb7d,0xdb5c,0xeb3f,0xfb1e,0x8bf9,0x9bd8,0xabbb,0xbb9a,
|
||||||
|
0x4a75,0x5a54,0x6a37,0x7a16,0x0af1,0x1ad0,0x2ab3,0x3a92,
|
||||||
|
0xfd2e,0xed0f,0xdd6c,0xcd4d,0xbdaa,0xad8b,0x9de8,0x8dc9,
|
||||||
|
0x7c26,0x6c07,0x5c64,0x4c45,0x3ca2,0x2c83,0x1ce0,0x0cc1,
|
||||||
|
0xef1f,0xff3e,0xcf5d,0xdf7c,0xaf9b,0xbfba,0x8fd9,0x9ff8,
|
||||||
|
0x6e17,0x7e36,0x4e55,0x5e74,0x2e93,0x3eb2,0x0ed1,0x1ef0
|
||||||
|
};
|
||||||
|
|
||||||
|
uint16_t Hash::crc16(const char *buf, int len) {
|
||||||
|
int counter;
|
||||||
|
uint16_t crc = 0;
|
||||||
|
for (counter = 0; counter < len; counter++)
|
||||||
|
crc = (crc<<8) ^ crc16tab[((crc>>8) ^ *buf++)&0x00FF];
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t Hash::crc16(uint16_t crc, char k)
|
||||||
|
{
|
||||||
|
crc = (crc<<8) ^ crc16tab[((crc>>8) ^ k)&0x00FF];
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
79
src/DC.cpp
Normal file
79
src/DC.cpp
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "DC.h"
|
||||||
|
|
||||||
|
DC::DC(const String& name, int size):
|
||||||
|
mName(name)
|
||||||
|
{
|
||||||
|
mReadPolicy.resize(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
DC::~DC()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
DataCenter::DataCenter():
|
||||||
|
mLocalDC(nullptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
DataCenter::~DataCenter()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataCenter::init(const Conf* conf)
|
||||||
|
{
|
||||||
|
for (auto& c : conf->dcConfs()) {
|
||||||
|
if (mDCs.find(c.name) != mDCs.end()) {
|
||||||
|
Throw(InitFail, "DC \"%s\" duplicate define", c.name.c_str());
|
||||||
|
}
|
||||||
|
DC* dc = new DC(c.name, conf->dcConfs().size());
|
||||||
|
mDCs[c.name] = dc;
|
||||||
|
if (c.name == conf->localDC()) {
|
||||||
|
mLocalDC = dc;
|
||||||
|
}
|
||||||
|
for (auto& addr : c.addrPrefix) {
|
||||||
|
if (mAddrDC.find(addr) != mAddrDC.end()) {
|
||||||
|
Throw(InitFail, "DC \"%s\" AddrPrefix \"%s\" collision with DC \"%s\"",
|
||||||
|
c.name.c_str(), addr.c_str(), mAddrDC[addr]->name().data());
|
||||||
|
}
|
||||||
|
mAddrDC[addr] = dc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!mLocalDC) {
|
||||||
|
Throw(InitFail, "DataCenter can't find localDC \"%s\"",
|
||||||
|
conf->localDC().c_str());
|
||||||
|
}
|
||||||
|
for (auto& c : conf->dcConfs()) {
|
||||||
|
DC* dc = mDCs[c.name];
|
||||||
|
for (auto& rp : c.readPolicy) {
|
||||||
|
auto it = mDCs.find(rp.name);
|
||||||
|
if (it == mDCs.end()) {
|
||||||
|
Throw(InitFail, "DC \"%s\" ReadPolicy \"%s\" no exists in DataCenter",
|
||||||
|
dc->name().data(), rp.name.c_str());
|
||||||
|
}
|
||||||
|
dc->set(it->second, rp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DC* DataCenter::getDC(const String& addr) const
|
||||||
|
{
|
||||||
|
String p = addr;
|
||||||
|
while (true) {
|
||||||
|
auto it = mAddrDC.upper_bound(p);
|
||||||
|
if (it == mAddrDC.begin()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
--it;
|
||||||
|
if (p.hasPrefix(it->first)) {
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
p = p.commonPrefix(it->first);
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
72
src/DC.h
Normal file
72
src/DC.h
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_DC_H_
|
||||||
|
#define _PREDIXY_DC_H_
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
|
#include "ID.h"
|
||||||
|
#include "Conf.h"
|
||||||
|
#include "String.h"
|
||||||
|
|
||||||
|
struct DCReadPolicy
|
||||||
|
{
|
||||||
|
int priority = 0;
|
||||||
|
int weight = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DC : public ID<DC>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DC(const String& name, int size);
|
||||||
|
~DC();
|
||||||
|
const String& name() const
|
||||||
|
{
|
||||||
|
return mName;
|
||||||
|
}
|
||||||
|
void set(DC* oth, const ReadPolicyConf& c)
|
||||||
|
{
|
||||||
|
mReadPolicy[oth->id()].priority = c.priority;
|
||||||
|
mReadPolicy[oth->id()].weight = c.weight;
|
||||||
|
}
|
||||||
|
int getReadPriority(DC* oth) const
|
||||||
|
{
|
||||||
|
return oth ? mReadPolicy[oth->id()].priority : 1;
|
||||||
|
}
|
||||||
|
int getReadWeight(DC* oth) const
|
||||||
|
{
|
||||||
|
return oth ? mReadPolicy[oth->id()].weight : 1;
|
||||||
|
}
|
||||||
|
const DCReadPolicy& getReadPolicy(DC* oth) const
|
||||||
|
{
|
||||||
|
return mReadPolicy[oth->id()];
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
String mName;
|
||||||
|
std::vector<DCReadPolicy> mReadPolicy;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DataCenter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DefException(InitFail);
|
||||||
|
public:
|
||||||
|
DataCenter();
|
||||||
|
~DataCenter();
|
||||||
|
void init(const Conf* conf);
|
||||||
|
DC* getDC(const String& addr) const;
|
||||||
|
DC* localDC() const
|
||||||
|
{
|
||||||
|
return mLocalDC;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
std::map<String, DC*> mDCs;
|
||||||
|
std::map<String, DC*> mAddrDC;
|
||||||
|
DC* mLocalDC;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
193
src/Deque.h
Normal file
193
src/Deque.h
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_DEQUE_H_
|
||||||
|
#define _PREDIXY_DEQUE_H_
|
||||||
|
|
||||||
|
|
||||||
|
template<class T, class P = T*, int Size = 1>
|
||||||
|
class DequeNode
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
typedef P Ptr;
|
||||||
|
DequeNode()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < Size; ++i) {
|
||||||
|
mPrev[i] = nullptr;
|
||||||
|
mNext[i] = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
~DequeNode()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < Size; ++i) {
|
||||||
|
mPrev[i] = nullptr;
|
||||||
|
mNext[i] = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void reset(int idx)
|
||||||
|
{
|
||||||
|
mPrev[idx] = nullptr;
|
||||||
|
mNext[idx] = nullptr;
|
||||||
|
}
|
||||||
|
void resetPrev(int idx)
|
||||||
|
{
|
||||||
|
mPrev[idx] = nullptr;
|
||||||
|
}
|
||||||
|
void resetNext(int idx)
|
||||||
|
{
|
||||||
|
mNext[idx] = nullptr;
|
||||||
|
}
|
||||||
|
void concat(T* obj, int idx)
|
||||||
|
{
|
||||||
|
mNext[idx] = obj;
|
||||||
|
static_cast<DequeNode*>(obj)->mPrev[idx] = (T*)this;
|
||||||
|
}
|
||||||
|
P prev(int idx) const
|
||||||
|
{
|
||||||
|
return mPrev[idx];
|
||||||
|
}
|
||||||
|
P next(int idx) const
|
||||||
|
{
|
||||||
|
return mNext[idx];
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
P mPrev[Size];
|
||||||
|
P mNext[Size];
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class N, int Idx = 0>
|
||||||
|
class Deque
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
typedef typename N::Value T;
|
||||||
|
typedef typename N::DequeNodeType Node;
|
||||||
|
typedef typename Node::Ptr P;
|
||||||
|
public:
|
||||||
|
Deque():
|
||||||
|
mSize(0),
|
||||||
|
mHead(nullptr),
|
||||||
|
mTail(nullptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
~Deque()
|
||||||
|
{
|
||||||
|
while (mSize > 0) {
|
||||||
|
pop_front();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
P prev(T* obj)
|
||||||
|
{
|
||||||
|
return node(obj)->prev(Idx);
|
||||||
|
}
|
||||||
|
P next(T* obj)
|
||||||
|
{
|
||||||
|
return node(obj)->next(Idx);
|
||||||
|
}
|
||||||
|
void push_back(T* obj)
|
||||||
|
{
|
||||||
|
N* p = static_cast<N*>(obj);
|
||||||
|
if (mTail) {
|
||||||
|
static_cast<Node*>((T*)mTail)->concat(p, Idx);
|
||||||
|
mTail = p;
|
||||||
|
} else {
|
||||||
|
mHead = mTail = p;
|
||||||
|
}
|
||||||
|
++mSize;
|
||||||
|
}
|
||||||
|
void push_front(T* obj)
|
||||||
|
{
|
||||||
|
N* p = static_cast<N*>(obj);
|
||||||
|
if (mHead) {
|
||||||
|
node(obj)->concat(mHead, Idx);
|
||||||
|
mHead = p;
|
||||||
|
} else {
|
||||||
|
mHead = mTail = p;
|
||||||
|
}
|
||||||
|
++mSize;
|
||||||
|
}
|
||||||
|
void remove(T* obj)
|
||||||
|
{
|
||||||
|
auto n = node(obj);
|
||||||
|
auto prev = n->prev(Idx);
|
||||||
|
auto next = n->next(Idx);
|
||||||
|
int exists = 1;
|
||||||
|
if (prev && next) {
|
||||||
|
node(prev)->concat(next, Idx);
|
||||||
|
} else if (prev) {
|
||||||
|
node(prev)->resetNext(Idx);
|
||||||
|
mTail = prev;
|
||||||
|
} else if (next) {
|
||||||
|
node(next)->resetPrev(Idx);
|
||||||
|
mHead = next;
|
||||||
|
} else {
|
||||||
|
if (mHead == n) {
|
||||||
|
mHead = mTail = nullptr;
|
||||||
|
} else {
|
||||||
|
exists = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mSize -= exists;
|
||||||
|
n->reset(Idx);
|
||||||
|
}
|
||||||
|
void move_back(T* obj)
|
||||||
|
{
|
||||||
|
auto n = node(obj);
|
||||||
|
if (auto next = n->next(Idx)) {
|
||||||
|
if (auto prev = n->prev(Idx)) {
|
||||||
|
node(prev)->concat(next, Idx);
|
||||||
|
} else {
|
||||||
|
next->resetPrev(Idx);
|
||||||
|
mHead = next;
|
||||||
|
}
|
||||||
|
node(mTail)->concat(obj, Idx);
|
||||||
|
mTail = obj;
|
||||||
|
n->resetNext(Idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
P pop_front()
|
||||||
|
{
|
||||||
|
P obj = mHead;
|
||||||
|
if (obj) {
|
||||||
|
remove(obj);
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
P pop_back()
|
||||||
|
{
|
||||||
|
P obj = mTail;
|
||||||
|
if (obj) {
|
||||||
|
remove(obj);
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
P front() const
|
||||||
|
{
|
||||||
|
return mHead;
|
||||||
|
}
|
||||||
|
P back() const
|
||||||
|
{
|
||||||
|
return mTail;
|
||||||
|
}
|
||||||
|
int size() const
|
||||||
|
{
|
||||||
|
return mSize;
|
||||||
|
}
|
||||||
|
bool empty() const
|
||||||
|
{
|
||||||
|
return mSize == 0;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
static Node* node(T* obj)
|
||||||
|
{
|
||||||
|
return static_cast<N*>(obj);
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
int mSize;
|
||||||
|
P mHead;
|
||||||
|
P mTail;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
36
src/Distribution.cpp
Normal file
36
src/Distribution.cpp
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <strings.h>
|
||||||
|
#include "Distribution.h"
|
||||||
|
|
||||||
|
struct TypeName
|
||||||
|
{
|
||||||
|
Distribution::Type type;
|
||||||
|
const char* name;
|
||||||
|
};
|
||||||
|
|
||||||
|
static TypeName Pairs[] = {
|
||||||
|
{Distribution::None, "none"},
|
||||||
|
{Distribution::Modula, "modula"},
|
||||||
|
{Distribution::Random, "random"}
|
||||||
|
};
|
||||||
|
|
||||||
|
const char* Distribution::name() const
|
||||||
|
{
|
||||||
|
return Pairs[mType].name;
|
||||||
|
}
|
||||||
|
|
||||||
|
Distribution Distribution::parse(const char* str)
|
||||||
|
{
|
||||||
|
for (auto& i : Pairs) {
|
||||||
|
if (strcasecmp(i.name, str) == 0) {
|
||||||
|
return i.type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Distribution::None;
|
||||||
|
}
|
||||||
34
src/Distribution.h
Normal file
34
src/Distribution.h
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_DISTRIBUTION_H_
|
||||||
|
#define _PREDIXY_DISTRIBUTION_H_
|
||||||
|
|
||||||
|
class Distribution
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum Type
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Modula,
|
||||||
|
Random
|
||||||
|
};
|
||||||
|
public:
|
||||||
|
Distribution(Type t = None):
|
||||||
|
mType(t)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
operator Type() const
|
||||||
|
{
|
||||||
|
return mType;
|
||||||
|
}
|
||||||
|
const char* name() const;
|
||||||
|
static Distribution parse(const char* str);
|
||||||
|
private:
|
||||||
|
Type mType;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
106
src/EpollMultiplexor.cpp
Normal file
106
src/EpollMultiplexor.cpp
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "EpollMultiplexor.h"
|
||||||
|
#include "Socket.h"
|
||||||
|
|
||||||
|
EpollMultiplexor::EpollMultiplexor():
|
||||||
|
mFd(-1)
|
||||||
|
{
|
||||||
|
int fd = epoll_create(1024);
|
||||||
|
if (fd < 0) {
|
||||||
|
Throw(EpollCreateFail, "epoll create fail %s", StrError());
|
||||||
|
}
|
||||||
|
mFd = fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
EpollMultiplexor::~EpollMultiplexor()
|
||||||
|
{
|
||||||
|
if (mFd >= 0) {
|
||||||
|
::close(mFd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EpollMultiplexor::addSocket(Socket* s, int evts)
|
||||||
|
{
|
||||||
|
epoll_event event;
|
||||||
|
memset(&event, 0, sizeof(event));
|
||||||
|
event.events |= (evts & ReadEvent) ? EPOLLIN : 0;
|
||||||
|
event.events |= (evts & WriteEvent) ? EPOLLOUT : 0;
|
||||||
|
event.events |= EPOLLET;
|
||||||
|
//event.events |= EPOLLONESHOT;
|
||||||
|
event.data.ptr = s;
|
||||||
|
int ret = epoll_ctl(mFd, EPOLL_CTL_ADD, s->fd(), &event);
|
||||||
|
if (ret == 0) {
|
||||||
|
s->setEvent(evts);
|
||||||
|
}
|
||||||
|
return ret == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EpollMultiplexor::delSocket(Socket* s)
|
||||||
|
{
|
||||||
|
epoll_event event;
|
||||||
|
memset(&event, 0, sizeof(event));
|
||||||
|
epoll_ctl(mFd, EPOLL_CTL_DEL, s->fd(), &event);
|
||||||
|
s->setEvent(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EpollMultiplexor::addEvent(Socket* s, int evts)
|
||||||
|
{
|
||||||
|
epoll_event event;
|
||||||
|
memset(&event, 0, sizeof(event));
|
||||||
|
event.data.ptr = s;
|
||||||
|
if ((evts & ReadEvent) || (s->getEvent() & ReadEvent)) {
|
||||||
|
event.events |= EPOLLIN;
|
||||||
|
}
|
||||||
|
if ((evts & WriteEvent) || (s->getEvent() & WriteEvent)) {
|
||||||
|
event.events |= EPOLLOUT;
|
||||||
|
}
|
||||||
|
if ((s->getEvent() | evts) != s->getEvent()) {
|
||||||
|
event.events |= EPOLLET;
|
||||||
|
//event.events |= EPOLLONESHOT;
|
||||||
|
int ret = epoll_ctl(mFd, EPOLL_CTL_MOD, s->fd(), &event);
|
||||||
|
if (ret == 0) {
|
||||||
|
s->setEvent(s->getEvent() | evts);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EpollMultiplexor::delEvent(Socket* s, int evts)
|
||||||
|
{
|
||||||
|
bool mod = false;
|
||||||
|
epoll_event event;
|
||||||
|
memset(&event, 0, sizeof(event));
|
||||||
|
event.data.ptr = s;
|
||||||
|
if (s->getEvent() & ReadEvent) {
|
||||||
|
if (evts & ReadEvent) {
|
||||||
|
mod = true;
|
||||||
|
} else {
|
||||||
|
event.events |= EPOLLIN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (s->getEvent() & WriteEvent) {
|
||||||
|
if (evts & WriteEvent) {
|
||||||
|
mod = true;
|
||||||
|
} else {
|
||||||
|
event.events |= EPOLLOUT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mod) {
|
||||||
|
event.events |= EPOLLET;
|
||||||
|
//event.events |= EPOLLONESHOT;
|
||||||
|
int ret = epoll_ctl(mFd, EPOLL_CTL_MOD, s->fd(), &event);
|
||||||
|
if (ret == 0) {
|
||||||
|
s->setEvent(s->getEvent() & ~evts);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
65
src/EpollMultiplexor.h
Normal file
65
src/EpollMultiplexor.h
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_EPOLL_MULTIPLEXOR_H_
|
||||||
|
#define _PREDIXY_EPOLL_MULTIPLEXOR_H_
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/epoll.h>
|
||||||
|
#include "Multiplexor.h"
|
||||||
|
#include "Exception.h"
|
||||||
|
#include "Util.h"
|
||||||
|
|
||||||
|
class EpollMultiplexor : public MultiplexorBase
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DefException(EpollCreateFail);
|
||||||
|
DefException(EpollWaitFail);
|
||||||
|
public:
|
||||||
|
EpollMultiplexor();
|
||||||
|
~EpollMultiplexor();
|
||||||
|
bool addSocket(Socket* s, int evts = ReadEvent);
|
||||||
|
void delSocket(Socket* s);
|
||||||
|
bool addEvent(Socket* s, int evts);
|
||||||
|
bool delEvent(Socket* s, int evts);
|
||||||
|
template<class T>
|
||||||
|
int wait(long usec, T* handler);
|
||||||
|
private:
|
||||||
|
int mFd;
|
||||||
|
static const int MaxEvents = 1024;
|
||||||
|
epoll_event mEvents[MaxEvents];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
int EpollMultiplexor::wait(long usec, T* handler)
|
||||||
|
{
|
||||||
|
int timeout = usec < 0 ? usec : usec / 1000;
|
||||||
|
logVerb("epoll wait with timeout %ld usec", usec);
|
||||||
|
int num = epoll_wait(mFd, mEvents, MaxEvents, timeout);
|
||||||
|
logVerb("epoll wait return with event count:%d", num);
|
||||||
|
if (num == -1) {
|
||||||
|
if (errno == EINTR) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
Throw(EpollWaitFail, "handler %d epoll wait fail %s", handler->id(), StrError());
|
||||||
|
}
|
||||||
|
for (int i = 0; i < num; ++i) {
|
||||||
|
Socket* s = static_cast<Socket*>(mEvents[i].data.ptr);
|
||||||
|
int evts = 0;
|
||||||
|
evts |= (mEvents[i].events & EPOLLIN) ? ReadEvent : 0;
|
||||||
|
evts |= (mEvents[i].events & EPOLLOUT) ? WriteEvent : 0;
|
||||||
|
evts |= (mEvents[i].events & (EPOLLERR|EPOLLHUP)) ? ErrorEvent : 0;
|
||||||
|
handler->handleEvent(s, evts);
|
||||||
|
}
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
typedef EpollMultiplexor Multiplexor;
|
||||||
|
#define _MULTIPLEXOR_ASYNC_ASSIGN_
|
||||||
|
|
||||||
|
#endif
|
||||||
63
src/Exception.h
Normal file
63
src/Exception.h
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_EXCEPTION_H_
|
||||||
|
#define _PREDIXY_EXCEPTION_H_
|
||||||
|
|
||||||
|
#include <exception>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
class ExceptionBase : public std::exception
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static const int MaxMsgLen = 1024;
|
||||||
|
public:
|
||||||
|
ExceptionBase()
|
||||||
|
{
|
||||||
|
mMsg[0] = '\0';
|
||||||
|
}
|
||||||
|
ExceptionBase(const char* file, int line, const char* fmt, ...)
|
||||||
|
{
|
||||||
|
int n = snprintf(mMsg, sizeof(mMsg), "%s:%d ", file, line);
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap, fmt);
|
||||||
|
vsnprintf(mMsg + n, sizeof(mMsg) - n, fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
}
|
||||||
|
ExceptionBase(const char* fmt, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap, fmt);
|
||||||
|
vsnprintf(mMsg, sizeof(mMsg), fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
}
|
||||||
|
~ExceptionBase()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
const char* what() const noexcept
|
||||||
|
{
|
||||||
|
return mMsg;
|
||||||
|
}
|
||||||
|
protected:
|
||||||
|
void init(const char* fmt, va_list ap)
|
||||||
|
{
|
||||||
|
vsnprintf(mMsg, sizeof(mMsg), fmt, ap);
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
char mMsg[MaxMsgLen];
|
||||||
|
};
|
||||||
|
|
||||||
|
#define DefException(T) class T : public ExceptionBase \
|
||||||
|
{ \
|
||||||
|
public: \
|
||||||
|
template<class... A> \
|
||||||
|
T(A&&... args):ExceptionBase(args...) {} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define Throw(T, ...) throw T(__FILE__, __LINE__, ##__VA_ARGS__)
|
||||||
|
|
||||||
|
#endif
|
||||||
1424
src/Handler.cpp
Normal file
1424
src/Handler.cpp
Normal file
File diff suppressed because it is too large
Load Diff
132
src/Handler.h
Normal file
132
src/Handler.h
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_HANDLER_H_
|
||||||
|
#define _PREDIXY_HANDLER_H_
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include "Predixy.h"
|
||||||
|
#include "Multiplexor.h"
|
||||||
|
#include "Stats.h"
|
||||||
|
#include "LatencyMonitor.h"
|
||||||
|
#include "AcceptConnection.h"
|
||||||
|
#include "ConnectConnectionPool.h"
|
||||||
|
#include "Proxy.h"
|
||||||
|
|
||||||
|
class Handler : public ID<Handler>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DefException(AddListenerEventFail);
|
||||||
|
public:
|
||||||
|
Handler(Proxy* p);
|
||||||
|
~Handler();
|
||||||
|
void run();
|
||||||
|
void stop();
|
||||||
|
void handleEvent(Socket* s, int evts);
|
||||||
|
void handleRequest(Request* req);
|
||||||
|
void handleRequest(Request* req, ConnectConnection* c);
|
||||||
|
void handleResponse(ConnectConnection* c, Request* req, Response* res);
|
||||||
|
void handleResponse(ConnectConnection* c, Request* req, Response::GenericCode code);
|
||||||
|
void directResponse(Request* req, Response::GenericCode code, ConnectConnection* s=nullptr);
|
||||||
|
Proxy* proxy() const
|
||||||
|
{
|
||||||
|
return mProxy;
|
||||||
|
}
|
||||||
|
Multiplexor* eventLoop() const
|
||||||
|
{
|
||||||
|
return mEventLoop;
|
||||||
|
}
|
||||||
|
HandlerStats& stats()
|
||||||
|
{
|
||||||
|
return mStats;
|
||||||
|
}
|
||||||
|
const HandlerStats& stats() const
|
||||||
|
{
|
||||||
|
return mStats;
|
||||||
|
}
|
||||||
|
const std::vector<LatencyMonitor>& latencyMonitors() const
|
||||||
|
{
|
||||||
|
return mLatencyMonitors;
|
||||||
|
}
|
||||||
|
ConnectConnectionPool* getConnectConnectionPool(int id) const
|
||||||
|
{
|
||||||
|
return id < (int)mConnPool.size() ? mConnPool[id] : nullptr;
|
||||||
|
}
|
||||||
|
int getPendRequests(Server* serv) const
|
||||||
|
{
|
||||||
|
if (auto cp = getConnectConnectionPool(serv->id())) {
|
||||||
|
return cp->pendRequests();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
void addServerReadStats(Server* serv, int num)
|
||||||
|
{
|
||||||
|
mStats.recvServerBytes += num;
|
||||||
|
mConnPool[serv->id()]->stats().recvBytes += num;
|
||||||
|
}
|
||||||
|
void addServerWriteStats(Server* serv, int num)
|
||||||
|
{
|
||||||
|
mStats.sendServerBytes += num;
|
||||||
|
mConnPool[serv->id()]->stats().sendBytes += num;
|
||||||
|
}
|
||||||
|
IDUnique& idUnique()
|
||||||
|
{
|
||||||
|
return mIDUnique;
|
||||||
|
}
|
||||||
|
int rand()
|
||||||
|
{
|
||||||
|
return rand_r(&mRandSeed);
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
bool preHandleRequest(Request* req, const String& key);
|
||||||
|
void postHandleRequest(Request* req, ConnectConnection* s);
|
||||||
|
void addPostEvent(AcceptConnection* c, int evts);
|
||||||
|
void addPostEvent(ConnectConnection* c, int evts);
|
||||||
|
void postEvent();
|
||||||
|
void handleListenEvent(ListenSocket* s, int evts);
|
||||||
|
void addAcceptSocket(int c, sockaddr* addr, socklen_t len);
|
||||||
|
void handleAcceptConnectionEvent(AcceptConnection* c, int evts);
|
||||||
|
void handleConnectConnectionEvent(ConnectConnection* c, int evts);
|
||||||
|
void postAcceptConnectionEvent();
|
||||||
|
void postConnectConnectionEvent();
|
||||||
|
ConnectConnection* getConnectConnection(Request* req, Server* s);
|
||||||
|
void refreshServerPool();
|
||||||
|
void checkConnectionPool();
|
||||||
|
int checkClientTimeout(long timeout);
|
||||||
|
void innerResponse(ConnectConnection* c, Request* req, Response* res);
|
||||||
|
void infoRequest(Request* req, const String& key);
|
||||||
|
void infoLatencyRequest(Request* req);
|
||||||
|
void infoServerLatencyRequest(Request* req);
|
||||||
|
void configRequest(Request* req, const String& key);
|
||||||
|
void configGetRequest(Request* req);
|
||||||
|
void configSetRequest(Request* req);
|
||||||
|
bool redirect(ConnectConnection* c, Request* req, Response* res, bool moveOrAsk);
|
||||||
|
bool permission(Request* req, const String& key, Response::GenericCode& code);
|
||||||
|
void resetStats();
|
||||||
|
void setAcceptConnectionActiveTime(AcceptConnection* c)
|
||||||
|
{
|
||||||
|
c->setLastActiveTime(Util::elapsedUSec());
|
||||||
|
mAcceptConns.remove(c);
|
||||||
|
mAcceptConns.push_back(c);
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
bool mStop;
|
||||||
|
Proxy* mProxy;
|
||||||
|
Multiplexor* mEventLoop;
|
||||||
|
std::vector<ConnectConnectionPool*> mConnPool;
|
||||||
|
AcceptConnectionDeque mAcceptConns;
|
||||||
|
AcceptConnectionList mPostAcceptConns;
|
||||||
|
ConnectConnectionList mPostConnectConns;
|
||||||
|
ConnectConnectionDeque mWaitConnectConns;
|
||||||
|
long mStatsVer;
|
||||||
|
HandlerStats mStats;
|
||||||
|
std::vector<LatencyMonitor> mLatencyMonitors;
|
||||||
|
IDUnique mIDUnique;
|
||||||
|
unsigned int mRandSeed;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
83
src/HashFunc.cpp
Normal file
83
src/HashFunc.cpp
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <strings.h>
|
||||||
|
#include "HashFunc.h"
|
||||||
|
|
||||||
|
|
||||||
|
Hash Hash::parse(const char* str)
|
||||||
|
{
|
||||||
|
if (strcasecmp(str, "atol") == 0) {
|
||||||
|
return Atol;
|
||||||
|
} else if (strcasecmp(str, "crc16") == 0) {
|
||||||
|
return Crc16;
|
||||||
|
}
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* Hash::hashTagStr(const char* buf, int& len, const char* tag)
|
||||||
|
{
|
||||||
|
if (tag && tag[0] && tag[1]) {
|
||||||
|
int i = 0;
|
||||||
|
while (i < len && buf[i] != tag[0]) {
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
if (i == len) {
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
const char* b = buf + ++i;
|
||||||
|
while (i < len && buf[i] != tag[1]) {
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
if (i == len) {
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
const char* e = buf + i;
|
||||||
|
if (b < e) {
|
||||||
|
len = e - b;
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
long Hash::hash(const char* buf, int len) const
|
||||||
|
{
|
||||||
|
switch (mType) {
|
||||||
|
case Atol:
|
||||||
|
return atol(buf, len);
|
||||||
|
case Crc16:
|
||||||
|
return crc16(buf, len);
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
long Hash::atol(const char* buf, int len)
|
||||||
|
{
|
||||||
|
long v = 0;
|
||||||
|
if (buf) {
|
||||||
|
int i = 0;
|
||||||
|
if (buf[i] == '+') {
|
||||||
|
++i;
|
||||||
|
} else if (buf[i] == '-') {
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
for ( ; i < len; ++i) {
|
||||||
|
if (buf[i] >= '0' && buf[i] <= '9') {
|
||||||
|
v = v * 10 + buf[i] - '0';
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (buf[0] == '-') {
|
||||||
|
v = -v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
47
src/HashFunc.h
Normal file
47
src/HashFunc.h
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_HASH_FUNC_H_
|
||||||
|
#define _PREDIXY_HASH_FUNC_H_
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
class Hash
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum Type
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Atol,
|
||||||
|
Crc16
|
||||||
|
};
|
||||||
|
static Hash parse(const char* str);
|
||||||
|
static const char* hashTagStr(const char* buf, int& len, const char* tag);
|
||||||
|
static uint16_t crc16(const char* buf, int len);
|
||||||
|
static uint16_t crc16(uint16_t crc, char k);
|
||||||
|
static long atol(const char* buf, int len);
|
||||||
|
public:
|
||||||
|
Hash(Type t = None):
|
||||||
|
mType(t)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
operator Type() const
|
||||||
|
{
|
||||||
|
return mType;
|
||||||
|
}
|
||||||
|
long hash(const char* buf, int len) const;
|
||||||
|
long hash(const char* buf, int len, const char* tag) const
|
||||||
|
{
|
||||||
|
buf = hashTagStr(buf, len, tag);
|
||||||
|
return hash(buf, len);
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
Type mType;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
94
src/ID.h
Normal file
94
src/ID.h
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_ID_H_
|
||||||
|
#define _PREDIXY_ID_H_
|
||||||
|
|
||||||
|
#include "Sync.h"
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
class TID
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TID():
|
||||||
|
mId(++Id)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
long id() const
|
||||||
|
{
|
||||||
|
return mId;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
long mId;
|
||||||
|
thread_local static long Id;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
thread_local long TID<T>::Id(0);
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
class ID
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ID():
|
||||||
|
mId(++Id - 1)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
int id() const
|
||||||
|
{
|
||||||
|
return mId;
|
||||||
|
}
|
||||||
|
static int maxId()
|
||||||
|
{
|
||||||
|
return Id;
|
||||||
|
}
|
||||||
|
protected:
|
||||||
|
~ID()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
int mId;
|
||||||
|
static AtomicInt Id;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
AtomicInt ID<T>::Id(0);
|
||||||
|
|
||||||
|
class IDUnique
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IDUnique(int sz = 0):
|
||||||
|
mMarker(sz, false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
void resize(int sz)
|
||||||
|
{
|
||||||
|
if (sz > (int)mMarker.size()) {
|
||||||
|
mMarker.resize(sz, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
template<class C>
|
||||||
|
int unique(C d, int num)
|
||||||
|
{
|
||||||
|
int n = 0;
|
||||||
|
for (int i = 0; i < num; ++i) {
|
||||||
|
auto p = d[i];
|
||||||
|
if (!mMarker[p->id()]) {
|
||||||
|
mMarker[p->id()] = true;
|
||||||
|
d[n++] = p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int i = 0; i < n; ++i) {
|
||||||
|
mMarker[d[i]->id()] = false;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
std::vector<bool> mMarker;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
30
src/IOVec.h
Normal file
30
src/IOVec.h
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_IOVEC_H_
|
||||||
|
#define _PREDIXY_IOVEC_H_
|
||||||
|
|
||||||
|
|
||||||
|
#include <sys/uio.h>
|
||||||
|
|
||||||
|
class Request;
|
||||||
|
class Response;
|
||||||
|
class Segment;
|
||||||
|
|
||||||
|
struct IOVec
|
||||||
|
{
|
||||||
|
char* dat;
|
||||||
|
int len;
|
||||||
|
int pos;
|
||||||
|
Buffer* buf;
|
||||||
|
Segment* seg;
|
||||||
|
Request* req;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
86
src/KqueueMultiplexor.cpp
Normal file
86
src/KqueueMultiplexor.cpp
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "KqueueMultiplexor.h"
|
||||||
|
#include "Socket.h"
|
||||||
|
|
||||||
|
KqueueMultiplexor::KqueueMultiplexor():
|
||||||
|
mFd(-1)
|
||||||
|
{
|
||||||
|
int fd = kqueue();
|
||||||
|
if (fd < 0) {
|
||||||
|
Throw(KqueueCreateFail, "kqueue call fail %s", StrError());
|
||||||
|
}
|
||||||
|
mFd = fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
KqueueMultiplexor::~KqueueMultiplexor()
|
||||||
|
{
|
||||||
|
if (mFd >= 0) {
|
||||||
|
::close(mFd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool KqueueMultiplexor::addSocket(Socket* s, int evts)
|
||||||
|
{
|
||||||
|
return addEvent(s, evts);
|
||||||
|
}
|
||||||
|
|
||||||
|
void KqueueMultiplexor::delSocket(Socket* s)
|
||||||
|
{
|
||||||
|
struct kevent event;
|
||||||
|
EV_SET(&event, s->fd(), EVFILT_READ, EV_DELETE, 0, 0, s);
|
||||||
|
kevent(mFd, &event, 1, NULL, 0, NULL);
|
||||||
|
EV_SET(&event, s->fd(), EVFILT_WRITE, EV_DELETE, 0, 0, s);
|
||||||
|
kevent(mFd, &event, 1, NULL, 0, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool KqueueMultiplexor::addEvent(Socket* s, int evts)
|
||||||
|
{
|
||||||
|
if ((evts & ReadEvent) && !(s->getEvent() & ReadEvent)) {
|
||||||
|
struct kevent event;
|
||||||
|
EV_SET(&event, s->fd(), EVFILT_READ, EV_ADD, 0, 0, s);
|
||||||
|
int ret = kevent(mFd, &event, 1, NULL, 0, NULL);
|
||||||
|
if (ret == -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
s->setEvent(s->getEvent() | ReadEvent);
|
||||||
|
}
|
||||||
|
if ((evts & WriteEvent) && !(s->getEvent() & WriteEvent)) {
|
||||||
|
struct kevent event;
|
||||||
|
EV_SET(&event, s->fd(), EVFILT_WRITE, EV_ADD, 0, 0, s);
|
||||||
|
int ret = kevent(mFd, &event, 1, NULL, 0, NULL);
|
||||||
|
if (ret == -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
s->setEvent(s->getEvent() | WriteEvent);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool KqueueMultiplexor::delEvent(Socket* s, int evts)
|
||||||
|
{
|
||||||
|
if ((evts & ReadEvent) && (s->getEvent() & ReadEvent)) {
|
||||||
|
struct kevent event;
|
||||||
|
EV_SET(&event, s->fd(), EVFILT_READ, EV_DELETE, 0, 0, s);
|
||||||
|
int ret = kevent(mFd, &event, 1, NULL, 0, NULL);
|
||||||
|
if (ret == -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
s->setEvent(s->getEvent() & ~ReadEvent);
|
||||||
|
}
|
||||||
|
if ((evts & WriteEvent) && (s->getEvent() & WriteEvent)) {
|
||||||
|
struct kevent event;
|
||||||
|
EV_SET(&event, s->fd(), EVFILT_WRITE, EV_DELETE, 0, 0, s);
|
||||||
|
int ret = kevent(mFd, &event, 1, NULL, 0, NULL);
|
||||||
|
if (ret == -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
s->setEvent(s->getEvent() & ~WriteEvent);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
72
src/KqueueMultiplexor.h
Normal file
72
src/KqueueMultiplexor.h
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_KQUEUE_MULTIPLEXOR_H_
|
||||||
|
#define _PREDIXY_KQUEUE_MULTIPLEXOR_H_
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/event.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include "Multiplexor.h"
|
||||||
|
#include "Util.h"
|
||||||
|
|
||||||
|
class KqueueMultiplexor : public MultiplexorBase
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DefException(KqueueCreateFail);
|
||||||
|
DefException(KqueueWaitFail);
|
||||||
|
DefException(AddSocketFail);
|
||||||
|
public:
|
||||||
|
KqueueMultiplexor();
|
||||||
|
~KqueueMultiplexor();
|
||||||
|
bool addSocket(Socket* s, int evts = ReadEvent);
|
||||||
|
void delSocket(Socket* s);
|
||||||
|
bool addEvent(Socket* s, int evts);
|
||||||
|
bool delEvent(Socket* s, int evts);
|
||||||
|
template<class T>
|
||||||
|
int wait(long usec, T* handler);
|
||||||
|
private:
|
||||||
|
int mFd;
|
||||||
|
static const int MaxEvents = 1024;
|
||||||
|
struct kevent mEvents[MaxEvents];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
int KqueueMultiplexor::wait(long usec, T* handler)
|
||||||
|
{
|
||||||
|
struct timespec timeout;
|
||||||
|
timeout.tv_sec = usec / 1000000;
|
||||||
|
timeout.tv_nsec = (usec % 1000000) * 1000;
|
||||||
|
int num = kevent(mFd, nullptr, 0, mEvents, MaxEvents, usec < 0 ? nullptr : &timeout);
|
||||||
|
if (num == -1) {
|
||||||
|
if (errno == EINTR) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
Throw(KqueueWaitFail, "h %d kqueue wait fail %s",
|
||||||
|
handler->id(), StrError());
|
||||||
|
}
|
||||||
|
for (int i = 0; i < num; ++i) {
|
||||||
|
Socket* s = static_cast<Socket*>(mEvents[i].udata);
|
||||||
|
int evts = 0;
|
||||||
|
if (mEvents[i].flags & EV_ERROR) {
|
||||||
|
evts = ErrorEvent;
|
||||||
|
} else if (mEvents[i].filter == EVFILT_READ) {
|
||||||
|
evts = ReadEvent;
|
||||||
|
} else if (mEvents[i].filter == EVFILT_WRITE) {
|
||||||
|
evts = WriteEvent;
|
||||||
|
}
|
||||||
|
handler->handleEvent(s, evts);
|
||||||
|
}
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
typedef KqueueMultiplexor Multiplexor;
|
||||||
|
#define _MULTIPLEXOR_ASYNC_ASSIGN_
|
||||||
|
|
||||||
|
#endif
|
||||||
61
src/LatencyMonitor.cpp
Normal file
61
src/LatencyMonitor.cpp
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "LatencyMonitor.h"
|
||||||
|
|
||||||
|
Buffer* LatencyMonitor::output(Buffer* buf) const
|
||||||
|
{
|
||||||
|
long tc = mLast.count;
|
||||||
|
for (auto& s : mTimeSpan) {
|
||||||
|
tc += s.count;
|
||||||
|
}
|
||||||
|
if (tc == 0) {
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
long te = mLast.total;
|
||||||
|
long t = 0;
|
||||||
|
float p = 0;
|
||||||
|
for (auto& s : mTimeSpan) {
|
||||||
|
if (s.count == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
te += s.total;
|
||||||
|
t += s.count;
|
||||||
|
p = t * 100. / tc;
|
||||||
|
buf = buf->fappend("<= %12ld %20ld %16ld %.2f%%\n", s.span, s.total, s.count, p);
|
||||||
|
}
|
||||||
|
if (mLast.count > 0) {
|
||||||
|
buf = buf->fappend("> %12ld %20ld %16ld 100.00%\n", mLast.span, mLast.total, mLast.count);
|
||||||
|
}
|
||||||
|
buf = buf->fappend("T %12ld %20ld %16ld\n", te / tc, te, tc);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void LatencyMonitorSet::init(const std::vector<LatencyMonitorConf>& conf)
|
||||||
|
{
|
||||||
|
mPool.resize(conf.size());
|
||||||
|
int i = 0;
|
||||||
|
for (auto& c : conf) {
|
||||||
|
if (mNameIdx.find(c.name) != mNameIdx.end()) {
|
||||||
|
Throw(DuplicateDef, "LatencyMonitor \"%s\" duplicate",
|
||||||
|
c.name.c_str());
|
||||||
|
}
|
||||||
|
mPool[i].init(c);
|
||||||
|
mNameIdx[mPool[i].name()] = i;
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
mCmdIdx.resize(Command::Sentinel);
|
||||||
|
int cursor = 0;
|
||||||
|
while (auto cmd = Command::iter(cursor)) {
|
||||||
|
int size = conf.size();
|
||||||
|
for (int i = 0; i < size; ++i) {
|
||||||
|
if (conf[i].cmds[cmd->type]) {
|
||||||
|
mCmdIdx[cmd->type].push_back(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
135
src/LatencyMonitor.h
Normal file
135
src/LatencyMonitor.h
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_LATENCY_MONITOR_H_
|
||||||
|
#define _PREDIXY_LATENCY_MONITOR_H_
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
|
#include "String.h"
|
||||||
|
#include "Buffer.h"
|
||||||
|
#include "Conf.h"
|
||||||
|
|
||||||
|
class LatencyMonitor
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
struct TimeSpan
|
||||||
|
{
|
||||||
|
long span;
|
||||||
|
long total;
|
||||||
|
long count;
|
||||||
|
|
||||||
|
bool operator<(const TimeSpan& oth) const
|
||||||
|
{
|
||||||
|
return span < oth.span;
|
||||||
|
}
|
||||||
|
TimeSpan& operator+=(const TimeSpan& s)
|
||||||
|
{
|
||||||
|
total += s.total;
|
||||||
|
count += s.count;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
public:
|
||||||
|
LatencyMonitor():
|
||||||
|
mCmds(nullptr),
|
||||||
|
mLast{0, 0, 0}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
LatencyMonitor& operator+=(const LatencyMonitor& m)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < mTimeSpan.size(); ++i) {
|
||||||
|
mTimeSpan[i] += m.mTimeSpan[i];
|
||||||
|
}
|
||||||
|
mLast += m.mLast;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
void init(const LatencyMonitorConf& c)
|
||||||
|
{
|
||||||
|
mName = c.name;
|
||||||
|
mCmds = &c.cmds;
|
||||||
|
mTimeSpan.resize(c.timeSpan.size());
|
||||||
|
for (size_t i = 0; i < mTimeSpan.size(); ++i) {
|
||||||
|
mTimeSpan[i].span = c.timeSpan[i];
|
||||||
|
mTimeSpan[i].total = 0;
|
||||||
|
mTimeSpan[i].count = 0;
|
||||||
|
}
|
||||||
|
if (!mTimeSpan.empty()) {
|
||||||
|
mLast.span = mTimeSpan.back().span;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void reset()
|
||||||
|
{
|
||||||
|
for (auto& s : mTimeSpan) {
|
||||||
|
s.total = 0;
|
||||||
|
s.count = 0;
|
||||||
|
}
|
||||||
|
mLast.total = 0;
|
||||||
|
mLast.count = 0;
|
||||||
|
}
|
||||||
|
const String& name() const
|
||||||
|
{
|
||||||
|
return mName;
|
||||||
|
}
|
||||||
|
int add(long v)
|
||||||
|
{
|
||||||
|
TimeSpan span{v};
|
||||||
|
auto it = std::lower_bound(mTimeSpan.begin(), mTimeSpan.end(), span);
|
||||||
|
if (it == mTimeSpan.end()) {
|
||||||
|
mLast.total += v;
|
||||||
|
++mLast.count;
|
||||||
|
return mTimeSpan.size();
|
||||||
|
} else {
|
||||||
|
it->total += v;
|
||||||
|
++it->count;
|
||||||
|
return it - mTimeSpan.begin();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void add(long v, int idx)
|
||||||
|
{
|
||||||
|
TimeSpan& s(idx < (int)mTimeSpan.size() ? mTimeSpan[idx] : mLast);
|
||||||
|
s.total += v;
|
||||||
|
++s.count;
|
||||||
|
}
|
||||||
|
Buffer* output(Buffer* buf) const;
|
||||||
|
private:
|
||||||
|
String mName;
|
||||||
|
const std::bitset<Command::Sentinel>* mCmds;
|
||||||
|
std::vector<TimeSpan> mTimeSpan;
|
||||||
|
TimeSpan mLast;
|
||||||
|
};
|
||||||
|
|
||||||
|
class LatencyMonitorSet
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DefException(DuplicateDef);
|
||||||
|
public:
|
||||||
|
LatencyMonitorSet()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
void init(const std::vector<LatencyMonitorConf>& conf);
|
||||||
|
const std::vector<LatencyMonitor>& latencyMonitors() const
|
||||||
|
{
|
||||||
|
return mPool;
|
||||||
|
}
|
||||||
|
int find(const String& name) const
|
||||||
|
{
|
||||||
|
auto it = mNameIdx.find(name);
|
||||||
|
return it == mNameIdx.end() ? -1 : it->second;
|
||||||
|
}
|
||||||
|
const std::vector<int>& cmdIndex(Command::Type type) const
|
||||||
|
{
|
||||||
|
return mCmdIdx[type];
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
std::vector<LatencyMonitor> mPool;
|
||||||
|
std::map<String, int> mNameIdx;
|
||||||
|
std::vector<std::vector<int>> mCmdIdx;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
129
src/List.h
Normal file
129
src/List.h
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_LIST_H_
|
||||||
|
#define _PREDIXY_LIST_H_
|
||||||
|
|
||||||
|
template<class T, class P = T*, int Size = 1>
|
||||||
|
class ListNode
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
typedef P Ptr;
|
||||||
|
ListNode()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < Size; ++i) {
|
||||||
|
mNext[i] = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
~ListNode()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < Size; ++i) {
|
||||||
|
mNext[i] = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void reset(int idx = 0)
|
||||||
|
{
|
||||||
|
mNext[idx] = nullptr;
|
||||||
|
}
|
||||||
|
void concat(T* obj, int idx = 0)
|
||||||
|
{
|
||||||
|
mNext[idx] = obj;
|
||||||
|
}
|
||||||
|
P next(int idx = 0) const
|
||||||
|
{
|
||||||
|
return mNext[idx];
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
P mNext[Size];
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class N, int Idx = 0>
|
||||||
|
class List
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
typedef typename N::Value T;
|
||||||
|
typedef typename N::ListNodeType Node;
|
||||||
|
typedef typename Node::Ptr P;
|
||||||
|
public:
|
||||||
|
List():
|
||||||
|
mSize(0),
|
||||||
|
mHead(nullptr),
|
||||||
|
mTail(nullptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
~List()
|
||||||
|
{
|
||||||
|
while (mSize > 0) {
|
||||||
|
pop_front();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
P next(T* obj)
|
||||||
|
{
|
||||||
|
return node(obj)->next(Idx);
|
||||||
|
}
|
||||||
|
void push_back(T* obj)
|
||||||
|
{
|
||||||
|
N* p = static_cast<N*>(obj);
|
||||||
|
if (mTail) {
|
||||||
|
static_cast<Node*>((T*)mTail)->concat(p, Idx);
|
||||||
|
mTail = p;
|
||||||
|
} else {
|
||||||
|
mHead = mTail = p;
|
||||||
|
}
|
||||||
|
++mSize;
|
||||||
|
}
|
||||||
|
void push_front(T* obj)
|
||||||
|
{
|
||||||
|
N* p = static_cast<N*>(obj);
|
||||||
|
if (mHead) {
|
||||||
|
node(obj)->concat(mHead, Idx);
|
||||||
|
mHead = p;
|
||||||
|
} else {
|
||||||
|
mHead = mTail = p;
|
||||||
|
}
|
||||||
|
++mSize;
|
||||||
|
}
|
||||||
|
P pop_front()
|
||||||
|
{
|
||||||
|
P obj = mHead;
|
||||||
|
if (obj) {
|
||||||
|
Node* n = node((T*)obj);
|
||||||
|
mHead = n->next(Idx);
|
||||||
|
if (--mSize == 0) {
|
||||||
|
mTail = nullptr;
|
||||||
|
}
|
||||||
|
n->reset(Idx);
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
P front() const
|
||||||
|
{
|
||||||
|
return mHead;
|
||||||
|
}
|
||||||
|
P back() const
|
||||||
|
{
|
||||||
|
return mTail;
|
||||||
|
}
|
||||||
|
int size() const
|
||||||
|
{
|
||||||
|
return mSize;
|
||||||
|
}
|
||||||
|
bool empty() const
|
||||||
|
{
|
||||||
|
return mSize == 0;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
static Node* node(T* obj)
|
||||||
|
{
|
||||||
|
return static_cast<N*>(obj);
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
int mSize;
|
||||||
|
P mHead;
|
||||||
|
P mTail;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
57
src/ListenSocket.cpp
Normal file
57
src/ListenSocket.cpp
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include "Util.h"
|
||||||
|
#include "ListenSocket.h"
|
||||||
|
|
||||||
|
ListenSocket::ListenSocket(const char* addr, int type, int protocol)
|
||||||
|
{
|
||||||
|
mClassType = ListenType;
|
||||||
|
strncpy(mAddr, addr, sizeof(mAddr));
|
||||||
|
sockaddr_storage saddr;
|
||||||
|
socklen_t len = sizeof(saddr);
|
||||||
|
getFirstAddr(addr, type, protocol, (sockaddr*)&saddr, &len);
|
||||||
|
sockaddr* in = (sockaddr*)&saddr;
|
||||||
|
int fd = Socket::socket(in->sa_family, type, protocol);
|
||||||
|
int flags = 1;
|
||||||
|
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &flags, sizeof(flags));
|
||||||
|
int ret = ::bind(fd, in, len);
|
||||||
|
if (ret != 0) {
|
||||||
|
int err = errno;
|
||||||
|
::close(fd);
|
||||||
|
errno = err;
|
||||||
|
Throw(BindFail, "socket bind to %s fail %s", addr, StrError());
|
||||||
|
}
|
||||||
|
attach(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ListenSocket::listen(int backlog)
|
||||||
|
{
|
||||||
|
int ret = ::listen(fd(), backlog);
|
||||||
|
if (ret != 0) {
|
||||||
|
Throw(ListenFail, "socket listen fail %s", StrError());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int ListenSocket::accept(sockaddr* addr, socklen_t* len)
|
||||||
|
{
|
||||||
|
while (true) {
|
||||||
|
int c = ::accept(fd(), addr, len);
|
||||||
|
if (c < 0) {
|
||||||
|
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||||
|
return -1;
|
||||||
|
} else if (errno == EINTR || errno == ECONNABORTED) {
|
||||||
|
continue;
|
||||||
|
} else if (errno == EMFILE || errno == ENFILE) {
|
||||||
|
Throw(TooManyOpenFiles, "socket accept fail %s", StrError());
|
||||||
|
}
|
||||||
|
Throw(AcceptFail, "socket accept fail %s", StrError());
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
31
src/ListenSocket.h
Normal file
31
src/ListenSocket.h
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_LISTEN_SOCKET_H_
|
||||||
|
#define _PREDIXY_LISTEN_SOCKET_H_
|
||||||
|
|
||||||
|
#include "Socket.h"
|
||||||
|
|
||||||
|
class ListenSocket : public Socket
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DefException(BindFail);
|
||||||
|
DefException(ListenFail);
|
||||||
|
DefException(TooManyOpenFiles);
|
||||||
|
DefException(AcceptFail);
|
||||||
|
public:
|
||||||
|
ListenSocket(const char* addr, int type, int protocol = 0);
|
||||||
|
void listen(int backlog = 511);
|
||||||
|
int accept(sockaddr* addr, socklen_t* len);
|
||||||
|
const char* addr() const
|
||||||
|
{
|
||||||
|
return mAddr;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
char mAddr[128];
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
191
src/LogFileSink.cpp
Normal file
191
src/LogFileSink.cpp
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <strings.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <sstream>
|
||||||
|
#include "LogFileSink.h"
|
||||||
|
#include "Logger.h"
|
||||||
|
|
||||||
|
static const int DaySecs = 86400;
|
||||||
|
static const int FileSuffixReserveLen = 20;
|
||||||
|
|
||||||
|
LogFileSink::LogFileSink():
|
||||||
|
mFilePathLen(0),
|
||||||
|
mFileSuffixFmt(nullptr),
|
||||||
|
mFile(nullptr),
|
||||||
|
mRotateSecs(0),
|
||||||
|
mRotateBytes(0),
|
||||||
|
mLastReopenTime(0),
|
||||||
|
mBytes(0),
|
||||||
|
mError(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
LogFileSink::~LogFileSink()
|
||||||
|
{
|
||||||
|
if (mFile && mFile != stdout) {
|
||||||
|
fclose(mFile);
|
||||||
|
mFile = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LogFileSink::parseRotate(const char* rotate, int& secs, long& bytes)
|
||||||
|
{
|
||||||
|
secs = 0;
|
||||||
|
bytes = 0;
|
||||||
|
if (!rotate || *rotate == '\0') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
std::istringstream iss(rotate);
|
||||||
|
std::string s;
|
||||||
|
while (iss >> s) {
|
||||||
|
int n;
|
||||||
|
char unit[4];
|
||||||
|
int c = sscanf(s.c_str(), "%d%2s", &n, unit);
|
||||||
|
if (c != 2 || n <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (strcmp(unit, "d") == 0) {
|
||||||
|
if (n != 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
secs = DaySecs;
|
||||||
|
} else if (strcmp(unit, "h") == 0) {
|
||||||
|
if (n > 24) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
secs = n * 3600;
|
||||||
|
} else if (strcmp(unit, "m") == 0) {
|
||||||
|
if (n > 1440) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
secs = n * 60;
|
||||||
|
} else if (strcmp(unit, "G") == 0) {
|
||||||
|
bytes = n * (1 << 30);
|
||||||
|
} else if (strcmp(unit, "M") == 0) {
|
||||||
|
bytes = n * (1 << 20);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
time_t LogFileSink::roundTime(time_t t)
|
||||||
|
{
|
||||||
|
if (mRotateSecs > 0) {
|
||||||
|
struct tm m;
|
||||||
|
localtime_r(&t, &m);
|
||||||
|
m.tm_sec = 0;
|
||||||
|
m.tm_min = 0;
|
||||||
|
m.tm_hour = 0;
|
||||||
|
time_t start = mktime(&m);
|
||||||
|
return start + (t - start) / mRotateSecs * mRotateSecs;
|
||||||
|
}
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LogFileSink::reopen(time_t t)
|
||||||
|
{
|
||||||
|
if (mFileSuffixFmt) {
|
||||||
|
struct tm m;
|
||||||
|
localtime_r(&t, &m);
|
||||||
|
char* p = mFilePath + mFilePathLen;
|
||||||
|
int len = MaxPathLen - mFilePathLen;
|
||||||
|
int num = strftime(p, len, mFileSuffixFmt, &m);
|
||||||
|
if (num <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mFilePathLen > 0) {
|
||||||
|
FILE* f = fopen(mFilePath, "a");
|
||||||
|
if (!f) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (mFile && mFile != stdout) {
|
||||||
|
fclose(mFile);
|
||||||
|
}
|
||||||
|
mFile = f;
|
||||||
|
if (mLastReopenTime != t) {
|
||||||
|
mBytes = ftell(f);
|
||||||
|
mLastReopenTime = t;
|
||||||
|
}
|
||||||
|
if (mFileSuffixFmt) {
|
||||||
|
unlink(mFileName.c_str());
|
||||||
|
if (symlink(mFilePath, mFileName.c_str()) == -1) {
|
||||||
|
fprintf(stderr, "create symbol link for %s fail", mFileName.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mFile = stdout;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LogFileSink::setFile(const char* path, int rotateSecs, long rotateBytes)
|
||||||
|
{
|
||||||
|
int len = strlen(path);
|
||||||
|
if (rotateSecs > 0 || rotateBytes > 0) {
|
||||||
|
if (len > 4 && strcasecmp(path + len - 4, ".log") == 0) {
|
||||||
|
len -= 4;
|
||||||
|
}
|
||||||
|
if (len + FileSuffixReserveLen >= MaxPathLen) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mFileName = path;
|
||||||
|
mFilePathLen = len;
|
||||||
|
memcpy(mFilePath, path, len);
|
||||||
|
mFilePath[len] = '\0';
|
||||||
|
mRotateSecs = rotateSecs;
|
||||||
|
mRotateBytes = rotateBytes;
|
||||||
|
if (len > 0) {
|
||||||
|
if (rotateBytes > 0) {
|
||||||
|
mFileSuffixFmt = ".%Y%m%d%H%M%S.log";
|
||||||
|
} else if (rotateSecs >= 86400) {
|
||||||
|
mFileSuffixFmt = ".%Y%m%d.log";
|
||||||
|
} else if (rotateSecs >= 3600) {
|
||||||
|
mFileSuffixFmt = ".%Y%m%d%H.log";
|
||||||
|
} else if (rotateSecs > 0) {
|
||||||
|
mFileSuffixFmt = ".%Y%m%d%H%M.log";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
time_t now = time(nullptr);
|
||||||
|
return reopen(mRotateBytes > 0 ? now : roundTime(now));
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogFileSink::checkRotate()
|
||||||
|
{
|
||||||
|
bool rotate = false;
|
||||||
|
time_t now = time(nullptr);
|
||||||
|
if (mRotateBytes > 0 && mBytes >= mRotateBytes) {
|
||||||
|
rotate = reopen(now);
|
||||||
|
}
|
||||||
|
if (!rotate && mRotateSecs > 0) {
|
||||||
|
now = roundTime(now);
|
||||||
|
if (now > mLastReopenTime &&
|
||||||
|
now - roundTime(mLastReopenTime) >= mRotateSecs) {
|
||||||
|
rotate = reopen(now);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogFileSink::write(const LogUnit* log)
|
||||||
|
{
|
||||||
|
if (mFile) {
|
||||||
|
int len = log->length();
|
||||||
|
int n = fwrite(log->data(), len, 1, mFile);
|
||||||
|
if (n != 1) {
|
||||||
|
++mError;
|
||||||
|
} else {
|
||||||
|
mBytes += len;
|
||||||
|
}
|
||||||
|
fflush(mFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
47
src/LogFileSink.h
Normal file
47
src/LogFileSink.h
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_LOG_FILE_SINK_H_
|
||||||
|
#define _PREDIXY_LOG_FILE_SINK_H_
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <atomic>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class LogUnit;
|
||||||
|
|
||||||
|
class LogFileSink
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LogFileSink();
|
||||||
|
~LogFileSink();
|
||||||
|
bool setFile(const char* path, int rotateSecs = 0, long rotateBytes = 0);
|
||||||
|
void checkRotate();
|
||||||
|
void write(const LogUnit* log);
|
||||||
|
int fd() const
|
||||||
|
{
|
||||||
|
return mFile ? fileno(mFile) : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool parseRotate(const char* rotate, int& secs, long& bytes);
|
||||||
|
static const int MaxPathLen = 1024;
|
||||||
|
private:
|
||||||
|
time_t roundTime(time_t t);
|
||||||
|
bool reopen(time_t t);
|
||||||
|
private:
|
||||||
|
std::string mFileName;
|
||||||
|
char mFilePath[MaxPathLen];
|
||||||
|
int mFilePathLen;
|
||||||
|
const char* mFileSuffixFmt;
|
||||||
|
FILE* mFile;
|
||||||
|
int mRotateSecs;
|
||||||
|
long mRotateBytes;
|
||||||
|
time_t mLastReopenTime;
|
||||||
|
long mBytes;
|
||||||
|
long mError;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
214
src/Logger.cpp
Normal file
214
src/Logger.cpp
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <chrono>
|
||||||
|
#include "LogFileSink.h"
|
||||||
|
#include "Util.h"
|
||||||
|
#include "Logger.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
const char* LogLevel::Str[Sentinel] = {
|
||||||
|
"V",
|
||||||
|
"D",
|
||||||
|
"I",
|
||||||
|
"N",
|
||||||
|
"W",
|
||||||
|
"E"
|
||||||
|
};
|
||||||
|
|
||||||
|
LogUnit::LogUnit():
|
||||||
|
mLen(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
LogUnit::~LogUnit()
|
||||||
|
{
|
||||||
|
mLen = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogUnit::format(LogLevel::Type level, const char* file, int line, const char* fmt, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap, fmt);
|
||||||
|
vformat(level, file, line, fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogUnit::vformat(LogLevel::Type level, const char* file, int line, const char* fmt, va_list ap)
|
||||||
|
{
|
||||||
|
long us = Util::nowUSec();
|
||||||
|
time_t t = us / 1000000;
|
||||||
|
struct tm m;
|
||||||
|
localtime_r(&t, &m);
|
||||||
|
char* p = mBuf;
|
||||||
|
size_t len = MaxLogLen;
|
||||||
|
int n = strftime(p, len, "%Y-%m-%d %H:%M:%S", &m);
|
||||||
|
p += n;
|
||||||
|
mLen = n;
|
||||||
|
len -= n;
|
||||||
|
us %= 1000000;
|
||||||
|
n = snprintf(p, len, ".%06ld %s %s:%d ", us, LogLevel::Str[level], file, line);
|
||||||
|
p += n;
|
||||||
|
mLen += n;
|
||||||
|
len -= n;
|
||||||
|
n = vsnprintf(p, len, fmt, ap);
|
||||||
|
mLen += n;
|
||||||
|
if (mLen >= MaxLogLen) {
|
||||||
|
mLen = MaxLogLen - 1;
|
||||||
|
}
|
||||||
|
mBuf[mLen++] = '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
thread_local long Logger::LogCnt[LogLevel::Sentinel]{0, 0, 0, 0, 0, 0};
|
||||||
|
Logger* Logger::gInst = NULL;
|
||||||
|
|
||||||
|
Logger::Logger(int maxLogUnitNum):
|
||||||
|
mStop(false),
|
||||||
|
mAllowMissLog(true),
|
||||||
|
mMissLogs(0),
|
||||||
|
mLogSample{0, 0, 100, 1, 1, 1},
|
||||||
|
mLogUnitCnt(0),
|
||||||
|
mLogs(maxLogUnitNum),
|
||||||
|
mFree(maxLogUnitNum),
|
||||||
|
mFileSink(nullptr)
|
||||||
|
{
|
||||||
|
mLogs.resize(0);
|
||||||
|
mFree.resize(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger::~Logger()
|
||||||
|
{
|
||||||
|
if (mFileSink) {
|
||||||
|
delete mFileSink;
|
||||||
|
}
|
||||||
|
if (mThread) {
|
||||||
|
delete mThread;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger::setLogFile(const char* file, int rotateSecs, long rotateBytes)
|
||||||
|
{
|
||||||
|
if (!mFileSink) {
|
||||||
|
mFileSink = new LogFileSink();
|
||||||
|
}
|
||||||
|
if (!mFileSink->setFile(file, rotateSecs, rotateBytes)) {
|
||||||
|
Throw(SetLogFileFail, "set log file %s fail %s", file, StrError());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger::start()
|
||||||
|
{
|
||||||
|
mThread = new std::thread([=](){this->run();});
|
||||||
|
mThread->detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger::stop()
|
||||||
|
{
|
||||||
|
mStop = true;
|
||||||
|
std::unique_lock<std::mutex> lck(mMtx);
|
||||||
|
mCond.notify_one();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger::run()
|
||||||
|
{
|
||||||
|
std::vector<LogUnit*> logs(mFree.capacity());
|
||||||
|
logs.resize(0);
|
||||||
|
while (!mStop) {
|
||||||
|
long missLogs = 0;
|
||||||
|
do {
|
||||||
|
std::unique_lock<std::mutex> lck(mMtx);
|
||||||
|
while (mLogs.empty() && !mStop) {
|
||||||
|
mCond.wait(lck);
|
||||||
|
}
|
||||||
|
logs.swap(mLogs);
|
||||||
|
missLogs = mMissLogs;
|
||||||
|
mMissLogs = 0;
|
||||||
|
} while (false);
|
||||||
|
if (mFileSink) {
|
||||||
|
mFileSink->checkRotate();
|
||||||
|
for (auto log : logs) {
|
||||||
|
mFileSink->write(log);
|
||||||
|
std::unique_lock<std::mutex> lck(mMtx);
|
||||||
|
mFree.push_back(log);
|
||||||
|
mCond.notify_one();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
std::unique_lock<std::mutex> lck(mMtx);
|
||||||
|
for (auto log : logs) {
|
||||||
|
mFree.push_back(log);
|
||||||
|
}
|
||||||
|
mCond.notify_one();
|
||||||
|
}
|
||||||
|
logs.resize(0);
|
||||||
|
if (missLogs > 0 && mFileSink) {
|
||||||
|
LogUnit log;
|
||||||
|
log.format(LogLevel::Notice, __FILE__, __LINE__, "MissLog count %ld", missLogs);
|
||||||
|
mFileSink->write(&log);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger::log(LogLevel::Type lvl, const char* file, int line, const char* fmt, ...)
|
||||||
|
{
|
||||||
|
LogUnit* log = getLogUnit();
|
||||||
|
if (!log) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap, fmt);
|
||||||
|
log->vformat(lvl, file, line, fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
put(log);
|
||||||
|
}
|
||||||
|
|
||||||
|
LogUnit* Logger::getLogUnit()
|
||||||
|
{
|
||||||
|
LogUnit* log = nullptr;
|
||||||
|
if (mAllowMissLog) {
|
||||||
|
std::unique_lock<std::mutex> lck(mMtx, std::try_to_lock_t());
|
||||||
|
if (!lck) {
|
||||||
|
++mMissLogs;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
if (mFree.size() > 0) {
|
||||||
|
log = mFree.back();
|
||||||
|
mFree.resize(mFree.size() - 1);
|
||||||
|
} else if (mLogUnitCnt < mFree.capacity()) {
|
||||||
|
++mLogUnitCnt;
|
||||||
|
} else {
|
||||||
|
++mMissLogs;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
std::unique_lock<std::mutex> lck(mMtx);
|
||||||
|
if (!mFree.empty()) {
|
||||||
|
log = mFree.back();
|
||||||
|
mFree.resize(mFree.size() - 1);
|
||||||
|
} else if (mLogUnitCnt < mFree.capacity()) {
|
||||||
|
++mLogUnitCnt;
|
||||||
|
} else {
|
||||||
|
while (mFree.empty() && !mStop) {
|
||||||
|
mCond.wait(lck);
|
||||||
|
}
|
||||||
|
if (!mFree.empty()) {
|
||||||
|
log = mFree.back();
|
||||||
|
mFree.resize(mFree.size() - 1);
|
||||||
|
} else {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return log ? log : new LogUnit();
|
||||||
|
}
|
||||||
|
|
||||||
|
int Logger::logFileFd() const
|
||||||
|
{
|
||||||
|
return mFileSink ? mFileSink->fd() : -1;
|
||||||
|
}
|
||||||
141
src/Logger.h
Normal file
141
src/Logger.h
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_LOGGER_H_
|
||||||
|
#define _PREDIXY_LOGGER_H_
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <atomic>
|
||||||
|
#include <mutex>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <thread>
|
||||||
|
#include "Exception.h"
|
||||||
|
|
||||||
|
class LogFileSink;
|
||||||
|
|
||||||
|
class LogLevel
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum Type
|
||||||
|
{
|
||||||
|
Verb,
|
||||||
|
Debug,
|
||||||
|
Info,
|
||||||
|
Notice,
|
||||||
|
Warn,
|
||||||
|
Error,
|
||||||
|
|
||||||
|
Sentinel
|
||||||
|
};
|
||||||
|
static const char* Str[Sentinel];
|
||||||
|
};
|
||||||
|
|
||||||
|
class LogUnit
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static const int MaxLogLen = 1024;
|
||||||
|
public:
|
||||||
|
LogUnit();
|
||||||
|
~LogUnit();
|
||||||
|
void format(LogLevel::Type level, const char* file, int line, const char* fmt, ...);
|
||||||
|
void vformat(LogLevel::Type level, const char* file, int line, const char* fmt, va_list ap);
|
||||||
|
const char* data() const {return mBuf;}
|
||||||
|
int length() const {return mLen;}
|
||||||
|
private:
|
||||||
|
int mLen;
|
||||||
|
char mBuf[MaxLogLen];
|
||||||
|
};
|
||||||
|
|
||||||
|
class Logger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DefException(SetLogFileFail);
|
||||||
|
public:
|
||||||
|
Logger(int maxLogUnitNum = 1024);
|
||||||
|
~Logger();
|
||||||
|
void start();
|
||||||
|
void stop();
|
||||||
|
void setLogFile(const char* file, int rotateSecs, long rotateBytes);
|
||||||
|
bool allowMissLog() const
|
||||||
|
{
|
||||||
|
return mAllowMissLog;
|
||||||
|
}
|
||||||
|
void setAllowMissLog(bool v)
|
||||||
|
{
|
||||||
|
mAllowMissLog = v;
|
||||||
|
}
|
||||||
|
int logSample(LogLevel::Type lvl) const
|
||||||
|
{
|
||||||
|
return mLogSample[lvl];
|
||||||
|
}
|
||||||
|
void setLogSample(LogLevel::Type lvl, int val)
|
||||||
|
{
|
||||||
|
mLogSample[lvl] = val;
|
||||||
|
}
|
||||||
|
LogUnit* log(LogLevel::Type lvl)
|
||||||
|
{
|
||||||
|
if (mLogSample[lvl] <= 0 || ++LogCnt[lvl] % mLogSample[lvl] != 0) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return getLogUnit();
|
||||||
|
}
|
||||||
|
void put(LogUnit* u)
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lck(mMtx);
|
||||||
|
mLogs.push_back(u);
|
||||||
|
mCond.notify_one();
|
||||||
|
}
|
||||||
|
void log(LogLevel::Type lvl, const char* file, int line, const char* fmt, ...);
|
||||||
|
int logFileFd() const;
|
||||||
|
static Logger* gInst;
|
||||||
|
private:
|
||||||
|
LogUnit* getLogUnit();
|
||||||
|
void run();
|
||||||
|
private:
|
||||||
|
bool mStop;
|
||||||
|
bool mAllowMissLog;
|
||||||
|
long mMissLogs;
|
||||||
|
int mLogSample[LogLevel::Sentinel];
|
||||||
|
unsigned mLogUnitCnt;
|
||||||
|
std::vector<LogUnit*> mLogs;
|
||||||
|
std::vector<LogUnit*> mFree;
|
||||||
|
std::mutex mMtx;
|
||||||
|
std::condition_variable mCond;
|
||||||
|
std::thread* mThread;
|
||||||
|
LogFileSink* mFileSink;
|
||||||
|
thread_local static long LogCnt[LogLevel::Sentinel];
|
||||||
|
};
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
|
||||||
|
#define logVerb(fmt, ...)
|
||||||
|
#define logDebug(fmt, ...)
|
||||||
|
#define logInfo(fmt, ...)
|
||||||
|
#define logNotice(fmt, ...)
|
||||||
|
#define logWarn(fmt, ...)
|
||||||
|
#define logError(fmt, ...)
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
#define logMacroImpl(lvl, fmt, ...) \
|
||||||
|
do { \
|
||||||
|
if (auto _lu_ = Logger::gInst->log(lvl)) { \
|
||||||
|
_lu_->format(lvl, __FILE__, __LINE__, fmt, ##__VA_ARGS__);\
|
||||||
|
Logger::gInst->put(_lu_); \
|
||||||
|
} \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
#define logVerb(fmt, ...) logMacroImpl(LogLevel::Verb, fmt, ##__VA_ARGS__)
|
||||||
|
#define logDebug(fmt, ...) logMacroImpl(LogLevel::Debug, fmt, ##__VA_ARGS__)
|
||||||
|
#define logInfo(fmt, ...) logMacroImpl(LogLevel::Info, fmt, ##__VA_ARGS__)
|
||||||
|
#define logNotice(fmt, ...) logMacroImpl(LogLevel::Notice, fmt, ##__VA_ARGS__)
|
||||||
|
#define logWarn(fmt, ...) logMacroImpl(LogLevel::Warn, fmt, ##__VA_ARGS__)
|
||||||
|
#define logError(fmt, ...) logMacroImpl(LogLevel::Error, fmt, ##__VA_ARGS__)
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
113
src/Makefile
Normal file
113
src/Makefile
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
CXX ?= g++
|
||||||
|
LVL ?= -O3
|
||||||
|
Opts += $(LVL)
|
||||||
|
|
||||||
|
ifeq ($(MT), false)
|
||||||
|
Opts += -D_PREDIXY_SINGLE_THREAD_
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(TS), true)
|
||||||
|
Opts += -D_PREDIXY_TIMER_STATS_
|
||||||
|
endif
|
||||||
|
|
||||||
|
EV ?= auto
|
||||||
|
|
||||||
|
LDLIBCPP = -Wl,-Bstatic -lstdc++ -Wl,-Bdynamic
|
||||||
|
|
||||||
|
ifeq ($(EV), auto)
|
||||||
|
plt = $(shell uname)
|
||||||
|
ifeq ($(plt), Linux)
|
||||||
|
EV = epoll
|
||||||
|
Opts += -D_PREDIXY_BACKTRACE_
|
||||||
|
else ifeq ($(plt), Darwin)
|
||||||
|
EV = kqueue
|
||||||
|
Opts += -D_PREDIXY_BACKTRACE_
|
||||||
|
LDLIBCPP = -static-libstdc++
|
||||||
|
else ifeq ($(plt), FreeBSD)
|
||||||
|
EV = kqueue
|
||||||
|
Opts += -D_PREDIXY_BACKTRACE_
|
||||||
|
LDFLAGS += $(shell pkg info -Dx gcc|grep 'rpath') -lexecinfo
|
||||||
|
else ifeq ($(plt), OpenBSD)
|
||||||
|
EV = kqueue
|
||||||
|
else
|
||||||
|
EV = poll
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(EV), epoll)
|
||||||
|
multiplexor = EpollMultiplexor
|
||||||
|
Opts += -D_EPOLL_
|
||||||
|
else ifeq ($(EV), poll)
|
||||||
|
multiplexor = PollMultiplexor
|
||||||
|
Opts += -D_POLL_
|
||||||
|
else ifeq ($(EV), kqueue)
|
||||||
|
multiplexor = KqueueMultiplexor
|
||||||
|
Opts += -D_KQUEUE_
|
||||||
|
else
|
||||||
|
$(error Unknown event:$(EV))
|
||||||
|
endif
|
||||||
|
|
||||||
|
CFLAGS = -std=c++11 -g -Wall -w $(Opts)
|
||||||
|
INCFLAGS =
|
||||||
|
LDFLAGS += $(LDLIBCPP) -rdynamic -lpthread
|
||||||
|
|
||||||
|
|
||||||
|
target = predixy
|
||||||
|
objs = \
|
||||||
|
Crc16.o \
|
||||||
|
HashFunc.o \
|
||||||
|
Timer.o \
|
||||||
|
Logger.o \
|
||||||
|
LogFileSink.o \
|
||||||
|
Alloc.o \
|
||||||
|
Socket.o \
|
||||||
|
ListenSocket.o \
|
||||||
|
AcceptSocket.o \
|
||||||
|
ConnectSocket.o \
|
||||||
|
$(multiplexor).o \
|
||||||
|
Subscribe.o \
|
||||||
|
Connection.o \
|
||||||
|
AcceptConnection.o \
|
||||||
|
ConnectConnection.o \
|
||||||
|
Buffer.o \
|
||||||
|
Command.o \
|
||||||
|
Distribution.o \
|
||||||
|
Reply.o \
|
||||||
|
ConfParser.o \
|
||||||
|
Conf.o \
|
||||||
|
Auth.o \
|
||||||
|
DC.o \
|
||||||
|
LatencyMonitor.o \
|
||||||
|
RequestParser.o \
|
||||||
|
Request.o \
|
||||||
|
ResponseParser.o \
|
||||||
|
Response.o \
|
||||||
|
Server.o \
|
||||||
|
ServerGroup.o \
|
||||||
|
ServerPool.o \
|
||||||
|
ClusterNodesParser.o \
|
||||||
|
ClusterServerPool.o \
|
||||||
|
SentinelServerPool.o \
|
||||||
|
ConnectConnectionPool.o \
|
||||||
|
Handler.o \
|
||||||
|
Proxy.o \
|
||||||
|
main.o
|
||||||
|
|
||||||
|
.PHONY : default debug clean
|
||||||
|
|
||||||
|
default: $(target)
|
||||||
|
|
||||||
|
|
||||||
|
$(target): $(objs)
|
||||||
|
$(CXX) $(CFLAGS) -o $@ $^ $(LDFLAGS)
|
||||||
|
|
||||||
|
debug:
|
||||||
|
@make LVL=
|
||||||
|
|
||||||
|
clean:
|
||||||
|
@rm -rf $(objs) $(target)
|
||||||
|
@echo Done.
|
||||||
|
|
||||||
|
%.o : %.cpp
|
||||||
|
$(CXX) $(CFLAGS) -c $^ $(INCFLAGS)
|
||||||
|
|
||||||
39
src/Multiplexor.h
Normal file
39
src/Multiplexor.h
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_MULTIPLEXOR_H_
|
||||||
|
#define _PREDIXY_MULTIPLEXOR_H_
|
||||||
|
|
||||||
|
#include "Socket.h"
|
||||||
|
#include "Logger.h"
|
||||||
|
|
||||||
|
class MultiplexorBase
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum EventEnum
|
||||||
|
{
|
||||||
|
ReadEvent = 0x1,
|
||||||
|
WriteEvent = 0x2,
|
||||||
|
ErrorEvent = 0x4,
|
||||||
|
AddedMask = 0x8
|
||||||
|
};
|
||||||
|
public:
|
||||||
|
virtual ~MultiplexorBase() {}
|
||||||
|
protected:
|
||||||
|
MultiplexorBase() {}
|
||||||
|
private:
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef _KQUEUE_
|
||||||
|
#include "KqueueMultiplexor.h"
|
||||||
|
#elif _EPOLL_
|
||||||
|
#include "EpollMultiplexor.h"
|
||||||
|
#elif _POLL_
|
||||||
|
#include "PollMultiplexor.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
68
src/PollMultiplexor.cpp
Normal file
68
src/PollMultiplexor.cpp
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "PollMultiplexor.h"
|
||||||
|
#include "Socket.h"
|
||||||
|
|
||||||
|
PollMultiplexor::PollMultiplexor(int maxFdSize):
|
||||||
|
mSkts(maxFdSize),
|
||||||
|
mFds(maxFdSize)
|
||||||
|
{
|
||||||
|
mSkts.resize(0);
|
||||||
|
mFds.resize(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
PollMultiplexor::~PollMultiplexor()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PollMultiplexor::addSocket(Socket* s, int evts)
|
||||||
|
{
|
||||||
|
if (s->fd() >= (int)mSkts.size()) {
|
||||||
|
mSkts.resize(s->fd() + 1, {nullptr, -1});
|
||||||
|
}
|
||||||
|
mSkts[s->fd()].skt = s;
|
||||||
|
mSkts[s->fd()].idx = mFds.size();
|
||||||
|
short int e = 0;
|
||||||
|
e |= (evts & ReadEvent) ? POLLIN : 0;
|
||||||
|
e |= (evts & WriteEvent) ? POLLOUT : 0;
|
||||||
|
mFds.push_back({s->fd(), e, 0});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PollMultiplexor::delSocket(Socket* s)
|
||||||
|
{
|
||||||
|
if (s->fd() < (int)mSkts.size()) {
|
||||||
|
int idx = mSkts[s->fd()].idx;
|
||||||
|
if (idx >= 0 && idx < (int)mFds.size()) {
|
||||||
|
mFds[idx] = mFds.back();
|
||||||
|
mFds.resize(mFds.size() - 1);
|
||||||
|
if (!mFds.empty()) {
|
||||||
|
mSkts[mFds[idx].fd].idx = idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mSkts[s->fd()].skt = nullptr;
|
||||||
|
mSkts[s->fd()].idx = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PollMultiplexor::addEvent(Socket* s, int evts)
|
||||||
|
{
|
||||||
|
int e = 0;
|
||||||
|
e |= (evts & ReadEvent) ? POLLIN : 0;
|
||||||
|
e |= (evts & WriteEvent) ? POLLOUT : 0;
|
||||||
|
mFds[mSkts[s->fd()].idx].events |= e;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PollMultiplexor::delEvent(Socket* s, int evts)
|
||||||
|
{
|
||||||
|
int e = 0;
|
||||||
|
e |= (evts & ReadEvent) ? POLLIN : 0;
|
||||||
|
e |= (evts & WriteEvent) ? POLLOUT : 0;
|
||||||
|
mFds[mSkts[s->fd()].idx].events &= ~e;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
74
src/PollMultiplexor.h
Normal file
74
src/PollMultiplexor.h
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_POLL_MULTIPLEXOR_H_
|
||||||
|
#define _PREDIXY_POLL_MULTIPLEXOR_H_
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <poll.h>
|
||||||
|
#include <vector>
|
||||||
|
#include "Multiplexor.h"
|
||||||
|
#include "Util.h"
|
||||||
|
|
||||||
|
class PollMultiplexor : public MultiplexorBase
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DefException(PollWaitFail);
|
||||||
|
public:
|
||||||
|
PollMultiplexor(int maxFdSize = 81920);
|
||||||
|
~PollMultiplexor();
|
||||||
|
bool addSocket(Socket* s, int evts = ReadEvent);
|
||||||
|
void delSocket(Socket* s);
|
||||||
|
bool addEvent(Socket* s, int evts);
|
||||||
|
bool delEvent(Socket* s, int evts);
|
||||||
|
template<class T>
|
||||||
|
int wait(long usec, T* handler);
|
||||||
|
private:
|
||||||
|
struct SktIdx
|
||||||
|
{
|
||||||
|
Socket* skt;
|
||||||
|
int idx;
|
||||||
|
};
|
||||||
|
private:
|
||||||
|
std::vector<SktIdx> mSkts;
|
||||||
|
std::vector<struct pollfd> mFds;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
int PollMultiplexor::wait(long usec, T* handler)
|
||||||
|
{
|
||||||
|
int timeout = usec < 0 ? usec : usec / 1000;
|
||||||
|
logVerb("poll wait with timeout %ld usec", usec);
|
||||||
|
int num = poll(mFds.data(), mFds.size(), timeout);
|
||||||
|
logVerb("poll wait return with events:%d", num);
|
||||||
|
if (num == -1) {
|
||||||
|
if (errno == EINTR) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
Throw(PollWaitFail, "pool wait fail %s", StrError());
|
||||||
|
}
|
||||||
|
int cnt = 0;
|
||||||
|
for (auto e : mFds) {
|
||||||
|
if (e.revents) {
|
||||||
|
Socket* s = mSkts[e.fd].skt;
|
||||||
|
int evts = 0;
|
||||||
|
evts |= (e.revents & POLLIN) ? ReadEvent : 0;
|
||||||
|
evts |= (e.revents & POLLOUT) ? WriteEvent : 0;
|
||||||
|
evts |= (e.revents & (POLLERR|POLLHUP)) ? ErrorEvent : 0;
|
||||||
|
handler->handleEvent(s, evts);
|
||||||
|
if (++cnt == num) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
typedef PollMultiplexor Multiplexor;
|
||||||
|
|
||||||
|
#endif
|
||||||
76
src/Predixy.h
Normal file
76
src/Predixy.h
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_H_
|
||||||
|
#define _PREDIXY_H_
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <strings.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/uio.h>
|
||||||
|
|
||||||
|
class Socket;
|
||||||
|
class ListenSocket;
|
||||||
|
class AcceptSocket;
|
||||||
|
class ConnectSocket;
|
||||||
|
class Buffer;
|
||||||
|
class Command;
|
||||||
|
class Reply;
|
||||||
|
class Auth;
|
||||||
|
class Authority;
|
||||||
|
class DC;
|
||||||
|
class DataCenter;
|
||||||
|
class RequestParser;
|
||||||
|
class ResponseParser;
|
||||||
|
class Request;
|
||||||
|
class Response;
|
||||||
|
class AcceptConnection;
|
||||||
|
class ConnectConnection;
|
||||||
|
class ConnectConnectionPool;
|
||||||
|
class Server;
|
||||||
|
class ServerGroup;
|
||||||
|
class ServerPool;
|
||||||
|
struct AuthConf;
|
||||||
|
struct ServerConf;
|
||||||
|
struct ServerGroupConf;
|
||||||
|
struct ServerPoolConf;
|
||||||
|
struct SentinelServerPoolConf;
|
||||||
|
struct ClusterServerPoolConf;
|
||||||
|
class ConfParser;
|
||||||
|
class Conf;
|
||||||
|
class Transaction;
|
||||||
|
class Subscribe;
|
||||||
|
class Handler;
|
||||||
|
class Proxy;
|
||||||
|
|
||||||
|
#include "Common.h"
|
||||||
|
#include "Sync.h"
|
||||||
|
#include "HashFunc.h"
|
||||||
|
#include "ID.h"
|
||||||
|
#include "IOVec.h"
|
||||||
|
#include "List.h"
|
||||||
|
#include "Deque.h"
|
||||||
|
#include "Util.h"
|
||||||
|
#include "String.h"
|
||||||
|
#include "Timer.h"
|
||||||
|
#include "Exception.h"
|
||||||
|
#include "Logger.h"
|
||||||
|
#include "Alloc.h"
|
||||||
|
#include "Command.h"
|
||||||
|
#include "Reply.h"
|
||||||
|
#include "Multiplexor.h"
|
||||||
|
#include "Buffer.h"
|
||||||
|
|
||||||
|
typedef SharePtr<Request> RequestPtr;
|
||||||
|
typedef SharePtr<Response> ResponsePtr;
|
||||||
|
typedef SharePtr<AcceptConnection> AcceptConnectionPtr;
|
||||||
|
typedef SharePtr<ConnectConnection> ConnectConnectionPtr;
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
179
src/Proxy.cpp
Normal file
179
src/Proxy.cpp
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <thread>
|
||||||
|
#include "Proxy.h"
|
||||||
|
#include "Handler.h"
|
||||||
|
#include "Socket.h"
|
||||||
|
#include "Alloc.h"
|
||||||
|
#include "ListenSocket.h"
|
||||||
|
#include "AcceptSocket.h"
|
||||||
|
#include "RequestParser.h"
|
||||||
|
#include "Backtrace.h"
|
||||||
|
|
||||||
|
static bool Running = false;
|
||||||
|
static bool Abort = false;
|
||||||
|
static bool Stop = false;
|
||||||
|
|
||||||
|
static void abortHandler(int sig)
|
||||||
|
{
|
||||||
|
Abort = true;
|
||||||
|
if (sig == SIGABRT) {
|
||||||
|
traceInfo();
|
||||||
|
}
|
||||||
|
if (!Running) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void stopHandler(int sig)
|
||||||
|
{
|
||||||
|
Stop = true;
|
||||||
|
if (!Running) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Proxy::Proxy():
|
||||||
|
mListener(nullptr),
|
||||||
|
mDataCenter(nullptr),
|
||||||
|
mServPool(nullptr),
|
||||||
|
mStartTime(time(nullptr)),
|
||||||
|
mStatsVer(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Proxy::~Proxy()
|
||||||
|
{
|
||||||
|
for (auto h : mHandlers) {
|
||||||
|
delete h;
|
||||||
|
}
|
||||||
|
delete mServPool;
|
||||||
|
delete mDataCenter;
|
||||||
|
delete mListener;
|
||||||
|
delete mConf;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Proxy::init(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
signal(SIGPIPE, SIG_IGN);
|
||||||
|
signal(SIGFPE, abortHandler);
|
||||||
|
signal(SIGILL, abortHandler);
|
||||||
|
signal(SIGSEGV, abortHandler);
|
||||||
|
signal(SIGABRT, abortHandler);
|
||||||
|
signal(SIGBUS, abortHandler);
|
||||||
|
signal(SIGQUIT, abortHandler);
|
||||||
|
signal(SIGHUP, abortHandler);
|
||||||
|
signal(SIGINT, stopHandler);
|
||||||
|
signal(SIGTERM, stopHandler);
|
||||||
|
|
||||||
|
Command::init();
|
||||||
|
mConf = new Conf();
|
||||||
|
if (!mConf->init(argc, argv)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Logger::gInst = new Logger();
|
||||||
|
Logger::gInst->setLogFile(mConf->log(), mConf->logRotateSecs(), mConf->logRotateBytes());
|
||||||
|
Logger::gInst->setAllowMissLog(mConf->allowMissLog());
|
||||||
|
for (int i = 0; i < LogLevel::Sentinel; ++i) {
|
||||||
|
LogLevel::Type lvl = LogLevel::Type(i);
|
||||||
|
Logger::gInst->setLogSample(lvl, mConf->logSample(lvl));
|
||||||
|
}
|
||||||
|
Logger::gInst->start();
|
||||||
|
for (auto& ac : mConf->authConfs()) {
|
||||||
|
mAuthority.add(ac);
|
||||||
|
}
|
||||||
|
if (!mConf->localDC().empty()) {
|
||||||
|
mDataCenter = new DataCenter();
|
||||||
|
mDataCenter->init(mConf);
|
||||||
|
}
|
||||||
|
AllocBase::setMaxMemory(mConf->maxMemory());
|
||||||
|
if (mConf->bufSize() > 0) {
|
||||||
|
Buffer::setSize(mConf->bufSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
mLatencyMonitorSet.init(mConf->latencyMonitors());
|
||||||
|
Request::init();
|
||||||
|
Response::init();
|
||||||
|
ListenSocket* s = new ListenSocket(mConf->bind(), SOCK_STREAM);
|
||||||
|
if (!s->setNonBlock()) {
|
||||||
|
logError("proxy listener set nonblock fail:%s", StrError());
|
||||||
|
Throw(InitFail, "listener set nonblock", StrError());
|
||||||
|
}
|
||||||
|
s->listen();
|
||||||
|
mListener = s;
|
||||||
|
logNotice("predixy listen in %s", mConf->bind());
|
||||||
|
switch (mConf->serverPoolType()) {
|
||||||
|
case ServerPool::Cluster:
|
||||||
|
{
|
||||||
|
ClusterServerPool* p = new ClusterServerPool(this);
|
||||||
|
p->init(mConf->clusterServerPool());
|
||||||
|
mServPool = p;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ServerPool::Sentinel:
|
||||||
|
{
|
||||||
|
SentinelServerPool* p = new SentinelServerPool(this);
|
||||||
|
p->init(mConf->sentinelServerPool());
|
||||||
|
mServPool = p;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Throw(InitFail, "unknown server pool type");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < mConf->workerThreads(); ++i) {
|
||||||
|
Handler* h = new Handler(this);
|
||||||
|
mHandlers.push_back(h);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Proxy::run()
|
||||||
|
{
|
||||||
|
logNotice("predixy running with Name:%s Workers:%d",
|
||||||
|
mConf->name(),
|
||||||
|
(int)mHandlers.size());
|
||||||
|
std::vector<std::shared_ptr<std::thread>> tasks;
|
||||||
|
for (auto h : mHandlers) {
|
||||||
|
std::shared_ptr<std::thread> t(new std::thread([=](){h->run();}));
|
||||||
|
tasks.push_back(t);
|
||||||
|
}
|
||||||
|
Running = true;
|
||||||
|
bool stop = false;
|
||||||
|
while (!stop) {
|
||||||
|
if (Abort) {
|
||||||
|
stop = true;
|
||||||
|
abort();
|
||||||
|
} else if (Stop) {
|
||||||
|
fprintf(stderr, "predixy will quit ASAP Bye!\n");
|
||||||
|
stop = true;
|
||||||
|
}
|
||||||
|
if (!stop) {
|
||||||
|
sleep(1);
|
||||||
|
TimerPoint::report();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto h : mHandlers) {
|
||||||
|
h->stop();
|
||||||
|
}
|
||||||
|
for (auto t : tasks) {
|
||||||
|
t->join();
|
||||||
|
}
|
||||||
|
Logger::gInst->stop();
|
||||||
|
TimerPoint::report();
|
||||||
|
if (*mConf->bind() == '/') {
|
||||||
|
unlink(mConf->bind());
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
94
src/Proxy.h
Normal file
94
src/Proxy.h
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_PROXY_H_
|
||||||
|
#define _PREDIXY_PROXY_H_
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include "Predixy.h"
|
||||||
|
#include "Handler.h"
|
||||||
|
#include "DC.h"
|
||||||
|
#include "ServerPool.h"
|
||||||
|
#include "ClusterServerPool.h"
|
||||||
|
#include "SentinelServerPool.h"
|
||||||
|
#include "LatencyMonitor.h"
|
||||||
|
|
||||||
|
class Proxy
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DefException(InitFail);
|
||||||
|
public:
|
||||||
|
Proxy();
|
||||||
|
~Proxy();
|
||||||
|
bool init(int argc, char* argv[]);
|
||||||
|
int run();
|
||||||
|
time_t startTime() const
|
||||||
|
{
|
||||||
|
return mStartTime;
|
||||||
|
}
|
||||||
|
Conf* conf() const
|
||||||
|
{
|
||||||
|
return mConf;
|
||||||
|
}
|
||||||
|
ListenSocket* listener() const
|
||||||
|
{
|
||||||
|
return mListener;
|
||||||
|
}
|
||||||
|
const Authority* authority() const
|
||||||
|
{
|
||||||
|
return &mAuthority;
|
||||||
|
}
|
||||||
|
DataCenter* dataCenter() const
|
||||||
|
{
|
||||||
|
return mDataCenter;
|
||||||
|
}
|
||||||
|
ServerPool* serverPool() const
|
||||||
|
{
|
||||||
|
return mServPool;
|
||||||
|
}
|
||||||
|
bool isSplitMultiKey() const
|
||||||
|
{
|
||||||
|
return mConf->sentinelServerPool().groups.size() != 1;
|
||||||
|
}
|
||||||
|
bool supportTransaction() const
|
||||||
|
{
|
||||||
|
return mConf->sentinelServerPool().groups.size() == 1;
|
||||||
|
}
|
||||||
|
bool supportSubscribe() const
|
||||||
|
{
|
||||||
|
return mConf->sentinelServerPool().groups.size() == 1 ||
|
||||||
|
mConf->clusterServerPool().servers.size() > 0;
|
||||||
|
}
|
||||||
|
const std::vector<Handler*>& handlers() const
|
||||||
|
{
|
||||||
|
return mHandlers;
|
||||||
|
}
|
||||||
|
long statsVer() const
|
||||||
|
{
|
||||||
|
return mStatsVer;
|
||||||
|
}
|
||||||
|
long incrStatsVer()
|
||||||
|
{
|
||||||
|
return ++mStatsVer;
|
||||||
|
}
|
||||||
|
const LatencyMonitorSet& latencyMonitorSet() const
|
||||||
|
{
|
||||||
|
return mLatencyMonitorSet;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
Conf* mConf;
|
||||||
|
ListenSocket* mListener;
|
||||||
|
Authority mAuthority;
|
||||||
|
DataCenter* mDataCenter;
|
||||||
|
std::vector<Handler*> mHandlers;
|
||||||
|
ServerPool* mServPool;
|
||||||
|
time_t mStartTime;
|
||||||
|
AtomicLong mStatsVer;
|
||||||
|
LatencyMonitorSet mLatencyMonitorSet;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
16
src/Reply.cpp
Normal file
16
src/Reply.cpp
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Reply.h"
|
||||||
|
|
||||||
|
const char* Reply::TypeStr[Sentinel] = {
|
||||||
|
"None",
|
||||||
|
"Status",
|
||||||
|
"Err",
|
||||||
|
"Str",
|
||||||
|
"Int",
|
||||||
|
"Array"
|
||||||
|
};
|
||||||
27
src/Reply.h
Normal file
27
src/Reply.h
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_REPLY_H_
|
||||||
|
#define _PREDIXY_REPLY_H_
|
||||||
|
|
||||||
|
class Reply
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum Type
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Status,
|
||||||
|
Error,
|
||||||
|
String,
|
||||||
|
Integer,
|
||||||
|
Array,
|
||||||
|
|
||||||
|
Sentinel
|
||||||
|
};
|
||||||
|
static const char* TypeStr[Sentinel];
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
388
src/Request.cpp
Normal file
388
src/Request.cpp
Normal file
@ -0,0 +1,388 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Request.h"
|
||||||
|
#include "RequestParser.h"
|
||||||
|
#include "Response.h"
|
||||||
|
|
||||||
|
struct GenericRequest
|
||||||
|
{
|
||||||
|
Request::GenericCode code;
|
||||||
|
Command::Type type;
|
||||||
|
const char* content;
|
||||||
|
const Request* req;
|
||||||
|
};
|
||||||
|
|
||||||
|
static GenericRequest GenericRequests[] = {
|
||||||
|
{Request::Ping, Command::Ping, "*1\r\n$4\r\nping\r\n", nullptr},
|
||||||
|
{Request::PingServ, Command::PingServ, "*1\r\n$4\r\nping\r\n", nullptr},
|
||||||
|
{Request::ClusterNodes, Command::ClusterNodes, "*2\r\n$7\r\ncluster\r\n$5\r\nnodes\r\n", nullptr},
|
||||||
|
{Request::Asking, Command::Asking, "*1\r\n$6\r\nasking\r\n", nullptr},
|
||||||
|
{Request::Readonly, Command::Readonly, "*1\r\n$8\r\nreadonly\r\n", nullptr},
|
||||||
|
{Request::UnwatchServ, Command::UnwatchServ, "*1\r\n$7\r\nunwatch\r\n", nullptr},
|
||||||
|
{Request::DiscardServ, Command::DiscardServ, "*1\r\n$7\r\ndiscard\r\n", nullptr},
|
||||||
|
{Request::MgetHead, Command::Mget, "*2\r\n$4\r\nmget\r\n", nullptr},
|
||||||
|
{Request::MsetHead, Command::Mset, "*3\r\n$4\r\nmset\r\n", nullptr},
|
||||||
|
{Request::MsetnxHead, Command::Msetnx, "*3\r\n$6\r\nmsetnx\r\n", nullptr},
|
||||||
|
{Request::TouchHead, Command::Touch, "*2\r\n$5\r\ntouch\r\n", nullptr},
|
||||||
|
{Request::ExistsHead, Command::Exists, "*2\r\n$6\r\nexists\r\n", nullptr},
|
||||||
|
{Request::DelHead, Command::Del, "*2\r\n$3\r\ndel\r\n", nullptr},
|
||||||
|
{Request::UnlinkHead, Command::Unlink, "*2\r\n$6\r\nunlink\r\n", nullptr},
|
||||||
|
{Request::PsubscribeHead,Command::Psubscribe, "*2\r\n$10\r\npsubscribe\r\n", nullptr},
|
||||||
|
{Request::SubscribeHead,Command::Subscribe, "*2\r\n$9\r\nsubscribe\r\n", nullptr}
|
||||||
|
};
|
||||||
|
|
||||||
|
void Request::init()
|
||||||
|
{
|
||||||
|
BufferPtr buf = BufferAlloc::create();
|
||||||
|
for (auto& r : GenericRequests) {
|
||||||
|
Request* req = new Request();
|
||||||
|
req->mType= r.type;
|
||||||
|
if (buf->room() < (int)strlen(r.content)) {
|
||||||
|
buf = BufferAlloc::create();
|
||||||
|
}
|
||||||
|
buf = req->mReq.set(buf, r.content);
|
||||||
|
r.req = req;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Request::Request():
|
||||||
|
mConn(nullptr),
|
||||||
|
mType(Command::None),
|
||||||
|
mDone(false),
|
||||||
|
mDelivered(false),
|
||||||
|
mFollowers(0),
|
||||||
|
mFollowersDone(0),
|
||||||
|
mRedirectCnt(0),
|
||||||
|
mCreateTime(Util::elapsedUSec()),
|
||||||
|
mData(nullptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Request::Request(AcceptConnection* c):
|
||||||
|
mConn(c),
|
||||||
|
mType(Command::None),
|
||||||
|
mDone(false),
|
||||||
|
mDelivered(false),
|
||||||
|
mFollowers(0),
|
||||||
|
mFollowersDone(0),
|
||||||
|
mRedirectCnt(0),
|
||||||
|
mCreateTime(Util::elapsedUSec()),
|
||||||
|
mData(nullptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Request::Request(GenericCode code):
|
||||||
|
mConn(nullptr),
|
||||||
|
mDone(false),
|
||||||
|
mDelivered(false),
|
||||||
|
mFollowers(0),
|
||||||
|
mFollowersDone(0),
|
||||||
|
mRedirectCnt(0),
|
||||||
|
mCreateTime(Util::elapsedUSec()),
|
||||||
|
mData(nullptr)
|
||||||
|
{
|
||||||
|
auto r = GenericRequests[code].req;
|
||||||
|
mType = r->mType;
|
||||||
|
mReq = r->mReq;
|
||||||
|
}
|
||||||
|
|
||||||
|
Request::~Request()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void Request::clear()
|
||||||
|
{
|
||||||
|
mRes = nullptr;
|
||||||
|
mHead.clear();
|
||||||
|
mReq.clear();
|
||||||
|
mKey.clear();
|
||||||
|
mLeader = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Request::set(const RequestParser& p, Request* leader)
|
||||||
|
{
|
||||||
|
mType = p.type();
|
||||||
|
if (leader) {
|
||||||
|
const Request* r = nullptr;
|
||||||
|
switch (mType) {
|
||||||
|
case Command::Mget:
|
||||||
|
r = GenericRequests[MgetHead].req;
|
||||||
|
break;
|
||||||
|
case Command::Mset:
|
||||||
|
r = GenericRequests[MsetHead].req;
|
||||||
|
break;
|
||||||
|
case Command::Msetnx:
|
||||||
|
r = GenericRequests[MsetnxHead].req;
|
||||||
|
break;
|
||||||
|
case Command::Touch:
|
||||||
|
r = GenericRequests[TouchHead].req;
|
||||||
|
break;
|
||||||
|
case Command::Exists:
|
||||||
|
r = GenericRequests[ExistsHead].req;
|
||||||
|
break;
|
||||||
|
case Command::Del:
|
||||||
|
r = GenericRequests[DelHead].req;
|
||||||
|
break;
|
||||||
|
case Command::Unlink:
|
||||||
|
r = GenericRequests[UnlinkHead].req;
|
||||||
|
break;
|
||||||
|
case Command::Psubscribe:
|
||||||
|
r = GenericRequests[PsubscribeHead].req;
|
||||||
|
break;
|
||||||
|
case Command::Subscribe:
|
||||||
|
r = GenericRequests[SubscribeHead].req;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
//should never reach
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
mHead = r->mReq;
|
||||||
|
mReq = p.arg();
|
||||||
|
mLeader = leader;
|
||||||
|
if (leader == this) {
|
||||||
|
if (mType == Command::Mset || mType == Command::Msetnx) {
|
||||||
|
mFollowers = (p.argNum() - 1) >> 1;
|
||||||
|
} else {
|
||||||
|
mFollowers = p.argNum() - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mReq = p.request();
|
||||||
|
}
|
||||||
|
mKey = p.key();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Request::setAuth(const String& password)
|
||||||
|
{
|
||||||
|
mType = Command::AuthServ;
|
||||||
|
mHead.clear();
|
||||||
|
mReq.fset(nullptr,
|
||||||
|
"*2\r\n"
|
||||||
|
"$4\r\n"
|
||||||
|
"auth\r\n"
|
||||||
|
"$%d\r\n"
|
||||||
|
"%s\r\n",
|
||||||
|
password.length(), password.data() ? password.data() : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Request::setSelect(int db)
|
||||||
|
{
|
||||||
|
char buf[16];
|
||||||
|
int num = snprintf(buf, sizeof(buf), "%d", db);
|
||||||
|
mType = Command::SelectServ;
|
||||||
|
mHead.clear();
|
||||||
|
mReq.fset(nullptr,
|
||||||
|
"*2\r\n"
|
||||||
|
"$6\r\n"
|
||||||
|
"select\r\n"
|
||||||
|
"$%d\r\n"
|
||||||
|
"%s\r\n",
|
||||||
|
num, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Request::setSentinels(const String& master)
|
||||||
|
{
|
||||||
|
mType = Command::SentinelSentinels;
|
||||||
|
mHead.clear();
|
||||||
|
mReq.fset(nullptr,
|
||||||
|
"*3\r\n"
|
||||||
|
"$8\r\n"
|
||||||
|
"sentinel\r\n"
|
||||||
|
"$9\r\n"
|
||||||
|
"sentinels\r\n"
|
||||||
|
"$%d\r\n"
|
||||||
|
"%.*s\r\n",
|
||||||
|
master.length(), master.length(), master.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Request::setSentinelGetMaster(const String& master)
|
||||||
|
{
|
||||||
|
mType = Command::SentinelGetMaster;
|
||||||
|
mHead.clear();
|
||||||
|
mReq.fset(nullptr,
|
||||||
|
"*3\r\n"
|
||||||
|
"$8\r\n"
|
||||||
|
"sentinel\r\n"
|
||||||
|
"$23\r\n"
|
||||||
|
"get-master-addr-by-name\r\n"
|
||||||
|
"$%d\r\n"
|
||||||
|
"%.*s\r\n",
|
||||||
|
master.length(), master.length(), master.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Request::setSentinelSlaves(const String& master)
|
||||||
|
{
|
||||||
|
mType = Command::SentinelSlaves;
|
||||||
|
mHead.clear();
|
||||||
|
mReq.fset(nullptr,
|
||||||
|
"*3\r\n"
|
||||||
|
"$8\r\n"
|
||||||
|
"sentinel\r\n"
|
||||||
|
"$6\r\n"
|
||||||
|
"slaves\r\n"
|
||||||
|
"$%d\r\n"
|
||||||
|
"%.*s\r\n",
|
||||||
|
master.length(), master.length(), master.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Request::adjustScanCursor(long cursor)
|
||||||
|
{
|
||||||
|
char buf[32];
|
||||||
|
int n = snprintf(buf, sizeof(buf), "%ld", cursor);
|
||||||
|
if (mHead.empty()) {
|
||||||
|
SegmentStr<64> str(mReq);
|
||||||
|
const char* p = strchr(str.data(), '$');
|
||||||
|
if (!p) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
p = strchr(p + 1, '$');
|
||||||
|
if (!p) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mHead.fset(nullptr,
|
||||||
|
"%.*s"
|
||||||
|
"$%d\r\n"
|
||||||
|
"%s\r\n",
|
||||||
|
p - str.data(), str.data(), n, buf);
|
||||||
|
p = strchr(p, '\r');
|
||||||
|
if (!p) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mReq.cut(p - str.data() + 2 + mKey.length() + 2);
|
||||||
|
} else {
|
||||||
|
SegmentStr<64> str(mHead);
|
||||||
|
const char* p = strchr(str.data(), '$');
|
||||||
|
if (!p) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
p = strchr(p + 1, '$');
|
||||||
|
if (!p) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mHead.fset(nullptr,
|
||||||
|
"%.*s"
|
||||||
|
"$%d\r\n"
|
||||||
|
"%s\r\n",
|
||||||
|
p - str.data(), str.data(), n, buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Request::follow(Request* leader)
|
||||||
|
{
|
||||||
|
++mFollowers;
|
||||||
|
if (leader == this) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mType = leader->mType;
|
||||||
|
mHead = leader->mHead;
|
||||||
|
mReq = leader->mReq;
|
||||||
|
mKey = leader->mKey;
|
||||||
|
mLeader = leader;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Request::send(Socket* s)
|
||||||
|
{
|
||||||
|
const char* dat;
|
||||||
|
int len;
|
||||||
|
while (mHead.get(dat, len)) {
|
||||||
|
int n = s->write(dat, len);
|
||||||
|
if (n > 0) {
|
||||||
|
mHead.use(n);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (mReq.get(dat, len)) {
|
||||||
|
int n = s->write(dat, len);
|
||||||
|
if (n > 0) {
|
||||||
|
mReq.use(n);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Request::fill(IOVec* vecs, int len)
|
||||||
|
{
|
||||||
|
bool all = false;
|
||||||
|
int n = mHead.fill(vecs, len, all);
|
||||||
|
if (!all) {
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
n += mReq.fill(vecs + n, len - n, all);
|
||||||
|
if (n > 0 && all) {
|
||||||
|
vecs[n - 1].req = this;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Request::setResponse(Response* res)
|
||||||
|
{
|
||||||
|
mDone = true;
|
||||||
|
if (mLeader) {
|
||||||
|
mLeader->mFollowersDone += 1;
|
||||||
|
switch (mType) {
|
||||||
|
case Command::Mget:
|
||||||
|
mRes = res;
|
||||||
|
break;
|
||||||
|
case Command::Mset:
|
||||||
|
if (Response* leaderRes = mLeader->getResponse()) {
|
||||||
|
if (res->isError() && !leaderRes->isError()) {
|
||||||
|
mLeader->mRes = res;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mLeader->mRes = res;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Command::Msetnx:
|
||||||
|
case Command::Touch:
|
||||||
|
case Command::Exists:
|
||||||
|
case Command::Del:
|
||||||
|
case Command::Unlink:
|
||||||
|
if (!mLeader->mRes) {
|
||||||
|
mLeader->mRes = res;
|
||||||
|
}
|
||||||
|
if (mLeader->isDone()) {
|
||||||
|
mLeader->mRes->set(mLeader->mRes->integer());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Command::ScriptLoad:
|
||||||
|
if (Response* leaderRes = mLeader->getResponse()) {
|
||||||
|
if (leaderRes->isString() && !res->isString()) {
|
||||||
|
mLeader->mRes = res;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mLeader->mRes = res;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
//should never reach here
|
||||||
|
mRes = res;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mRes = res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Request::isDone() const
|
||||||
|
{
|
||||||
|
if (mLeader == this) {
|
||||||
|
switch (mType) {
|
||||||
|
case Command::Mget:
|
||||||
|
case Command::Psubscribe:
|
||||||
|
case Command::Subscribe:
|
||||||
|
return mDone;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return mFollowers == mFollowersDone;
|
||||||
|
}
|
||||||
|
return mDone;
|
||||||
|
}
|
||||||
|
|
||||||
177
src/Request.h
Normal file
177
src/Request.h
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_REQUEST_H_
|
||||||
|
#define _PREDIXY_REQUEST_H_
|
||||||
|
|
||||||
|
#include "Predixy.h"
|
||||||
|
|
||||||
|
enum RequestListIndex
|
||||||
|
{
|
||||||
|
Recv = 0,
|
||||||
|
Send,
|
||||||
|
|
||||||
|
Size
|
||||||
|
};
|
||||||
|
|
||||||
|
class Request :
|
||||||
|
public TID<Request>,
|
||||||
|
public ListNode<Request, SharePtr<Request>, RequestListIndex::Size>,
|
||||||
|
public RefCntObj<Request>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
typedef Request Value;
|
||||||
|
typedef ListNode<Request, SharePtr<Request>, RequestListIndex::Size> ListNodeType;
|
||||||
|
static const int MaxRedirectLimit = 3;
|
||||||
|
enum GenericCode
|
||||||
|
{
|
||||||
|
Ping,
|
||||||
|
PingServ,
|
||||||
|
ClusterNodes,
|
||||||
|
Asking,
|
||||||
|
Readonly,
|
||||||
|
UnwatchServ,
|
||||||
|
DiscardServ,
|
||||||
|
MgetHead,
|
||||||
|
MsetHead,
|
||||||
|
MsetnxHead,
|
||||||
|
TouchHead,
|
||||||
|
ExistsHead,
|
||||||
|
DelHead,
|
||||||
|
UnlinkHead,
|
||||||
|
PsubscribeHead,
|
||||||
|
SubscribeHead
|
||||||
|
};
|
||||||
|
static void init();
|
||||||
|
public:
|
||||||
|
Request();
|
||||||
|
Request(AcceptConnection* c);
|
||||||
|
Request(GenericCode code);
|
||||||
|
~Request();
|
||||||
|
void clear();
|
||||||
|
void set(const RequestParser& p, Request* leader = nullptr);
|
||||||
|
void setAuth(const String& password);
|
||||||
|
void setSelect(int db);
|
||||||
|
void setSentinels(const String& master);
|
||||||
|
void setSentinelGetMaster(const String& master);
|
||||||
|
void setSentinelSlaves(const String& master);
|
||||||
|
void adjustScanCursor(long cursor);
|
||||||
|
void follow(Request* leader);
|
||||||
|
void setResponse(Response* res);
|
||||||
|
bool send(Socket* s);
|
||||||
|
int fill(IOVec* vecs, int len);
|
||||||
|
bool isDone() const;
|
||||||
|
AcceptConnection* connection() const
|
||||||
|
{
|
||||||
|
return mConn;
|
||||||
|
}
|
||||||
|
void detach()
|
||||||
|
{
|
||||||
|
mConn = nullptr;
|
||||||
|
}
|
||||||
|
void setType(Command::Type t)
|
||||||
|
{
|
||||||
|
mType = t;
|
||||||
|
}
|
||||||
|
Command::Type type() const
|
||||||
|
{
|
||||||
|
return mType;
|
||||||
|
}
|
||||||
|
const char* cmd() const
|
||||||
|
{
|
||||||
|
return Command::get(mType).name;
|
||||||
|
}
|
||||||
|
bool isInner() const
|
||||||
|
{
|
||||||
|
return Command::get(mType).mode & Command::Inner;
|
||||||
|
}
|
||||||
|
const Segment& key() const
|
||||||
|
{
|
||||||
|
return mKey;
|
||||||
|
}
|
||||||
|
const Segment& body() const
|
||||||
|
{
|
||||||
|
return mReq;
|
||||||
|
}
|
||||||
|
void setData(void* dat)
|
||||||
|
{
|
||||||
|
mData = dat;
|
||||||
|
}
|
||||||
|
void* data() const
|
||||||
|
{
|
||||||
|
return mData;
|
||||||
|
}
|
||||||
|
void rewind()
|
||||||
|
{
|
||||||
|
mHead.rewind();
|
||||||
|
mReq.rewind();
|
||||||
|
}
|
||||||
|
Response* getResponse() const
|
||||||
|
{
|
||||||
|
return mRes;
|
||||||
|
}
|
||||||
|
Request* leader() const
|
||||||
|
{
|
||||||
|
return mLeader;
|
||||||
|
}
|
||||||
|
bool isLeader() const
|
||||||
|
{
|
||||||
|
return mLeader == this;
|
||||||
|
}
|
||||||
|
bool isDelivered() const
|
||||||
|
{
|
||||||
|
return mDelivered;
|
||||||
|
}
|
||||||
|
void setDelivered()
|
||||||
|
{
|
||||||
|
mDelivered = true;
|
||||||
|
}
|
||||||
|
int followers() const
|
||||||
|
{
|
||||||
|
return mFollowers;
|
||||||
|
}
|
||||||
|
int redirectCnt() const
|
||||||
|
{
|
||||||
|
return mRedirectCnt;
|
||||||
|
}
|
||||||
|
int incrRedirectCnt()
|
||||||
|
{
|
||||||
|
return ++mRedirectCnt;
|
||||||
|
}
|
||||||
|
bool requireWrite() const
|
||||||
|
{
|
||||||
|
return Command::get(mType).mode & Command::Write;
|
||||||
|
}
|
||||||
|
bool requirePrivateConnection() const
|
||||||
|
{
|
||||||
|
return Command::get(mType).mode & Command::Private;
|
||||||
|
}
|
||||||
|
long createTime() const
|
||||||
|
{
|
||||||
|
return mCreateTime;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
AcceptConnection* mConn;
|
||||||
|
Command::Type mType;
|
||||||
|
ResponsePtr mRes;
|
||||||
|
bool mDone;
|
||||||
|
bool mDelivered;
|
||||||
|
Segment mHead; //for multi key command mget/mset/del...
|
||||||
|
Segment mReq;
|
||||||
|
Segment mKey;
|
||||||
|
RequestPtr mLeader;
|
||||||
|
int mFollowers;
|
||||||
|
int mFollowersDone;
|
||||||
|
int mRedirectCnt;
|
||||||
|
long mCreateTime; //steady time point us
|
||||||
|
void* mData; //user data for response
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef List<Request, RequestListIndex::Recv> RecvRequestList;
|
||||||
|
typedef List<Request, RequestListIndex::Send> SendRequestList;
|
||||||
|
typedef Alloc<Request, Const::RequestAllocCacheSize> RequestAlloc;
|
||||||
|
|
||||||
|
#endif
|
||||||
290
src/RequestParser.cpp
Normal file
290
src/RequestParser.cpp
Normal file
@ -0,0 +1,290 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "RequestParser.h"
|
||||||
|
|
||||||
|
RequestParser::RequestParser()
|
||||||
|
{
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
RequestParser::~RequestParser()
|
||||||
|
{
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RequestParser::reset()
|
||||||
|
{
|
||||||
|
mType = Command::None;
|
||||||
|
mCommand = nullptr;
|
||||||
|
mReq.clear();
|
||||||
|
mCmd.clear();
|
||||||
|
mArg.clear();
|
||||||
|
mKey.clear();
|
||||||
|
mStatus = Normal;
|
||||||
|
mState = Idle;
|
||||||
|
mInline = false;
|
||||||
|
mArgNum = 0;
|
||||||
|
mArgCnt = 0;
|
||||||
|
mArgLen = 0;
|
||||||
|
mArgBodyCnt = 0;
|
||||||
|
mByteCnt = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RequestParser::isKey(bool split) const
|
||||||
|
{
|
||||||
|
if (mCommand) {
|
||||||
|
switch (mCommand->mode & Command::KeyMask) {
|
||||||
|
case Command::NoKey:
|
||||||
|
return false;
|
||||||
|
case Command::MultiKey:
|
||||||
|
return split ? mArgCnt > 0 : mArgCnt == 1;
|
||||||
|
case Command::SMultiKey:
|
||||||
|
return mArgCnt > 0;
|
||||||
|
case Command::MultiKeyVal:
|
||||||
|
return split ? (mArgCnt & 1) : mArgCnt == 1;
|
||||||
|
case Command::KeyAt3:
|
||||||
|
return mArgCnt == 3;
|
||||||
|
default:
|
||||||
|
return mArgCnt == 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RequestParser::Status RequestParser::parse(Buffer* buf, int& pos, bool split)
|
||||||
|
{
|
||||||
|
FuncCallTimer();
|
||||||
|
int start = pos;
|
||||||
|
char* cursor = buf->data() + pos;
|
||||||
|
char* end = buf->tail();
|
||||||
|
int error = 0;
|
||||||
|
while (cursor < end && !error) {
|
||||||
|
++mByteCnt;
|
||||||
|
if (mStatus != Normal && mByteCnt > MaxAllowInvalidByteCount) {
|
||||||
|
logWarn("request command argument invalid");
|
||||||
|
error = __LINE__;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
char ch = *cursor;
|
||||||
|
switch (mState) {
|
||||||
|
case Idle:
|
||||||
|
mArgNum = 0;
|
||||||
|
if (ch == '*') {
|
||||||
|
mReq.begin().buf = buf;
|
||||||
|
mReq.begin().pos = pos;
|
||||||
|
mState = ArgNum;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
mState = InlineBegin;
|
||||||
|
mInline = true;
|
||||||
|
}
|
||||||
|
case InlineBegin:
|
||||||
|
if (ch == '\r') {
|
||||||
|
error = __LINE__;
|
||||||
|
} else if (!isspace(ch)) {
|
||||||
|
mReq.begin().buf = buf;
|
||||||
|
mReq.begin().pos = pos;
|
||||||
|
mCmd.begin() = mReq.begin();
|
||||||
|
mState = InlineCmd;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case InlineCmd:
|
||||||
|
if (isspace(ch)) {
|
||||||
|
mCmd.end().buf = buf;
|
||||||
|
mCmd.end().pos = pos;
|
||||||
|
parseCmd();
|
||||||
|
mState = ch == '\r' ? InlineLF : InlineArg;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case InlineArg:
|
||||||
|
if (ch == '\r') {
|
||||||
|
mState = InlineLF;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case InlineLF:
|
||||||
|
if (ch == '\n') {
|
||||||
|
mState = Finished;
|
||||||
|
goto Done;
|
||||||
|
} else {
|
||||||
|
error = __LINE__;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ArgNum:
|
||||||
|
if (ch >= '0' && ch <= '9') {
|
||||||
|
mArgNum = mArgNum * 10 + (ch - '0');
|
||||||
|
} else if (ch == '\r') {
|
||||||
|
//mState = mArgNum > 0 ? ArgNumLF : Error;
|
||||||
|
mArgNum > 0 ? mState = ArgNumLF : error = __LINE__;
|
||||||
|
} else {
|
||||||
|
error = __LINE__;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ArgNumLF:
|
||||||
|
mArgCnt = 0;
|
||||||
|
//mState = ch == '\n' ? ArgTag : Error;
|
||||||
|
ch == '\n' ? mState = ArgTag : error = __LINE__;
|
||||||
|
break;
|
||||||
|
case ArgTag:
|
||||||
|
mArgLen = 0;
|
||||||
|
if (ch == '$') {
|
||||||
|
mState = ArgLen;
|
||||||
|
if (isKey(split)) {
|
||||||
|
mArg.begin().buf = buf;
|
||||||
|
mArg.begin().pos = pos;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error = __LINE__;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ArgLen:
|
||||||
|
if (ch >= '0' && ch <= '9') {
|
||||||
|
mArgLen = mArgLen * 10 + (ch - '0');
|
||||||
|
} else if (ch == '\r') {
|
||||||
|
//mState = mArgLen >= 0 ? ArgLenLF : Error;
|
||||||
|
mArgLen >= 0 ? mState = ArgLenLF : error = __LINE__;
|
||||||
|
} else {
|
||||||
|
error = __LINE__;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ArgLenLF:
|
||||||
|
mArgBodyCnt = 0;
|
||||||
|
ch == '\n' ? mState = ArgBody : error = __LINE__;
|
||||||
|
break;
|
||||||
|
case ArgBody:
|
||||||
|
if (mArgBodyCnt == 0) {
|
||||||
|
if (mArgCnt == 0) {
|
||||||
|
mCmd.begin().buf = buf;
|
||||||
|
mCmd.begin().pos = pos;
|
||||||
|
} else if (isKey(split)) {
|
||||||
|
mKey.begin().buf = buf;
|
||||||
|
mKey.begin().pos = pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mArgBodyCnt + (end - cursor) > mArgLen) {
|
||||||
|
pos += mArgLen - mArgBodyCnt;
|
||||||
|
cursor = buf->data() + pos;
|
||||||
|
if (*cursor == '\r') {
|
||||||
|
mState = ArgBodyLF;
|
||||||
|
if (mArgCnt == 0) {
|
||||||
|
mCmd.end().buf = buf;
|
||||||
|
mCmd.end().pos = pos;
|
||||||
|
parseCmd();
|
||||||
|
} else if (isKey(split)) {
|
||||||
|
mKey.end().buf = buf;
|
||||||
|
mKey.end().pos = pos;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error = __LINE__;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mArgBodyCnt += end - cursor;
|
||||||
|
pos = buf->length() - 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ArgBodyLF:
|
||||||
|
if (ch == '\n') {
|
||||||
|
if (++mArgCnt == mArgNum) {
|
||||||
|
mState = Finished;
|
||||||
|
goto Done;
|
||||||
|
} else {
|
||||||
|
mState = ArgTag;
|
||||||
|
if (mArgCnt > 1 && isKey(split) && mStatus == Normal &&
|
||||||
|
(mCommand->mode&(Command::MultiKey|Command::SMultiKey|Command::MultiKeyVal))) {
|
||||||
|
goto Done;
|
||||||
|
}
|
||||||
|
if (mArgCnt > 1 && mCommand && mStatus == Normal && split) {
|
||||||
|
if (mCommand->isMultiKey()) {
|
||||||
|
goto Done;
|
||||||
|
} else if (mCommand->isMultiKeyVal() && (mArgCnt & 1)) {
|
||||||
|
goto Done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error = __LINE__;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
error = __LINE__;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++pos;
|
||||||
|
cursor = buf->data() + pos;
|
||||||
|
}
|
||||||
|
if (error) {
|
||||||
|
SString<64> bufHex;
|
||||||
|
bufHex.printHex(buf->data() + start, buf->length() - start);
|
||||||
|
SString<16> errHex;
|
||||||
|
errHex.printHex(cursor - 1, end - cursor + 1);
|
||||||
|
logDebug("request parse error %d state %d buf:%s errpos %d err:%s",
|
||||||
|
error, mState, bufHex.data(),
|
||||||
|
pos - 1 - start, errHex.data());
|
||||||
|
return ParseError;
|
||||||
|
}
|
||||||
|
return Normal;
|
||||||
|
Done:
|
||||||
|
mReq.end().buf = buf;
|
||||||
|
mReq.end().pos = ++pos;
|
||||||
|
mReq.rewind();
|
||||||
|
mArg.end() = mReq.end();
|
||||||
|
mArg.rewind();
|
||||||
|
if (mState == Finished) {
|
||||||
|
return mStatus == Normal ? Complete : mStatus;
|
||||||
|
} else {
|
||||||
|
return mStatus == Normal ? Partial : ParseError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RequestParser::parseCmd()
|
||||||
|
{
|
||||||
|
FuncCallTimer();
|
||||||
|
SegmentStr<MaxCmdLen> cmd(mCmd);
|
||||||
|
if (!cmd.complete()) {
|
||||||
|
mStatus = CmdError;
|
||||||
|
mType = Command::None;
|
||||||
|
logNotice("unknown request cmd too long:%.*s...",
|
||||||
|
cmd.length(), cmd.data());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto c = Command::find(cmd);
|
||||||
|
if (!c) {
|
||||||
|
mStatus = CmdError;
|
||||||
|
mType = Command::None;
|
||||||
|
logNotice("unknown request cmd:%.*s", cmd.length(), cmd.data());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mType = c->type;
|
||||||
|
mCommand = c;
|
||||||
|
if (mInline) {
|
||||||
|
if (mType != Command::Ping) {
|
||||||
|
mStatus = CmdError;
|
||||||
|
logNotice("unsupport command %s in inline command protocol", c->name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mArgNum < c->minArgs || mArgNum > c->maxArgs) {
|
||||||
|
mStatus = ArgError;
|
||||||
|
logNotice("request argument is invalid cmd %.*s argnum %d",
|
||||||
|
cmd.length(), cmd.data(), mArgNum);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (mType) {
|
||||||
|
case Command::Mset:
|
||||||
|
case Command::Msetnx:
|
||||||
|
if (!(mArgNum & 1)) {
|
||||||
|
mStatus = ArgError;
|
||||||
|
logNotice("request argument is invalid cmd %.*s argnum %d",
|
||||||
|
cmd.length(), cmd.data(), mArgNum);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
124
src/RequestParser.h
Normal file
124
src/RequestParser.h
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_REQUEST_PARSER_H_
|
||||||
|
#define _PREDIXY_REQUEST_PARSER_H_
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include "Predixy.h"
|
||||||
|
|
||||||
|
class RequestParser
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static const int MaxAllowInvalidByteCount = 1024;
|
||||||
|
static const int MaxCmdLen = 32;
|
||||||
|
enum State
|
||||||
|
{
|
||||||
|
Idle, // * or inline command
|
||||||
|
InlineBegin,
|
||||||
|
InlineCmd,
|
||||||
|
InlineLF,
|
||||||
|
InlineArg,
|
||||||
|
ArgNum, // 2
|
||||||
|
ArgNumLF, // \r\n
|
||||||
|
ArgTag, // $ $
|
||||||
|
ArgLen, // 3 5
|
||||||
|
ArgLenLF, // \r\n \r\n
|
||||||
|
ArgBody, // get hello
|
||||||
|
ArgBodyLF, // \r\n \r\n
|
||||||
|
Finished,
|
||||||
|
|
||||||
|
Error
|
||||||
|
};
|
||||||
|
enum Status
|
||||||
|
{
|
||||||
|
Normal,
|
||||||
|
Partial,
|
||||||
|
Complete,
|
||||||
|
CmdError,
|
||||||
|
ArgError,
|
||||||
|
ParseError
|
||||||
|
};
|
||||||
|
static void init();
|
||||||
|
public:
|
||||||
|
RequestParser();
|
||||||
|
~RequestParser();
|
||||||
|
Status parse(Buffer* buf, int& pos, bool split);
|
||||||
|
void reset();
|
||||||
|
bool isIdle() const
|
||||||
|
{
|
||||||
|
return mState == Idle;
|
||||||
|
}
|
||||||
|
Command::Type type() const
|
||||||
|
{
|
||||||
|
return mType;
|
||||||
|
}
|
||||||
|
const Command* command() const
|
||||||
|
{
|
||||||
|
return mCommand;
|
||||||
|
}
|
||||||
|
bool isInline() const
|
||||||
|
{
|
||||||
|
return mInline;
|
||||||
|
}
|
||||||
|
Segment& request()
|
||||||
|
{
|
||||||
|
return mReq;
|
||||||
|
}
|
||||||
|
const Segment& request() const
|
||||||
|
{
|
||||||
|
return mReq;
|
||||||
|
}
|
||||||
|
int argNum() const
|
||||||
|
{
|
||||||
|
return mArgNum;
|
||||||
|
}
|
||||||
|
Segment& arg()
|
||||||
|
{
|
||||||
|
return mArg;
|
||||||
|
}
|
||||||
|
const Segment& arg() const
|
||||||
|
{
|
||||||
|
return mArg;
|
||||||
|
}
|
||||||
|
Segment& cmd()
|
||||||
|
{
|
||||||
|
return mCmd;
|
||||||
|
}
|
||||||
|
const Segment& cmd() const
|
||||||
|
{
|
||||||
|
return mCmd;
|
||||||
|
}
|
||||||
|
Segment& key()
|
||||||
|
{
|
||||||
|
return mKey;
|
||||||
|
}
|
||||||
|
const Segment& key() const
|
||||||
|
{
|
||||||
|
return mKey;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
void parseCmd();
|
||||||
|
bool isKey(bool split) const;
|
||||||
|
private:
|
||||||
|
Command::Type mType;
|
||||||
|
const Command* mCommand;
|
||||||
|
// *2\r\n$3\r\nget\r\n$3\r\nkey\r\n
|
||||||
|
Segment mReq; // |------------------------------|
|
||||||
|
Segment mCmd; // |-|
|
||||||
|
Segment mArg; // |-----------|
|
||||||
|
Segment mKey; // |-|
|
||||||
|
Status mStatus;
|
||||||
|
State mState;
|
||||||
|
bool mInline;
|
||||||
|
int mArgNum;
|
||||||
|
int mArgCnt;
|
||||||
|
int mArgLen;
|
||||||
|
int mArgBodyCnt;
|
||||||
|
int mByteCnt;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
213
src/Response.cpp
Normal file
213
src/Response.cpp
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Response.h"
|
||||||
|
#include "Request.h"
|
||||||
|
|
||||||
|
struct GenericResponse
|
||||||
|
{
|
||||||
|
Response::GenericCode code;
|
||||||
|
Reply::Type type;
|
||||||
|
const char* content;
|
||||||
|
const Response* res;
|
||||||
|
};
|
||||||
|
|
||||||
|
static GenericResponse GenericResponses[] = {
|
||||||
|
{Response::Pong, Reply::Status, "+PONG\r\n", nullptr},
|
||||||
|
{Response::Ok, Reply::Status, "+OK\r\n", nullptr},
|
||||||
|
{Response::Cmd, Reply::Array, "*0\r\n"},
|
||||||
|
{Response::UnknownCmd, Reply::Error, "-ERR unknown command\r\n", nullptr},
|
||||||
|
{Response::ArgWrong, Reply::Error, "-ERR argument wrong\r\n", nullptr},
|
||||||
|
{Response::InvalidDb, Reply::Error, "-ERR invalid DB index\r\n", nullptr},
|
||||||
|
{Response::NoPasswordSet, Reply::Error, "-ERR Client sent AUTH, but no password is set\r\n", nullptr},
|
||||||
|
{Response::InvalidPassword, Reply::Error, "-ERR invalid password\r\n", nullptr},
|
||||||
|
{Response::Unauth, Reply::Error, "-NOAUTH Authentication required.\r\n", nullptr},
|
||||||
|
{Response::PermissionDeny, Reply::Error, "-ERR auth permission deny\r\n", nullptr},
|
||||||
|
{Response::NoServer, Reply::Error, "-ERR no server avaliable\r\n", nullptr},
|
||||||
|
{Response::NoServerConnection, Reply::Error, "-ERR no server connection avaliable\r\n", nullptr},
|
||||||
|
{Response::ServerConnectionClose, Reply::Error, "-ERR server connection close\r\n", nullptr},
|
||||||
|
{Response::DeliverRequestFail, Reply::Error, "-ERR deliver request fail\r\n", nullptr},
|
||||||
|
{Response::ForbidTransaction, Reply::Error, "-ERR forbid transaction in current server pool\r\n", nullptr},
|
||||||
|
{Response::ConfigSubCmdUnknown, Reply::Error, "-ERR CONFIG subcommand must be one of GET, SET\r\n", nullptr},
|
||||||
|
{Response::InvalidScanCursor, Reply::Error, "-ERR invalid cursor\r\n", nullptr},
|
||||||
|
{Response::ScanEnd, Reply::Array, "*2\r\n$1\r\n0\r\n*0\r\n", nullptr}
|
||||||
|
};
|
||||||
|
|
||||||
|
void Response::init()
|
||||||
|
{
|
||||||
|
BufferPtr buf = BufferAlloc::create();
|
||||||
|
for (auto& r : GenericResponses) {
|
||||||
|
Response* res = new Response();
|
||||||
|
res->mType = r.type;
|
||||||
|
if (buf->room() < (int)strlen(r.content)) {
|
||||||
|
buf = BufferAlloc::create();
|
||||||
|
}
|
||||||
|
buf = res->mRes.set(buf, r.content);
|
||||||
|
r.res = res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Response::Response():
|
||||||
|
mType(Reply::None),
|
||||||
|
mInteger(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Response::Response(GenericCode code):
|
||||||
|
mType(Reply::None),
|
||||||
|
mInteger(0)
|
||||||
|
{
|
||||||
|
auto r = GenericResponses[code].res;
|
||||||
|
mType = r->mType;
|
||||||
|
mRes = r->mRes;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Response::~Response()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void Response::set(const ResponseParser& p)
|
||||||
|
{
|
||||||
|
mType = p.type();
|
||||||
|
mInteger = p.integer();
|
||||||
|
mHead.clear();
|
||||||
|
mRes = p.response();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Response::set(int64_t num)
|
||||||
|
{
|
||||||
|
mType = Reply::Integer;
|
||||||
|
mInteger = num;
|
||||||
|
mHead.clear();
|
||||||
|
mRes.fset(nullptr, ":%ld\r\n", num);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Response::setStr(const char* str, int len)
|
||||||
|
{
|
||||||
|
mType = Reply::String;
|
||||||
|
mHead.clear();
|
||||||
|
if (len < 0) {
|
||||||
|
len = strlen(str);
|
||||||
|
}
|
||||||
|
mRes.fset(nullptr, "$%d\r\n%.*s\r\n", len, len, str);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Response::setErr(const char* str, int len)
|
||||||
|
{
|
||||||
|
mType = Reply::Error;
|
||||||
|
mHead.clear();
|
||||||
|
if (len < 0) {
|
||||||
|
len = strlen(str);
|
||||||
|
}
|
||||||
|
mRes.fset(nullptr, "-ERR %.*s\r\n", len, str);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Response::adjustForLeader(Request* req)
|
||||||
|
{
|
||||||
|
if (Request* leader = req->leader()) {
|
||||||
|
switch (req->type()) {
|
||||||
|
case Command::Mget:
|
||||||
|
if (mType == Reply::Array) {
|
||||||
|
mRes.cut(4);// cut "*1\r\n"
|
||||||
|
if (leader == req) {
|
||||||
|
mHead.fset(nullptr, "*%d\r\n", req->followers());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mType = Reply::Array;
|
||||||
|
if (leader == req) {
|
||||||
|
mRes.fset(nullptr, "*%d\r\n$-1\r\n", req->followers());
|
||||||
|
} else {
|
||||||
|
mRes.set(nullptr, "$-1\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Command::Mset:
|
||||||
|
break;
|
||||||
|
case Command::Msetnx:
|
||||||
|
case Command::Touch:
|
||||||
|
case Command::Exists:
|
||||||
|
case Command::Del:
|
||||||
|
case Command::Unlink:
|
||||||
|
if (Response* r = leader->getResponse()) {
|
||||||
|
if (r != this && mType == Reply::Integer && mInteger > 0) {
|
||||||
|
r->mInteger += mInteger;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Response::send(Socket* s)
|
||||||
|
{
|
||||||
|
const char* dat;
|
||||||
|
int len;
|
||||||
|
while (mHead.get(dat, len)) {
|
||||||
|
int n = s->write(dat, len);
|
||||||
|
if (n > 0) {
|
||||||
|
mHead.use(n);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (mRes.get(dat, len)) {
|
||||||
|
int n = s->write(dat, len);
|
||||||
|
if (n > 0) {
|
||||||
|
mRes.use(n);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Response::fill(IOVec* vecs, int len, Request* req) const
|
||||||
|
{
|
||||||
|
bool all = false;
|
||||||
|
int n = mHead.fill(vecs, len, all);
|
||||||
|
if (!all) {
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
n += mRes.fill(vecs + n, len - n, all);
|
||||||
|
if (n > 0 && all) {
|
||||||
|
vecs[n - 1].req = req;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Response::getAddr(int& slot, SString<Const::MaxAddrLen>& addr, const char* token) const
|
||||||
|
{
|
||||||
|
SegmentStr<Const::MaxAddrLen + 16> str(mRes);
|
||||||
|
if (!str.complete() || !str.hasPrefix(token)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const char* p = str.data() + strlen(token);
|
||||||
|
slot = atoi(p);
|
||||||
|
if (slot < 0 || slot >= Const::RedisClusterSlots) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
p = strchr(p, ' ');
|
||||||
|
if (!p) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
p++;
|
||||||
|
const char* e = strchr(p, '\r');
|
||||||
|
if (!e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int len = e - p;
|
||||||
|
if (len >= Const::MaxAddrLen) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
addr.set(p, len);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
140
src/Response.h
Normal file
140
src/Response.h
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_RESPONSE_H_
|
||||||
|
#define _PREDIXY_RESPONSE_H_
|
||||||
|
|
||||||
|
#include "Predixy.h"
|
||||||
|
#include "ResponseParser.h"
|
||||||
|
|
||||||
|
class Response :
|
||||||
|
public TID<Response>,
|
||||||
|
public ListNode<Response, SharePtr<Response>>,
|
||||||
|
public RefCntObj<Response>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
typedef Response Value;
|
||||||
|
typedef ListNode<Response, SharePtr<Response>> ListNodeType;
|
||||||
|
enum GenericCode
|
||||||
|
{
|
||||||
|
Pong,
|
||||||
|
Ok,
|
||||||
|
Cmd,
|
||||||
|
UnknownCmd,
|
||||||
|
ArgWrong,
|
||||||
|
InvalidDb,
|
||||||
|
NoPasswordSet,
|
||||||
|
InvalidPassword,
|
||||||
|
Unauth,
|
||||||
|
PermissionDeny,
|
||||||
|
NoServer,
|
||||||
|
NoServerConnection,
|
||||||
|
ServerConnectionClose,
|
||||||
|
DeliverRequestFail,
|
||||||
|
ForbidTransaction,
|
||||||
|
ConfigSubCmdUnknown,
|
||||||
|
InvalidScanCursor,
|
||||||
|
ScanEnd
|
||||||
|
};
|
||||||
|
static void init();
|
||||||
|
static Response* create(GenericCode code, Request* req = nullptr);
|
||||||
|
public:
|
||||||
|
Response();
|
||||||
|
Response(GenericCode code);
|
||||||
|
~Response();
|
||||||
|
void set(const ResponseParser& p);
|
||||||
|
void set(int64_t num);
|
||||||
|
void setStr(const char* str, int len = -1);
|
||||||
|
void setErr(const char* str, int len = -1);
|
||||||
|
void adjustForLeader(Request* req);
|
||||||
|
bool send(Socket* s);
|
||||||
|
int fill(IOVec* vecs, int len, Request* req) const;
|
||||||
|
void setType(Reply::Type t)
|
||||||
|
{
|
||||||
|
mType = t;
|
||||||
|
}
|
||||||
|
Reply::Type type() const
|
||||||
|
{
|
||||||
|
return mType;
|
||||||
|
}
|
||||||
|
const char* typeStr() const
|
||||||
|
{
|
||||||
|
return Reply::TypeStr[mType];
|
||||||
|
}
|
||||||
|
int64_t integer() const
|
||||||
|
{
|
||||||
|
return mInteger;
|
||||||
|
}
|
||||||
|
bool isOk() const
|
||||||
|
{
|
||||||
|
return mType == Reply::Status && mRes.hasPrefix("+OK");
|
||||||
|
}
|
||||||
|
bool isPong() const
|
||||||
|
{
|
||||||
|
return mType == Reply::Status && mRes.hasPrefix("+PONG");
|
||||||
|
}
|
||||||
|
bool isError() const
|
||||||
|
{
|
||||||
|
return mType == Reply::Error;
|
||||||
|
}
|
||||||
|
bool isInteger() const
|
||||||
|
{
|
||||||
|
return mType == Reply::Integer;
|
||||||
|
}
|
||||||
|
bool isString() const
|
||||||
|
{
|
||||||
|
return mType == Reply::String;
|
||||||
|
}
|
||||||
|
bool isArray() const
|
||||||
|
{
|
||||||
|
return mType == Reply::Array;
|
||||||
|
}
|
||||||
|
bool isMoved() const
|
||||||
|
{
|
||||||
|
return mType == Reply::Error && mRes.hasPrefix("-MOVED ");
|
||||||
|
}
|
||||||
|
bool getMoved(int& slot, SString<Const::MaxAddrLen>& addr) const
|
||||||
|
{
|
||||||
|
return getAddr(slot, addr, "-MOVED ");
|
||||||
|
}
|
||||||
|
bool isAsk() const
|
||||||
|
{
|
||||||
|
return mType == Reply::Error && mRes.hasPrefix("-ASK ");
|
||||||
|
}
|
||||||
|
bool getAsk(SString<Const::MaxAddrLen>& addr) const
|
||||||
|
{
|
||||||
|
int slot;
|
||||||
|
return getAddr(slot, addr, "-ASK ");
|
||||||
|
}
|
||||||
|
Segment& head()
|
||||||
|
{
|
||||||
|
return mHead;
|
||||||
|
}
|
||||||
|
const Segment& head() const
|
||||||
|
{
|
||||||
|
return mHead;
|
||||||
|
}
|
||||||
|
const Segment& body() const
|
||||||
|
{
|
||||||
|
return mRes;
|
||||||
|
}
|
||||||
|
Segment& body()
|
||||||
|
{
|
||||||
|
return mRes;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
bool getAddr(int& slot, SString<Const::MaxAddrLen>& addr, const char* token) const;
|
||||||
|
private:
|
||||||
|
Reply::Type mType;
|
||||||
|
int64_t mInteger;
|
||||||
|
Segment mHead; //for mget
|
||||||
|
Segment mRes;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef List<Response> ResponseList;
|
||||||
|
typedef Alloc<Response, Const::ResponseAllocCacheSize> ResponseAlloc;
|
||||||
|
|
||||||
|
#endif
|
||||||
260
src/ResponseParser.cpp
Normal file
260
src/ResponseParser.cpp
Normal file
@ -0,0 +1,260 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "String.h"
|
||||||
|
#include "ResponseParser.h"
|
||||||
|
|
||||||
|
ResponseParser::ResponseParser()
|
||||||
|
{
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
ResponseParser::~ResponseParser()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResponseParser::reset()
|
||||||
|
{
|
||||||
|
mType = Reply::None;
|
||||||
|
mRes.clear();
|
||||||
|
mState = Idle;
|
||||||
|
mInteger = 0;
|
||||||
|
mStringLen = 0;
|
||||||
|
mStringCnt = 0;
|
||||||
|
mDepth = 0;
|
||||||
|
mSign = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResponseParser::Status ResponseParser::parse(Buffer* buf, int& pos)
|
||||||
|
{
|
||||||
|
FuncCallTimer();
|
||||||
|
char* cursor = buf->data() + pos;
|
||||||
|
char* end = buf->tail();
|
||||||
|
int error = 0;
|
||||||
|
while (cursor < end && !error) {
|
||||||
|
char ch = *cursor;
|
||||||
|
switch (mState) {
|
||||||
|
case Idle:
|
||||||
|
mRes.begin().buf = buf;
|
||||||
|
mRes.begin().pos = cursor - buf->data();
|
||||||
|
mRes.rewind();
|
||||||
|
switch (ch) {
|
||||||
|
case '+':
|
||||||
|
mType = Reply::Status;
|
||||||
|
mState = StatusHead;
|
||||||
|
break;
|
||||||
|
case '-':
|
||||||
|
mType = Reply::Error;
|
||||||
|
mState = ErrorHead;
|
||||||
|
break;
|
||||||
|
case ':':
|
||||||
|
mType = Reply::Integer;
|
||||||
|
mState = IntegerHead;
|
||||||
|
break;
|
||||||
|
case '$':
|
||||||
|
mType = Reply::String;
|
||||||
|
mState = StringHead;
|
||||||
|
break;
|
||||||
|
case '*':
|
||||||
|
mType = Reply::Array;
|
||||||
|
mState = ArrayHead;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
error = __LINE__;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case StatusHead:
|
||||||
|
case ErrorHead:
|
||||||
|
if (ch == '\r') {
|
||||||
|
mState = HeadLF;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case IntegerHead:
|
||||||
|
case StringHead:
|
||||||
|
case ArrayHead:
|
||||||
|
if (ch >= '0' && ch <= '9') {
|
||||||
|
mInteger = mInteger * 10 + ch - '0';
|
||||||
|
} else if (ch == '\r') {
|
||||||
|
if (mSign) {
|
||||||
|
mInteger = -mInteger;
|
||||||
|
}
|
||||||
|
mState = HeadLF;
|
||||||
|
} else if (ch == '-' && !mSign) {
|
||||||
|
mSign = true;
|
||||||
|
} else {
|
||||||
|
error = __LINE__;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case HeadLF:
|
||||||
|
if (ch != '\n') {
|
||||||
|
error = __LINE__;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
switch (mType) {
|
||||||
|
case Reply::Status:
|
||||||
|
case Reply::Error:
|
||||||
|
case Reply::Integer:
|
||||||
|
goto Done;
|
||||||
|
case Reply::String:
|
||||||
|
if (mInteger < 0) {
|
||||||
|
goto Done;
|
||||||
|
}
|
||||||
|
mStringLen = mInteger;
|
||||||
|
mStringCnt = 0;
|
||||||
|
mState = StringBody;
|
||||||
|
break;
|
||||||
|
case Reply::Array:
|
||||||
|
if (mInteger <= 0) {
|
||||||
|
goto Done;
|
||||||
|
}
|
||||||
|
mDepth = 0;
|
||||||
|
mArrayNum[mDepth] = mInteger;
|
||||||
|
mElementCnt[mDepth++] = 0;
|
||||||
|
mState = ArrayBody;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
error = __LINE__;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case StringBody:
|
||||||
|
if (mStringCnt == mStringLen) {
|
||||||
|
ch == '\r' ? mState = StringBodyLF : error = __LINE__;
|
||||||
|
} else {
|
||||||
|
if (mStringCnt + (end - cursor) >= mStringLen) {
|
||||||
|
cursor += mStringLen - mStringCnt - 1;
|
||||||
|
mStringCnt = mStringLen;
|
||||||
|
} else {
|
||||||
|
mStringCnt += end - cursor;
|
||||||
|
cursor = end - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case StringBodyLF:
|
||||||
|
if (ch == '\n') {
|
||||||
|
goto Done;
|
||||||
|
}
|
||||||
|
error = __LINE__;
|
||||||
|
break;
|
||||||
|
case ArrayBody:
|
||||||
|
switch (ch) {
|
||||||
|
case '+':
|
||||||
|
case '-':
|
||||||
|
case ':':
|
||||||
|
mState = LineBody;
|
||||||
|
break;
|
||||||
|
case '$':
|
||||||
|
mSign = false;
|
||||||
|
mInteger = 0;
|
||||||
|
mState = SubStringLen;
|
||||||
|
break;
|
||||||
|
case '*':
|
||||||
|
mInteger = 0;
|
||||||
|
mState = SubArrayLen;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
error = __LINE__;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case LineBody:
|
||||||
|
if (ch == '\r') {
|
||||||
|
mState = ElementLF;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SubStringLen:
|
||||||
|
if (ch >= '0' && ch <= '9') {
|
||||||
|
mInteger = mInteger * 10 + ch - '0';
|
||||||
|
} else if (ch == '\r') {
|
||||||
|
mState = mSign ? ElementLF : SubStringLenLF;
|
||||||
|
} else if (ch == '-' && !mSign) {
|
||||||
|
mSign = true;
|
||||||
|
} else {
|
||||||
|
error = __LINE__;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SubArrayLen:
|
||||||
|
if (ch >= '0' && ch <= '9') {
|
||||||
|
mInteger = mInteger * 10 + ch - '0';
|
||||||
|
} else if (ch == '\r') {
|
||||||
|
mState = mInteger > 0 ? SubArrayLenLF : ElementLF;
|
||||||
|
} else {
|
||||||
|
error = __LINE__;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SubStringLenLF:
|
||||||
|
mStringLen = mInteger;
|
||||||
|
mStringCnt = 0;
|
||||||
|
ch == '\n' ? mState = SubStringBody : error = __LINE__;
|
||||||
|
break;
|
||||||
|
case SubArrayLenLF:
|
||||||
|
if (ch == '\n') {
|
||||||
|
if (mDepth < MaxArrayDepth) {
|
||||||
|
mArrayNum[mDepth] = mInteger;
|
||||||
|
mElementCnt[mDepth++] = 0;
|
||||||
|
mState = ArrayBody;
|
||||||
|
} else {
|
||||||
|
logError("response parse error:array too depth");
|
||||||
|
error = __LINE__;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error = __LINE__;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SubStringBody:
|
||||||
|
if (mStringCnt + (end - cursor) > mStringLen) {
|
||||||
|
cursor += mStringLen - mStringCnt;
|
||||||
|
*cursor == '\r' ? mState = ElementLF : error = __LINE__;
|
||||||
|
} else {
|
||||||
|
mStringCnt += end - cursor;
|
||||||
|
cursor = end - 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ElementLF:
|
||||||
|
if (ch != '\n') {
|
||||||
|
error = __LINE__;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
mState = ArrayBody;
|
||||||
|
while (true) {
|
||||||
|
if (++mElementCnt[mDepth - 1] == mArrayNum[mDepth - 1]) {
|
||||||
|
if (--mDepth == 0) {
|
||||||
|
goto Done;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
error = __LINE__;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++cursor;
|
||||||
|
}
|
||||||
|
if (error) {
|
||||||
|
SString<64> bufHex;
|
||||||
|
bufHex.printHex(buf->data() + pos, buf->length() - pos);
|
||||||
|
SString<16> errHex;
|
||||||
|
errHex.printHex(cursor - 1, end - cursor + 1);
|
||||||
|
logError("response parse error %d state %d buf:%s errpos %d err:%s",
|
||||||
|
error, mState, bufHex.data(),
|
||||||
|
cursor - 1 - buf->data() - pos, errHex.data());
|
||||||
|
return ParseError;
|
||||||
|
}
|
||||||
|
pos = cursor + 1 - buf->data();
|
||||||
|
mRes.end().buf = buf;
|
||||||
|
mRes.end().pos = pos;
|
||||||
|
return (mState > HeadLF) ? Partial : Normal;
|
||||||
|
Done:
|
||||||
|
mState = Finished;
|
||||||
|
pos = cursor + 1 - buf->data();
|
||||||
|
mRes.end().buf = buf;
|
||||||
|
mRes.end().pos = pos;
|
||||||
|
return Complete;
|
||||||
|
}
|
||||||
|
|
||||||
86
src/ResponseParser.h
Normal file
86
src/ResponseParser.h
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_RESPONSE_PARSER_H_
|
||||||
|
#define _PREDIXY_RESPONSE_PARSER_H_
|
||||||
|
|
||||||
|
#include "Predixy.h"
|
||||||
|
|
||||||
|
class ResponseParser
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static const int MaxArrayDepth = 8;
|
||||||
|
enum State
|
||||||
|
{
|
||||||
|
Idle,
|
||||||
|
StatusHead,
|
||||||
|
ErrorHead,
|
||||||
|
IntegerHead,
|
||||||
|
StringHead,
|
||||||
|
ArrayHead,
|
||||||
|
HeadLF,
|
||||||
|
ArrayBody,
|
||||||
|
StringLen,
|
||||||
|
StringBody,
|
||||||
|
StringBodyLF,
|
||||||
|
LineBody,
|
||||||
|
SubStringLen,
|
||||||
|
SubStringLenLF,
|
||||||
|
SubStringBody,
|
||||||
|
SubStringBodyLF,
|
||||||
|
SubArrayLen,
|
||||||
|
SubArrayLenLF,
|
||||||
|
ElementLF,
|
||||||
|
Finished,
|
||||||
|
|
||||||
|
Error
|
||||||
|
};
|
||||||
|
enum Status
|
||||||
|
{
|
||||||
|
Normal,
|
||||||
|
Partial,
|
||||||
|
Complete,
|
||||||
|
ParseError
|
||||||
|
};
|
||||||
|
public:
|
||||||
|
ResponseParser();
|
||||||
|
~ResponseParser();
|
||||||
|
void reset();
|
||||||
|
Status parse(Buffer* buf, int& pos);
|
||||||
|
bool isIdle() const
|
||||||
|
{
|
||||||
|
return mState == Idle;
|
||||||
|
}
|
||||||
|
Reply::Type type() const
|
||||||
|
{
|
||||||
|
return mType;
|
||||||
|
}
|
||||||
|
Segment& response()
|
||||||
|
{
|
||||||
|
return mRes;
|
||||||
|
}
|
||||||
|
const Segment& response() const
|
||||||
|
{
|
||||||
|
return mRes;
|
||||||
|
}
|
||||||
|
int64_t integer() const
|
||||||
|
{
|
||||||
|
return mInteger;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
Reply::Type mType;
|
||||||
|
Segment mRes;
|
||||||
|
State mState;
|
||||||
|
int64_t mInteger;
|
||||||
|
int mStringLen;
|
||||||
|
int mStringCnt;
|
||||||
|
int mArrayNum[MaxArrayDepth];
|
||||||
|
int mElementCnt[MaxArrayDepth];
|
||||||
|
int mDepth;
|
||||||
|
bool mSign;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
446
src/SentinelServerPool.cpp
Normal file
446
src/SentinelServerPool.cpp
Normal file
@ -0,0 +1,446 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include "Logger.h"
|
||||||
|
#include "ServerGroup.h"
|
||||||
|
#include "Handler.h"
|
||||||
|
#include "SentinelServerPool.h"
|
||||||
|
|
||||||
|
SentinelServerPool::SentinelServerPool(Proxy* p):
|
||||||
|
ServerPoolTmpl(p, Sentinel),
|
||||||
|
mDist(Distribution::Modula)
|
||||||
|
{
|
||||||
|
mSentinels.reserve(MaxSentinelNum);
|
||||||
|
mServPool.reserve(Const::MaxServNum);
|
||||||
|
mHashTag[0] = mHashTag[1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
SentinelServerPool::~SentinelServerPool()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void SentinelServerPool::init(const SentinelServerPoolConf& conf)
|
||||||
|
{
|
||||||
|
ServerPool::init(conf);
|
||||||
|
mDist = conf.dist;
|
||||||
|
mHash = conf.hash;
|
||||||
|
mHashTag[0] = conf.hashTag[0];
|
||||||
|
mHashTag[1] = conf.hashTag[1];
|
||||||
|
mSentinels.resize(conf.sentinels.size());
|
||||||
|
int i = 0;
|
||||||
|
for (auto& sc : conf.sentinels) {
|
||||||
|
Server* s = new Server(this, sc.addr, true);
|
||||||
|
s->setRole(Server::Sentinel);
|
||||||
|
s->setPassword(sc.password.empty() ? conf.password : sc.password);
|
||||||
|
mSentinels[i++] = s;
|
||||||
|
mServs[s->addr()] = s;
|
||||||
|
}
|
||||||
|
mGroups.resize(conf.groups.size());
|
||||||
|
i = 0;
|
||||||
|
for (auto& gc : conf.groups) {
|
||||||
|
ServerGroup* g = new ServerGroup(this, gc.name);
|
||||||
|
mGroups[i++] = g;
|
||||||
|
for (auto& sc : gc.servers) {
|
||||||
|
Server* s = new Server(this, sc.addr, true);
|
||||||
|
s->setPassword(sc.password.empty() ? conf.password : sc.password);
|
||||||
|
s->setOnline(false);
|
||||||
|
mServPool.push_back(s);
|
||||||
|
mServs[s->addr()] = s;
|
||||||
|
g->add(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Server* SentinelServerPool::getServer(Handler* h, Request* req) const
|
||||||
|
{
|
||||||
|
FuncCallTimer();
|
||||||
|
switch (req->type()) {
|
||||||
|
case Command::SentinelGetMaster:
|
||||||
|
case Command::SentinelSlaves:
|
||||||
|
case Command::SentinelSentinels:
|
||||||
|
if (mSentinels.empty()) {
|
||||||
|
return nullptr;
|
||||||
|
} else {
|
||||||
|
Server* s = randServer(h, mSentinels);
|
||||||
|
logDebug("sentinel server pool get server %s for sentinel command",
|
||||||
|
s->addr().data());
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Command::Randomkey:
|
||||||
|
return randServer(h, mServPool);
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (mGroups.size() == 1) {
|
||||||
|
return mGroups[0]->getServer(h, req);
|
||||||
|
} else if (mGroups.size() > 1) {
|
||||||
|
switch (mDist) {
|
||||||
|
case Distribution::Modula:
|
||||||
|
{
|
||||||
|
SegmentStr<Const::MaxKeyLen> key(req->key());
|
||||||
|
long idx = mHash.hash(key.data(), key.length(), mHashTag);
|
||||||
|
idx %= mGroups.size();
|
||||||
|
return mGroups[idx]->getServer(h, req);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Distribution::Random:
|
||||||
|
{
|
||||||
|
int idx = h->rand() % mGroups.size();
|
||||||
|
return mGroups[idx]->getServer(h, req);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SentinelServerPool::refreshRequest(Handler* h)
|
||||||
|
{
|
||||||
|
logDebug("h %d update sentinel server pool", h->id());
|
||||||
|
for (auto g : mGroups) {
|
||||||
|
RequestPtr req = RequestAlloc::create();
|
||||||
|
req->setSentinels(g->name());
|
||||||
|
req->setData(g);
|
||||||
|
h->handleRequest(req);
|
||||||
|
req = RequestAlloc::create();
|
||||||
|
req->setSentinelGetMaster(g->name());
|
||||||
|
req->setData(g);
|
||||||
|
h->handleRequest(req);
|
||||||
|
req = RequestAlloc::create();
|
||||||
|
req->setSentinelSlaves(g->name());
|
||||||
|
req->setData(g);
|
||||||
|
h->handleRequest(req);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SentinelServerPool::handleResponse(Handler* h, ConnectConnection* s, Request* req, Response* res)
|
||||||
|
{
|
||||||
|
switch (req->type()) {
|
||||||
|
case Command::SentinelSentinels:
|
||||||
|
handleSentinels(h, s, req, res);
|
||||||
|
break;
|
||||||
|
case Command::SentinelGetMaster:
|
||||||
|
handleGetMaster(h, s, req, res);
|
||||||
|
break;
|
||||||
|
case Command::SentinelSlaves:
|
||||||
|
handleSlaves(h, s, req, res);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AddrParser
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum Status {
|
||||||
|
Ok,
|
||||||
|
Error,
|
||||||
|
Done
|
||||||
|
};
|
||||||
|
public:
|
||||||
|
AddrParser(const Segment& res):
|
||||||
|
mRes(res),
|
||||||
|
mState(Idle),
|
||||||
|
mCnt(0),
|
||||||
|
mArgLen(0),
|
||||||
|
mIp(false),
|
||||||
|
mPort(false)
|
||||||
|
{
|
||||||
|
mRes.rewind();
|
||||||
|
}
|
||||||
|
int count() const {return mCnt;}
|
||||||
|
Status parse(SString<Const::MaxAddrLen>& addr);
|
||||||
|
private:
|
||||||
|
enum State {
|
||||||
|
Idle,
|
||||||
|
Count,
|
||||||
|
CountLF,
|
||||||
|
Arg,
|
||||||
|
ArgLen,
|
||||||
|
ArgLenLF,
|
||||||
|
SubArrayLen,
|
||||||
|
Body,
|
||||||
|
BodyLF,
|
||||||
|
Invalid,
|
||||||
|
Finished
|
||||||
|
};
|
||||||
|
private:
|
||||||
|
Segment mRes;
|
||||||
|
State mState;
|
||||||
|
int mCnt;
|
||||||
|
int mArgLen;
|
||||||
|
bool mIp;
|
||||||
|
bool mPort;
|
||||||
|
SString<4> mKey;
|
||||||
|
};
|
||||||
|
|
||||||
|
AddrParser::Status AddrParser::parse(SString<Const::MaxAddrLen>& addr)
|
||||||
|
{
|
||||||
|
const char* dat;
|
||||||
|
int len;
|
||||||
|
addr.clear();
|
||||||
|
while (mRes.get(dat, len) && mState != Invalid) {
|
||||||
|
for (int i = 0; i < len && mState != Invalid; ++i) {
|
||||||
|
char ch = dat[i];
|
||||||
|
switch (mState) {
|
||||||
|
case Idle:
|
||||||
|
mState = ch == '*' ? Count : Invalid;
|
||||||
|
break;
|
||||||
|
case Count:
|
||||||
|
if (ch >= '0' && ch <= '9') {
|
||||||
|
mCnt = mCnt * 10 + (ch - '0');
|
||||||
|
} else if (ch == '\r') {
|
||||||
|
if (mCnt == 0) {
|
||||||
|
mState = Finished;
|
||||||
|
return Done;
|
||||||
|
} else if (mCnt < 0) {
|
||||||
|
mState = Invalid;
|
||||||
|
return Error;
|
||||||
|
}
|
||||||
|
mState = CountLF;
|
||||||
|
} else {
|
||||||
|
mState = Invalid;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CountLF:
|
||||||
|
mState = ch == '\n' ? Arg : Invalid;
|
||||||
|
break;
|
||||||
|
case Arg:
|
||||||
|
if (ch == '$') {
|
||||||
|
mState = ArgLen;
|
||||||
|
mArgLen = 0;
|
||||||
|
} else if (ch == '*') {
|
||||||
|
mState = SubArrayLen;
|
||||||
|
} else {
|
||||||
|
mState = Invalid;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ArgLen:
|
||||||
|
if (ch >= '0' && ch <= '9') {
|
||||||
|
mArgLen = mArgLen * 10 + (ch - '0');
|
||||||
|
} else if (ch == '\r') {
|
||||||
|
mState = ArgLenLF;
|
||||||
|
} else {
|
||||||
|
mState = Invalid;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ArgLenLF:
|
||||||
|
mState = ch == '\n' ? Body : Invalid;
|
||||||
|
break;
|
||||||
|
case SubArrayLen:
|
||||||
|
if (ch == '\n') {
|
||||||
|
mState = Arg;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Body:
|
||||||
|
if (ch == '\r') {
|
||||||
|
mState = BodyLF;
|
||||||
|
if (mPort) {
|
||||||
|
mPort = false;
|
||||||
|
mRes.use(i + 1);
|
||||||
|
return Ok;
|
||||||
|
} else if (mIp) {
|
||||||
|
mIp = false;
|
||||||
|
addr.append(':');
|
||||||
|
} else if (mArgLen == 2 && strcmp(mKey.data(), "ip") == 0) {
|
||||||
|
mIp = true;
|
||||||
|
} else if (mArgLen == 4 && strcmp(mKey.data(), "port") == 0) {
|
||||||
|
mPort = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (mIp || mPort) {
|
||||||
|
addr.append(ch);
|
||||||
|
} else if (mArgLen == 2 || mArgLen == 4) {
|
||||||
|
mKey.append(ch);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case BodyLF:
|
||||||
|
mKey.clear();
|
||||||
|
mState = ch == '\n' ? Arg : Invalid;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mRes.use(len);
|
||||||
|
}
|
||||||
|
return mState != Invalid ? Done : Error;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SentinelServerPool::handleSentinels(Handler* h, ConnectConnection* s, Request* req, Response* res)
|
||||||
|
{
|
||||||
|
if (!res || !res->isArray()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AddrParser parser(res->body());
|
||||||
|
SString<Const::MaxAddrLen> addr;
|
||||||
|
while (true) {
|
||||||
|
auto st = parser.parse(addr);
|
||||||
|
if (st == AddrParser::Ok) {
|
||||||
|
logDebug("sentinel server pool parse sentinel %s", addr.data());
|
||||||
|
auto it = mServs.find(addr);
|
||||||
|
Server* serv = it == mServs.end() ? nullptr : it->second;
|
||||||
|
if (!serv) {
|
||||||
|
if (mSentinels.size() == mSentinels.capacity()) {
|
||||||
|
logWarn("too many sentinels %d, will ignore new sentinel %s",
|
||||||
|
(int)mSentinels.size(), addr.data());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
serv = new Server(this, addr, false);
|
||||||
|
serv->setRole(Server::Sentinel);
|
||||||
|
serv->setPassword(password());
|
||||||
|
mSentinels.push_back(serv);
|
||||||
|
mServs[serv->addr()] = serv;
|
||||||
|
logNotice("h %d create new sentinel %s",
|
||||||
|
h->id(), addr.data());
|
||||||
|
}
|
||||||
|
serv->setOnline(true);
|
||||||
|
} else if (st == AddrParser::Done) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
logError("sentinel server pool parse sentinel sentinels error");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SentinelServerPool::handleGetMaster(Handler* h, ConnectConnection* s, Request* req, Response* res)
|
||||||
|
{
|
||||||
|
if (!res || !res->isArray()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ServerGroup* g = (ServerGroup*)req->data();
|
||||||
|
if (!g) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SegmentStr<Const::MaxAddrLen + 32> str(res->body());
|
||||||
|
if (!str.complete()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (strncmp(str.data(), "*2\r\n$", 5) != 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SString<Const::MaxAddrLen> addr;
|
||||||
|
const char* p = str.data() + 5;
|
||||||
|
int len = atoi(p);
|
||||||
|
if (len <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
p = strchr(p, '\r') + 2;
|
||||||
|
if (!addr.append(p, len)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!addr.append(':')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
p += len + 3;
|
||||||
|
len = atoi(p);
|
||||||
|
if (len <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
p = strchr(p, '\r') + 2;
|
||||||
|
if (!addr.append(p, len)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logDebug("sentinel server pool group %s get master %s",
|
||||||
|
g->name().data(), addr.data());
|
||||||
|
auto it = mServs.find(addr);
|
||||||
|
Server* serv = it == mServs.end() ? nullptr : it->second;
|
||||||
|
if (serv) {
|
||||||
|
serv->setOnline(true);
|
||||||
|
serv->setRole(Server::Master);
|
||||||
|
auto old = serv->group();
|
||||||
|
if (old) {
|
||||||
|
if (old != g) {
|
||||||
|
old->remove(serv);
|
||||||
|
g->add(serv);
|
||||||
|
serv->setGroup(g);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
g->add(serv);
|
||||||
|
serv->setGroup(g);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (mServPool.size() == mServPool.capacity()) {
|
||||||
|
logWarn("too many servers %d, will ignore new master server %s",
|
||||||
|
(int)mServPool.size(), addr.data());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
serv = new Server(this, addr, false);
|
||||||
|
serv->setRole(Server::Master);
|
||||||
|
serv->setPassword(password());
|
||||||
|
mServPool.push_back(serv);
|
||||||
|
g->add(serv);
|
||||||
|
serv->setGroup(g);
|
||||||
|
mServs[serv->addr()] = serv;
|
||||||
|
logNotice("sentinel server pool group %s create master server %s %s",
|
||||||
|
g->name().data(), addr.data(), serv->dcName().data());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SentinelServerPool::handleSlaves(Handler* h, ConnectConnection* s, Request* req, Response* res)
|
||||||
|
{
|
||||||
|
if (!res || !res->isArray()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ServerGroup* g = (ServerGroup*)req->data();
|
||||||
|
if (!g) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AddrParser parser(res->body());
|
||||||
|
SString<Const::MaxAddrLen> addr;
|
||||||
|
while (true) {
|
||||||
|
auto st = parser.parse(addr);
|
||||||
|
if (st == AddrParser::Ok) {
|
||||||
|
logDebug("sentinel server pool group %s parse slave %s",
|
||||||
|
g->name().data(), addr.data());
|
||||||
|
auto it = mServs.find(addr);
|
||||||
|
Server* serv = it == mServs.end() ? nullptr : it->second;
|
||||||
|
if (serv) {
|
||||||
|
serv->setOnline(true);
|
||||||
|
serv->setRole(Server::Slave);
|
||||||
|
auto old = serv->group();
|
||||||
|
if (old) {
|
||||||
|
if (old != g) {
|
||||||
|
old->remove(serv);
|
||||||
|
g->add(serv);
|
||||||
|
serv->setGroup(g);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
g->add(serv);
|
||||||
|
serv->setGroup(g);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (mServPool.size() == mServPool.capacity()) {
|
||||||
|
logWarn("too many servers %d, will ignore new slave server %s",
|
||||||
|
(int)mServPool.size(), addr.data());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
serv = new Server(this, addr, false);
|
||||||
|
serv->setRole(Server::Slave);
|
||||||
|
serv->setPassword(password());
|
||||||
|
mServPool.push_back(serv);
|
||||||
|
g->add(serv);
|
||||||
|
serv->setGroup(g);
|
||||||
|
mServs[serv->addr()] = serv;
|
||||||
|
logNotice("sentinel server pool group %s create slave server %s %s",
|
||||||
|
g->name().data(), addr.data(), serv->dcName().data());
|
||||||
|
}
|
||||||
|
} else if (st == AddrParser::Done) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
logError("sentinel server pool group %s parse sentinel sentinels error",
|
||||||
|
g->name().data());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
43
src/SentinelServerPool.h
Normal file
43
src/SentinelServerPool.h
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_SENTINEL_SERVER_POOL_H_
|
||||||
|
#define _PREDIXY_SENTINEL_SERVER_POOL_H_
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include "Predixy.h"
|
||||||
|
#include "ServerPool.h"
|
||||||
|
|
||||||
|
class SentinelServerPool : public ServerPoolTmpl<SentinelServerPool>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static const int MaxSentinelNum = 64;
|
||||||
|
public:
|
||||||
|
SentinelServerPool(Proxy* p);
|
||||||
|
~SentinelServerPool();
|
||||||
|
void init(const SentinelServerPoolConf& conf);
|
||||||
|
Server* getServer(Handler* h, Request* req) const;
|
||||||
|
Server* iter(int& cursor) const
|
||||||
|
{
|
||||||
|
return ServerPool::iter(mServPool, cursor);
|
||||||
|
}
|
||||||
|
void refreshRequest(Handler* h);
|
||||||
|
void handleResponse(Handler* h, ConnectConnection* s, Request* req, Response* res);
|
||||||
|
private:
|
||||||
|
void handleSentinels(Handler* h, ConnectConnection* s, Request* req, Response* res);
|
||||||
|
void handleGetMaster(Handler* h, ConnectConnection* s, Request* req, Response* res);
|
||||||
|
void handleSlaves(Handler* h, ConnectConnection* s, Request* req, Response* res);
|
||||||
|
friend class ServerPoolTmpl<SentinelServerPool>;
|
||||||
|
private:
|
||||||
|
std::vector<Server*> mSentinels;
|
||||||
|
std::vector<Server*> mServPool;
|
||||||
|
std::vector<ServerGroup*> mGroups;
|
||||||
|
Distribution mDist;
|
||||||
|
Hash mHash;
|
||||||
|
char mHashTag[2];
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
57
src/Server.cpp
Normal file
57
src/Server.cpp
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Proxy.h"
|
||||||
|
#include "Server.h"
|
||||||
|
#include "ServerPool.h"
|
||||||
|
|
||||||
|
const char* Server::RoleStr[] = {
|
||||||
|
"unknown",
|
||||||
|
"master",
|
||||||
|
"slave",
|
||||||
|
"sentinel"
|
||||||
|
};
|
||||||
|
|
||||||
|
Server::Server(ServerPool* pool, const String& addr, bool isStatic):
|
||||||
|
mPool(pool),
|
||||||
|
mGroup(nullptr),
|
||||||
|
mRole(Unknown),
|
||||||
|
mDC(nullptr),
|
||||||
|
mAddr(addr),
|
||||||
|
mNextActivateTime(0),
|
||||||
|
mFailureCnt(0),
|
||||||
|
mStatic(isStatic),
|
||||||
|
mFail(false),
|
||||||
|
mOnline(true),
|
||||||
|
mUpdating(false)
|
||||||
|
{
|
||||||
|
if (auto dataCenter = pool->proxy()->dataCenter()) {
|
||||||
|
mDC = dataCenter->getDC(addr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Server::~Server()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Server::activate()
|
||||||
|
{
|
||||||
|
long v = mNextActivateTime;
|
||||||
|
long now = Util::nowUSec();
|
||||||
|
if (now < v) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return AtomicCAS(mNextActivateTime, v, now + mPool->serverRetryTimeout());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Server::incrFail()
|
||||||
|
{
|
||||||
|
long cnt = ++mFailureCnt;
|
||||||
|
if (cnt % mPool->serverFailureLimit() == 0) {
|
||||||
|
setFail(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
144
src/Server.h
Normal file
144
src/Server.h
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_SERVER_H_
|
||||||
|
#define _PREDIXY_SERVER_H_
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include "Predixy.h"
|
||||||
|
#include "DC.h"
|
||||||
|
#include "ConnectConnection.h"
|
||||||
|
|
||||||
|
class Server : public ID<Server>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum Role
|
||||||
|
{
|
||||||
|
Unknown,
|
||||||
|
Master,
|
||||||
|
Slave,
|
||||||
|
Sentinel
|
||||||
|
};
|
||||||
|
static const char* RoleStr[];
|
||||||
|
public:
|
||||||
|
Server(ServerPool* pool, const String& addr, bool isStatic);
|
||||||
|
~Server();
|
||||||
|
bool activate();
|
||||||
|
void incrFail();
|
||||||
|
ServerPool* pool() const
|
||||||
|
{
|
||||||
|
return mPool;
|
||||||
|
}
|
||||||
|
ServerGroup* group() const
|
||||||
|
{
|
||||||
|
return mGroup;
|
||||||
|
}
|
||||||
|
void setGroup(ServerGroup* g)
|
||||||
|
{
|
||||||
|
mGroup = g;
|
||||||
|
}
|
||||||
|
void setRole(Role role)
|
||||||
|
{
|
||||||
|
mRole = role;
|
||||||
|
}
|
||||||
|
Role role() const
|
||||||
|
{
|
||||||
|
return mRole;
|
||||||
|
}
|
||||||
|
const char* roleStr() const
|
||||||
|
{
|
||||||
|
return RoleStr[mRole];
|
||||||
|
}
|
||||||
|
bool isMaster() const
|
||||||
|
{
|
||||||
|
return mRole == Master;
|
||||||
|
}
|
||||||
|
bool isSlave() const
|
||||||
|
{
|
||||||
|
return mRole == Slave;
|
||||||
|
}
|
||||||
|
bool isStatic() const
|
||||||
|
{
|
||||||
|
return mStatic;
|
||||||
|
}
|
||||||
|
const String& name() const
|
||||||
|
{
|
||||||
|
return mName;
|
||||||
|
}
|
||||||
|
void setName(const String& str)
|
||||||
|
{
|
||||||
|
mName = str;
|
||||||
|
}
|
||||||
|
const String& masterName() const
|
||||||
|
{
|
||||||
|
return mMasterName;
|
||||||
|
}
|
||||||
|
void setMasterName(const String& str)
|
||||||
|
{
|
||||||
|
mMasterName = str;
|
||||||
|
}
|
||||||
|
const String& addr() const
|
||||||
|
{
|
||||||
|
return mAddr;
|
||||||
|
}
|
||||||
|
const String& password() const
|
||||||
|
{
|
||||||
|
return mPassword;
|
||||||
|
}
|
||||||
|
void setPassword(const String& pw)
|
||||||
|
{
|
||||||
|
mPassword = pw;
|
||||||
|
}
|
||||||
|
DC* dc() const
|
||||||
|
{
|
||||||
|
return mDC;
|
||||||
|
}
|
||||||
|
String dcName() const
|
||||||
|
{
|
||||||
|
return mDC ? mDC->name() : String("", 0);
|
||||||
|
}
|
||||||
|
bool fail() const
|
||||||
|
{
|
||||||
|
return mFail;
|
||||||
|
}
|
||||||
|
void setFail(bool v)
|
||||||
|
{
|
||||||
|
mFail = v;
|
||||||
|
}
|
||||||
|
bool online() const
|
||||||
|
{
|
||||||
|
return mOnline;
|
||||||
|
}
|
||||||
|
void setOnline(bool v)
|
||||||
|
{
|
||||||
|
mOnline = v;
|
||||||
|
}
|
||||||
|
bool updating() const
|
||||||
|
{
|
||||||
|
return mUpdating;
|
||||||
|
}
|
||||||
|
void setUpdating(bool v)
|
||||||
|
{
|
||||||
|
mUpdating = v;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
ServerPool* mPool;
|
||||||
|
ServerGroup* mGroup;
|
||||||
|
Role mRole;
|
||||||
|
DC* mDC;
|
||||||
|
SString<Const::MaxAddrLen> mAddr;
|
||||||
|
SString<Const::MaxServNameLen> mName;
|
||||||
|
SString<Const::MaxServNameLen> mMasterName;
|
||||||
|
String mPassword;
|
||||||
|
AtomicLong mNextActivateTime; //us
|
||||||
|
AtomicLong mFailureCnt;
|
||||||
|
bool mStatic;
|
||||||
|
bool mFail;
|
||||||
|
bool mOnline;
|
||||||
|
bool mUpdating;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
303
src/ServerGroup.cpp
Normal file
303
src/ServerGroup.cpp
Normal file
@ -0,0 +1,303 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include "DC.h"
|
||||||
|
#include "Proxy.h"
|
||||||
|
#include "ServerPool.h"
|
||||||
|
#include "ServerGroup.h"
|
||||||
|
|
||||||
|
ServerGroup::ServerGroup(ServerPool* pool, const String& name):
|
||||||
|
mPool(pool),
|
||||||
|
mName(name)
|
||||||
|
{
|
||||||
|
mServs.reserve(Const::MaxServInGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerGroup::~ServerGroup()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Server* ServerGroup::getMaster() const
|
||||||
|
{
|
||||||
|
FuncCallTimer();
|
||||||
|
int cnt = mServs.size();
|
||||||
|
for (int i = 0; i < cnt; ++i) {
|
||||||
|
Server* s = mServs[i];
|
||||||
|
if (!s->online()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (s->role() == Server::Master) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ServCond
|
||||||
|
{
|
||||||
|
Server* serv = nullptr;
|
||||||
|
int priority = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
Server* ServerGroup::getServer(Handler* h, Request* req) const
|
||||||
|
{
|
||||||
|
FuncCallTimer();
|
||||||
|
Server* serv = nullptr;
|
||||||
|
if (req->requireWrite()) {
|
||||||
|
int cnt = mServs.size();
|
||||||
|
for (int i = 0; i < cnt; ++i) {
|
||||||
|
Server* s = mServs[i];
|
||||||
|
if (!s->online()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (s->role() == Server::Master) {
|
||||||
|
serv = s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (auto dataCenter = mPool->proxy()->dataCenter()) {
|
||||||
|
serv = getReadServer(h, dataCenter->localDC());
|
||||||
|
} else {
|
||||||
|
serv = getReadServer(h);
|
||||||
|
}
|
||||||
|
logDebug("server group %s for req %ld get server %s",
|
||||||
|
mName.data(), req->id(), (serv ? serv->addr().data() : "None"));
|
||||||
|
return serv;
|
||||||
|
}
|
||||||
|
|
||||||
|
Server* ServerGroup::getReadServer(Handler* h) const
|
||||||
|
{
|
||||||
|
FuncCallTimer();
|
||||||
|
Server* servs[Const::MaxServInGroup];
|
||||||
|
Server* deadServs[Const::MaxServInGroup];
|
||||||
|
int n = 0;
|
||||||
|
int dn = 0;
|
||||||
|
int prior = 0;
|
||||||
|
int dprior = 0;
|
||||||
|
int pendRequests = INT_MAX;
|
||||||
|
int cnt = mServs.size();
|
||||||
|
for (int i = 0; i < cnt; ++i) {
|
||||||
|
Server* s = mServs[i];
|
||||||
|
if (!s->online()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int rp = 0;
|
||||||
|
if (s->role() == Server::Master) {
|
||||||
|
rp = mPool->masterReadPriority();
|
||||||
|
} else if (s->role() == Server::Slave) {
|
||||||
|
rp = s->isStatic() ? mPool->staticSlaveReadPriority() : mPool->dynamicSlaveReadPriority();
|
||||||
|
}
|
||||||
|
if (rp <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (s->fail()) {
|
||||||
|
if (rp > dprior) {
|
||||||
|
dprior = rp;
|
||||||
|
deadServs[0] = s;
|
||||||
|
dn = 1;
|
||||||
|
} else if (rp == dprior) {
|
||||||
|
deadServs[dn++] = s;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (rp > prior) {
|
||||||
|
prior = rp;
|
||||||
|
pendRequests = h->getPendRequests(s);
|
||||||
|
servs[0] = s;
|
||||||
|
n = 1;
|
||||||
|
} else if (rp == prior) {
|
||||||
|
int preqs = h->getPendRequests(s);
|
||||||
|
if (preqs < pendRequests) {
|
||||||
|
pendRequests = preqs;
|
||||||
|
servs[0] = s;
|
||||||
|
n = 1;
|
||||||
|
} else if (preqs == pendRequests) {
|
||||||
|
servs[n++] = s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Server** ss = nullptr;
|
||||||
|
if (n > 0) {
|
||||||
|
ss = servs;
|
||||||
|
} else if (dn > 0) {
|
||||||
|
ss = deadServs;
|
||||||
|
n = dn;
|
||||||
|
} else {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
n = h->rand() % n;
|
||||||
|
return ss[n];
|
||||||
|
}
|
||||||
|
|
||||||
|
Server* ServerGroup::getReadServer(Handler* h, DC* localDC) const
|
||||||
|
{
|
||||||
|
FuncCallTimer();
|
||||||
|
Server* servs[Const::MaxServInGroup];
|
||||||
|
int sprior[Const::MaxServInGroup];
|
||||||
|
int pendRequests[Const::MaxServInGroup];
|
||||||
|
int num = 0;
|
||||||
|
DC* dcs[Const::MaxServInGroup];
|
||||||
|
DC* deadDCs[Const::MaxServInGroup];
|
||||||
|
int n = 0;
|
||||||
|
int dn = 0;
|
||||||
|
int prior = 0;
|
||||||
|
int dprior = 0;
|
||||||
|
int cnt = mServs.size();
|
||||||
|
for (int i = 0; i < cnt; ++i) {
|
||||||
|
Server* s = mServs[i];
|
||||||
|
if (!s->online()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int rp = 0;
|
||||||
|
if (s->role() == Server::Master) {
|
||||||
|
rp = mPool->masterReadPriority();
|
||||||
|
} else if (s->role() == Server::Slave) {
|
||||||
|
rp = s->isStatic() ? mPool->staticSlaveReadPriority() : mPool->dynamicSlaveReadPriority();
|
||||||
|
}
|
||||||
|
if (rp <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
DC* dc = s->dc();
|
||||||
|
int dcrp = localDC->getReadPriority(dc);
|
||||||
|
if (dcrp <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
servs[num] = s;
|
||||||
|
sprior[num] = rp;
|
||||||
|
pendRequests[num++] = h->getPendRequests(s);
|
||||||
|
if (s->fail()) {
|
||||||
|
if (dcrp > dprior) {
|
||||||
|
dprior = dcrp;
|
||||||
|
deadDCs[0] = dc;
|
||||||
|
dn = 1;
|
||||||
|
} else if (dcrp == dprior) {
|
||||||
|
deadDCs[dn++] = dc;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (dcrp > prior) {
|
||||||
|
prior = dcrp;
|
||||||
|
dcs[0] = dc;
|
||||||
|
n = 1;
|
||||||
|
} else if (dcrp == prior) {
|
||||||
|
dcs[n++] = dc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DC** sdc = nullptr;
|
||||||
|
if (n > 0) {
|
||||||
|
sdc = dcs;
|
||||||
|
} else if (dn > 0) {
|
||||||
|
sdc = deadDCs;
|
||||||
|
n = dn;
|
||||||
|
} else {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
bool found = false;
|
||||||
|
DC* dc = nullptr;
|
||||||
|
if (n > 1) {
|
||||||
|
int m = h->idUnique().unique(sdc, n);
|
||||||
|
int weights[Const::MaxServInGroup];
|
||||||
|
int w = 0;
|
||||||
|
n = 0;
|
||||||
|
for (int i = 0; i < m; ++i) {
|
||||||
|
int dw = localDC->getReadWeight(sdc[i]);
|
||||||
|
if (dw > 0) {
|
||||||
|
w += dw;
|
||||||
|
sdc[n] = sdc[i];
|
||||||
|
weights[n++] = w;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (w > 0) {
|
||||||
|
w = h->rand() % w;
|
||||||
|
int i = std::lower_bound(weights, weights + n, w) - weights;
|
||||||
|
dc = sdc[i];
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
} else if (localDC->getReadWeight(sdc[0]) > 0) {
|
||||||
|
dc = sdc[0];
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
if (!found) {//dc maybe nullptr even we found
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
Server* deadServs[Const::MaxServInGroup];
|
||||||
|
n = 0;
|
||||||
|
dn = 0;
|
||||||
|
prior = 0;
|
||||||
|
dprior = 0;
|
||||||
|
int preqs = INT_MAX;
|
||||||
|
for (int i = 0; i < num; ++i) {
|
||||||
|
Server* s = servs[i];
|
||||||
|
if (servs[i]->dc() != dc) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (s->fail()) {
|
||||||
|
if (sprior[i] > dprior) {
|
||||||
|
dprior = sprior[i];
|
||||||
|
deadServs[0] = s;
|
||||||
|
dn = 1;
|
||||||
|
} else if (sprior[i] == dprior) {
|
||||||
|
deadServs[dn++] = s;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (sprior[i] > prior) {
|
||||||
|
prior = sprior[i];
|
||||||
|
preqs = pendRequests[i];
|
||||||
|
servs[0] = s;
|
||||||
|
n = 1;
|
||||||
|
} else if (sprior[i] == prior) {
|
||||||
|
if (pendRequests[i] < preqs) {
|
||||||
|
preqs = pendRequests[i];
|
||||||
|
servs[0] = s;
|
||||||
|
n = 1;
|
||||||
|
} else if (pendRequests[i] == preqs) {
|
||||||
|
servs[n++] = s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Server** ss = nullptr;
|
||||||
|
if (n > 0) {
|
||||||
|
ss = servs;
|
||||||
|
} else if (dn > 0) {
|
||||||
|
ss = deadServs;
|
||||||
|
n = dn;
|
||||||
|
} else {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
n = h->rand() % n;
|
||||||
|
return ss[n];
|
||||||
|
}
|
||||||
|
|
||||||
|
/***************************************************
|
||||||
|
* other thread may read ServerGroup when add or remoev,
|
||||||
|
* but we don't use lock
|
||||||
|
*
|
||||||
|
*
|
||||||
|
**************************************************/
|
||||||
|
void ServerGroup::add(Server* s)
|
||||||
|
{
|
||||||
|
if (mServs.size() == mServs.capacity()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (unsigned i = 0; i < mServs.size(); ++i) {
|
||||||
|
if (mServs[i] == s) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mServs.push_back(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerGroup::remove(Server* s)
|
||||||
|
{
|
||||||
|
for (unsigned i = 0; i < mServs.size(); ++i) {
|
||||||
|
if (mServs[i] == s) {
|
||||||
|
mServs[i] = mServs.back();
|
||||||
|
mServs.resize(mServs.size() - 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
37
src/ServerGroup.h
Normal file
37
src/ServerGroup.h
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_SERVER_GROUP_H_
|
||||||
|
#define _PREDIXY_SERVER_GROUP_H_
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <deque>
|
||||||
|
#include <vector>
|
||||||
|
#include "Server.h"
|
||||||
|
#include "String.h"
|
||||||
|
#include "Predixy.h"
|
||||||
|
|
||||||
|
class ServerGroup : public ID<ServerGroup>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ServerGroup(ServerPool* pool, const String& name);
|
||||||
|
~ServerGroup();
|
||||||
|
ServerPool* pool() const {return mPool;}
|
||||||
|
String name() const {return mName;}
|
||||||
|
Server* getMaster() const;
|
||||||
|
Server* getServer(Handler* h, Request* req) const;
|
||||||
|
void add(Server* s);
|
||||||
|
void remove(Server* s);
|
||||||
|
private:
|
||||||
|
Server* getReadServer(Handler* h) const;
|
||||||
|
Server* getReadServer(Handler* h, DC* localDC) const;
|
||||||
|
private:
|
||||||
|
ServerPool* mPool;
|
||||||
|
SString<Const::MaxServNameLen> mName;
|
||||||
|
std::vector<Server*> mServs;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
77
src/ServerPool.cpp
Normal file
77
src/ServerPool.cpp
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Server.h"
|
||||||
|
#include "ServerPool.h"
|
||||||
|
#include "Handler.h"
|
||||||
|
|
||||||
|
ServerPool::~ServerPool()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerPool::init(const ServerPoolConf& conf)
|
||||||
|
{
|
||||||
|
mPassword = conf.password;
|
||||||
|
mMasterReadPriority = conf.masterReadPriority;
|
||||||
|
mStaticSlaveReadPriority = conf.staticSlaveReadPriority;
|
||||||
|
mDynamicSlaveReadPriority = conf.dynamicSlaveReadPriority;
|
||||||
|
mRefreshInterval = conf.refreshInterval * 1000000;
|
||||||
|
mServerFailureLimit = conf.serverFailureLimit;
|
||||||
|
mServerRetryTimeout = conf.serverRetryTimeout * 1000000;
|
||||||
|
mDbNum = conf.databases;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ServerPool::refresh()
|
||||||
|
{
|
||||||
|
long last = mLastRefreshTime;
|
||||||
|
long now = Util::nowUSec();
|
||||||
|
if (now - last < mRefreshInterval) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return AtomicCAS(mLastRefreshTime, last, now);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerPool::handleResponse(Handler* h, ConnectConnection* s, Request* req, Response* res)
|
||||||
|
{
|
||||||
|
UniqueLock<Mutex> lck(mMtx, TryLockTag);
|
||||||
|
if (!lck) {
|
||||||
|
logNotice("server pool is updating by other thread");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mHandleResponseFunc(this, h, s, req, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
Server* ServerPool::randServer(Handler* h, const std::vector<Server*>& servs)
|
||||||
|
{
|
||||||
|
Server* s = nullptr;
|
||||||
|
int idx = h->rand() % servs.size();
|
||||||
|
for (size_t i = 0; i < servs.size(); ++i) {
|
||||||
|
Server* serv = servs[idx++ % servs.size()];
|
||||||
|
if (!serv->online()) {
|
||||||
|
continue;
|
||||||
|
} else if (serv->fail()) {
|
||||||
|
s = serv;
|
||||||
|
} else {
|
||||||
|
return serv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
Server* ServerPool::iter(const std::vector<Server*>& servs, int& cursor)
|
||||||
|
{
|
||||||
|
int size = servs.size();
|
||||||
|
if (cursor < 0 || cursor >= size) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
while (cursor < size) {
|
||||||
|
Server* serv = servs[cursor++];
|
||||||
|
if (serv->online()) {
|
||||||
|
return serv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
169
src/ServerPool.h
Normal file
169
src/ServerPool.h
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_SERVER_POOL_H_
|
||||||
|
#define _PREDIXY_SERVER_POOL_H_
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <map>
|
||||||
|
#include "Predixy.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class ServerPool
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum Type
|
||||||
|
{
|
||||||
|
Unknown,
|
||||||
|
Cluster,
|
||||||
|
Sentinel
|
||||||
|
};
|
||||||
|
static const int DefaultServerRetryTimeout = 10000000;
|
||||||
|
static const int DefaultRefreshInterval = 1000000;
|
||||||
|
public:
|
||||||
|
virtual ~ServerPool();
|
||||||
|
void init(const ServerPoolConf& conf);
|
||||||
|
Proxy* proxy() const
|
||||||
|
{
|
||||||
|
return mProxy;
|
||||||
|
}
|
||||||
|
bool refresh();
|
||||||
|
int type() const
|
||||||
|
{
|
||||||
|
return mType;
|
||||||
|
}
|
||||||
|
const String& password() const
|
||||||
|
{
|
||||||
|
return mPassword;
|
||||||
|
}
|
||||||
|
int masterReadPriority() const
|
||||||
|
{
|
||||||
|
return mMasterReadPriority;
|
||||||
|
}
|
||||||
|
int staticSlaveReadPriority() const
|
||||||
|
{
|
||||||
|
return mStaticSlaveReadPriority;
|
||||||
|
}
|
||||||
|
int dynamicSlaveReadPriority() const
|
||||||
|
{
|
||||||
|
return mDynamicSlaveReadPriority;
|
||||||
|
}
|
||||||
|
long refreshInterval() const
|
||||||
|
{
|
||||||
|
return mRefreshInterval;
|
||||||
|
}
|
||||||
|
int serverFailureLimit() const
|
||||||
|
{
|
||||||
|
return mServerFailureLimit;
|
||||||
|
}
|
||||||
|
long serverRetryTimeout() const
|
||||||
|
{
|
||||||
|
return mServerRetryTimeout;
|
||||||
|
}
|
||||||
|
int dbNum() const
|
||||||
|
{
|
||||||
|
return mDbNum;
|
||||||
|
}
|
||||||
|
ServerGroup* getGroup(int idx)
|
||||||
|
{
|
||||||
|
return idx < (int)mGroupPool.size() ? mGroupPool[idx] : nullptr;
|
||||||
|
}
|
||||||
|
Server* getServer(const String& addr)
|
||||||
|
{
|
||||||
|
UniqueLock<Mutex> lck(mMtx);
|
||||||
|
auto it = mServs.find(addr);
|
||||||
|
return it == mServs.end() ? nullptr : it->second;
|
||||||
|
}
|
||||||
|
Server* getServer(Handler* h, Request* req) const
|
||||||
|
{
|
||||||
|
return mGetServerFunc(this, h, req);
|
||||||
|
}
|
||||||
|
Server* iter(int& cursor) const
|
||||||
|
{
|
||||||
|
return mIterFunc(this, cursor);
|
||||||
|
}
|
||||||
|
void refreshRequest(Handler* h)
|
||||||
|
{
|
||||||
|
mRefreshRequestFunc(this, h);
|
||||||
|
}
|
||||||
|
void handleResponse(Handler* h, ConnectConnection* s, Request* req, Response* res);
|
||||||
|
protected:
|
||||||
|
typedef Server* (*GetServerFunc)(const ServerPool* p, Handler* h, Request* req);
|
||||||
|
typedef Server* (*IterFunc)(const ServerPool* p, int& cursor);
|
||||||
|
typedef void (*RefreshRequestFunc)(ServerPool* p, Handler* h);
|
||||||
|
typedef void (*HandleResponseFunc)(ServerPool* p, Handler* h, ConnectConnection* s, Request* req, Response* res);
|
||||||
|
template<class T>
|
||||||
|
ServerPool(Proxy* p, int type, T* sub = nullptr):
|
||||||
|
mProxy(p),
|
||||||
|
mType(type),
|
||||||
|
mGetServerFunc(T::getServer),
|
||||||
|
mIterFunc(T::iter),
|
||||||
|
mRefreshRequestFunc(T::refreshRequest),
|
||||||
|
mHandleResponseFunc(T::handleResponse),
|
||||||
|
mLastRefreshTime(0),
|
||||||
|
mMasterReadPriority(50),
|
||||||
|
mStaticSlaveReadPriority(0),
|
||||||
|
mDynamicSlaveReadPriority(0),
|
||||||
|
mRefreshInterval(DefaultRefreshInterval),
|
||||||
|
mServerFailureLimit(10),
|
||||||
|
mServerRetryTimeout(DefaultServerRetryTimeout),
|
||||||
|
mDbNum(1)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
static Server* randServer(Handler* h, const std::vector<Server*>& servs);
|
||||||
|
static Server* iter(const std::vector<Server*>& servs, int& cursor);
|
||||||
|
protected:
|
||||||
|
std::map<String, Server*> mServs;
|
||||||
|
std::vector<ServerGroup*> mGroupPool;
|
||||||
|
private:
|
||||||
|
Proxy* mProxy;
|
||||||
|
int mType;
|
||||||
|
GetServerFunc mGetServerFunc;
|
||||||
|
IterFunc mIterFunc;
|
||||||
|
RefreshRequestFunc mRefreshRequestFunc;
|
||||||
|
HandleResponseFunc mHandleResponseFunc;
|
||||||
|
AtomicLong mLastRefreshTime;
|
||||||
|
Mutex mMtx;
|
||||||
|
String mPassword;
|
||||||
|
int mMasterReadPriority;
|
||||||
|
int mStaticSlaveReadPriority;
|
||||||
|
int mDynamicSlaveReadPriority;
|
||||||
|
long mRefreshInterval;
|
||||||
|
int mServerFailureLimit;
|
||||||
|
long mServerRetryTimeout;
|
||||||
|
int mDbNum;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
class ServerPoolTmpl : public ServerPool
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ServerPoolTmpl(Proxy* p, int type):
|
||||||
|
ServerPool(p, type, (ServerPoolTmpl<T>*)this)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
static Server* getServer(const ServerPool* p, Handler* h, Request* req)
|
||||||
|
{
|
||||||
|
return static_cast<const T*>(p)->getServer(h, req);
|
||||||
|
}
|
||||||
|
static Server* iter(const ServerPool* p, int& cursor)
|
||||||
|
{
|
||||||
|
return static_cast<const T*>(p)->iter(cursor);
|
||||||
|
}
|
||||||
|
static void refreshRequest(ServerPool* p, Handler* h)
|
||||||
|
{
|
||||||
|
static_cast<T*>(p)->refreshRequest(h);
|
||||||
|
}
|
||||||
|
static void handleResponse(ServerPool* p, Handler* h, ConnectConnection* s, Request* req, Response* res)
|
||||||
|
{
|
||||||
|
static_cast<T*>(p)->handleResponse(h, s, req, res);
|
||||||
|
}
|
||||||
|
friend class ServerPool;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
212
src/Socket.cpp
Normal file
212
src/Socket.cpp
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
#include <sys/uio.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <netinet/ip.h>
|
||||||
|
#include <netinet/tcp.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <string>
|
||||||
|
#include "Socket.h"
|
||||||
|
#include "Util.h"
|
||||||
|
#include "Logger.h"
|
||||||
|
#include "Timer.h"
|
||||||
|
|
||||||
|
Socket::Socket(int fd):
|
||||||
|
mClassType(RawType),
|
||||||
|
mFd(fd),
|
||||||
|
mEvts(0),
|
||||||
|
mStatus(fd >= 0 ? Normal : None)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Socket::Socket(int domain, int type, int protocol):
|
||||||
|
mClassType(RawType),
|
||||||
|
mFd(Socket::socket(domain, type, protocol)),
|
||||||
|
mStatus(Normal)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Socket::~Socket()
|
||||||
|
{
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Socket::close()
|
||||||
|
{
|
||||||
|
if (mFd >= 0) {
|
||||||
|
::close(mFd);
|
||||||
|
mFd = -1;
|
||||||
|
mEvts = 0;
|
||||||
|
mStatus = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Socket::attach(int fd)
|
||||||
|
{
|
||||||
|
close();
|
||||||
|
mFd = fd;
|
||||||
|
mEvts = 0;
|
||||||
|
mStatus = fd >= 0 ? Normal : None;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Socket::detach()
|
||||||
|
{
|
||||||
|
mFd = -1;
|
||||||
|
mEvts = 0;
|
||||||
|
mStatus = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* Socket::statusStr() const
|
||||||
|
{
|
||||||
|
static const char* strs[] = {
|
||||||
|
"Normal",
|
||||||
|
"None",
|
||||||
|
"End",
|
||||||
|
"IOError",
|
||||||
|
"EventError",
|
||||||
|
"ExceptError"
|
||||||
|
};
|
||||||
|
return mStatus < CustomStatus ? strs[mStatus] : "Custom";
|
||||||
|
}
|
||||||
|
|
||||||
|
int Socket::socket(int domain, int type, int protocol)
|
||||||
|
{
|
||||||
|
int fd = ::socket(domain, type, protocol);
|
||||||
|
if (fd < 0) {
|
||||||
|
Throw(SocketCallFail, "%s", StrError());
|
||||||
|
}
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Socket::getFirstAddr(const char* addr, int type, int protocol, sockaddr* res, socklen_t* len)
|
||||||
|
{
|
||||||
|
if (*addr == '/') { //unix socket
|
||||||
|
struct sockaddr_un sun;
|
||||||
|
memset(&sun, 0, sizeof(sun));
|
||||||
|
sun.sun_family = AF_UNIX;
|
||||||
|
strncpy(sun.sun_path, addr, sizeof(sun.sun_path));
|
||||||
|
if (*len < sizeof(sun)) {
|
||||||
|
*len = sizeof(sun);
|
||||||
|
Throw(AddrLenTooShort, "result address space too short");
|
||||||
|
}
|
||||||
|
*len = sizeof(sun);
|
||||||
|
memcpy(res, &sun, *len);
|
||||||
|
} else {
|
||||||
|
std::string tmp;
|
||||||
|
const char* host = addr;
|
||||||
|
const char* port = strchr(addr, ':');
|
||||||
|
if (port) {
|
||||||
|
tmp.append(addr, port - addr);
|
||||||
|
host = tmp.c_str();
|
||||||
|
port++;
|
||||||
|
}
|
||||||
|
struct addrinfo hints;
|
||||||
|
struct addrinfo *dst;
|
||||||
|
memset(&hints, 0, sizeof(hints));
|
||||||
|
hints.ai_family = AF_UNSPEC;
|
||||||
|
hints.ai_socktype = type;
|
||||||
|
hints.ai_flags = AI_PASSIVE;
|
||||||
|
hints.ai_protocol = protocol;
|
||||||
|
int s = getaddrinfo(host, port, &hints, &dst);
|
||||||
|
if (s != 0) {
|
||||||
|
Throw(InvalidAddr, "invalid addr %s:%s", addr, gai_strerror(s));
|
||||||
|
}
|
||||||
|
if (!dst) {
|
||||||
|
Throw(InvalidAddr, "invalid addr %s", addr);
|
||||||
|
}
|
||||||
|
if (*len < dst->ai_addrlen) {
|
||||||
|
*len = dst->ai_addrlen;
|
||||||
|
Throw(AddrLenTooShort, "result address space too short");
|
||||||
|
}
|
||||||
|
*len = dst->ai_addrlen;
|
||||||
|
memcpy(res, dst->ai_addr, *len);
|
||||||
|
freeaddrinfo(dst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Socket::setNonBlock(bool val)
|
||||||
|
{
|
||||||
|
int flags = fcntl(mFd, F_GETFL, NULL);
|
||||||
|
if (flags < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (val) {
|
||||||
|
flags |= O_NONBLOCK;
|
||||||
|
} else {
|
||||||
|
flags &= ~O_NONBLOCK;
|
||||||
|
}
|
||||||
|
int ret = fcntl(mFd, F_SETFL, flags);
|
||||||
|
return ret == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Socket::setTcpNoDelay(bool val)
|
||||||
|
{
|
||||||
|
int nodelay = val ? 1 : 0;
|
||||||
|
socklen_t len = sizeof(nodelay);
|
||||||
|
int ret = setsockopt(mFd, IPPROTO_TCP, TCP_NODELAY, &nodelay, len);
|
||||||
|
return ret == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Socket::read(void* buf, int cnt)
|
||||||
|
{
|
||||||
|
FuncCallTimer();
|
||||||
|
while (cnt > 0) {
|
||||||
|
int n = ::read(mFd, buf, cnt);
|
||||||
|
if (n < 0) {
|
||||||
|
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||||
|
break;
|
||||||
|
} else if (errno == EINTR) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
mStatus = IOError;
|
||||||
|
} else if (n == 0) {
|
||||||
|
mStatus = End;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Socket::write(const void* buf, int cnt)
|
||||||
|
{
|
||||||
|
FuncCallTimer();
|
||||||
|
while (cnt > 0) {
|
||||||
|
int n = ::write(mFd, buf, cnt);
|
||||||
|
if (n < 0) {
|
||||||
|
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||||
|
break;
|
||||||
|
} else if (errno == EINTR) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
mStatus = IOError;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Socket::writev(const struct iovec* vecs, int cnt)
|
||||||
|
{
|
||||||
|
FuncCallTimer();
|
||||||
|
while (cnt > 0) {
|
||||||
|
int n = ::writev(mFd, vecs, cnt);
|
||||||
|
if (n < 0) {
|
||||||
|
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||||
|
return 0;
|
||||||
|
} else if (errno == EINTR) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
mStatus = IOError;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
105
src/Socket.h
Normal file
105
src/Socket.h
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_SOCKET_H_
|
||||||
|
#define _PREDIXY_SOCKET_H_
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
#include <sys/uio.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <netinet/ip.h>
|
||||||
|
#include <netinet/tcp.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include "Exception.h"
|
||||||
|
|
||||||
|
class Socket
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DefException(SocketCallFail);
|
||||||
|
DefException(InvalidAddr);
|
||||||
|
DefException(AddrLenTooShort);
|
||||||
|
|
||||||
|
enum ClassType
|
||||||
|
{
|
||||||
|
RawType,
|
||||||
|
ListenType,
|
||||||
|
AcceptType,
|
||||||
|
ConnectType,
|
||||||
|
|
||||||
|
CustomType = 1024
|
||||||
|
};
|
||||||
|
enum StatusCode
|
||||||
|
{
|
||||||
|
Normal = 0,
|
||||||
|
None,
|
||||||
|
End,
|
||||||
|
IOError,
|
||||||
|
EventError,
|
||||||
|
ExceptError,
|
||||||
|
|
||||||
|
CustomStatus
|
||||||
|
};
|
||||||
|
public:
|
||||||
|
Socket(int fd = -1);
|
||||||
|
Socket(int domain, int type, int protocol = 0);
|
||||||
|
Socket(const Socket&) = delete;
|
||||||
|
Socket(const Socket&&) = delete;
|
||||||
|
~Socket();
|
||||||
|
void attach(int fd);
|
||||||
|
void detach();
|
||||||
|
void close();
|
||||||
|
bool setNonBlock(bool val = true);
|
||||||
|
bool setTcpNoDelay(bool val = true);
|
||||||
|
int read(void* buf, int cnt);
|
||||||
|
int write(const void* buf, int cnt);
|
||||||
|
int writev(const struct iovec* vecs, int cnt);
|
||||||
|
int classType() const
|
||||||
|
{
|
||||||
|
return mClassType;
|
||||||
|
}
|
||||||
|
int fd() const
|
||||||
|
{
|
||||||
|
return mFd;
|
||||||
|
}
|
||||||
|
bool good() const
|
||||||
|
{
|
||||||
|
return mStatus == Normal;
|
||||||
|
}
|
||||||
|
int status() const
|
||||||
|
{
|
||||||
|
return mStatus;
|
||||||
|
}
|
||||||
|
const char* statusStr() const;
|
||||||
|
void setStatus(int st)
|
||||||
|
{
|
||||||
|
mStatus = st;
|
||||||
|
}
|
||||||
|
int getEvent() const
|
||||||
|
{
|
||||||
|
return mEvts;
|
||||||
|
}
|
||||||
|
void setEvent(int evts)
|
||||||
|
{
|
||||||
|
mEvts = evts;
|
||||||
|
}
|
||||||
|
public:
|
||||||
|
static int socket(int domain, int type, int protocol);
|
||||||
|
static void getFirstAddr(const char* addr, int type, int protocol, sockaddr* res, socklen_t* len);
|
||||||
|
protected:
|
||||||
|
int mClassType;
|
||||||
|
private:
|
||||||
|
int mFd;
|
||||||
|
int mEvts;
|
||||||
|
int mStatus;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
79
src/Stats.h
Normal file
79
src/Stats.h
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_STATS_H_
|
||||||
|
#define _PREDIXY_STATS_H_
|
||||||
|
|
||||||
|
|
||||||
|
struct HandlerStats
|
||||||
|
{
|
||||||
|
long accept = 0;
|
||||||
|
long clientConnections = 0;
|
||||||
|
long requests = 0;
|
||||||
|
long responses = 0;
|
||||||
|
long recvClientBytes = 0;
|
||||||
|
long sendClientBytes = 0;
|
||||||
|
long sendServerBytes = 0;
|
||||||
|
long recvServerBytes = 0;
|
||||||
|
|
||||||
|
HandlerStats& operator+=(const HandlerStats& oth)
|
||||||
|
{
|
||||||
|
accept += oth.accept;
|
||||||
|
clientConnections += oth.clientConnections;
|
||||||
|
requests += oth.requests;
|
||||||
|
responses += oth.responses;
|
||||||
|
recvClientBytes += oth.recvClientBytes;
|
||||||
|
sendClientBytes += oth.sendClientBytes;
|
||||||
|
sendServerBytes += oth.sendServerBytes;
|
||||||
|
recvServerBytes += oth.recvServerBytes;
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
void reset()
|
||||||
|
{
|
||||||
|
accept = 0;
|
||||||
|
requests = 0;
|
||||||
|
responses = 0;
|
||||||
|
recvClientBytes = 0;
|
||||||
|
sendClientBytes = 0;
|
||||||
|
sendServerBytes = 0;
|
||||||
|
recvServerBytes = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ServerStats
|
||||||
|
{
|
||||||
|
int connections = 0;
|
||||||
|
long connect = 0;
|
||||||
|
long close = 0;
|
||||||
|
long requests = 0;
|
||||||
|
long responses = 0;
|
||||||
|
long sendBytes = 0;
|
||||||
|
long recvBytes = 0;
|
||||||
|
|
||||||
|
ServerStats& operator+=(const ServerStats& oth)
|
||||||
|
{
|
||||||
|
connections += oth.connections;
|
||||||
|
connect += oth.connect;
|
||||||
|
close += oth.close;
|
||||||
|
requests += oth.requests;
|
||||||
|
responses += oth.responses;
|
||||||
|
sendBytes += oth.sendBytes;
|
||||||
|
recvBytes += oth.recvBytes;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
void reset()
|
||||||
|
{
|
||||||
|
connect = 0;
|
||||||
|
close = 0;
|
||||||
|
requests = 0;
|
||||||
|
responses = 0;
|
||||||
|
sendBytes = 0;
|
||||||
|
recvBytes = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
325
src/String.h
Normal file
325
src/String.h
Normal file
@ -0,0 +1,325 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_STRING_h_
|
||||||
|
#define _PREDIXY_STRING_h_
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <strings.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class String
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
String():
|
||||||
|
mLen(0),
|
||||||
|
mDat(nullptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
String(const char* str):
|
||||||
|
mLen(strlen(str)),
|
||||||
|
mDat(str)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
String(const char* dat, int len):
|
||||||
|
mLen(len),
|
||||||
|
mDat(dat)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
String(const std::string& s):
|
||||||
|
mLen(s.size()),
|
||||||
|
mDat(s.c_str())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
String(const String& str):
|
||||||
|
mLen(str.mLen),
|
||||||
|
mDat(str.mDat)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
String(String&& str):
|
||||||
|
mLen(str.mLen),
|
||||||
|
mDat(str.mDat)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
String& operator=(const String& str)
|
||||||
|
{
|
||||||
|
if (this != &str) {
|
||||||
|
mDat = str.mDat;
|
||||||
|
mLen = str.mLen;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
String& operator=(const std::string& str)
|
||||||
|
{
|
||||||
|
mDat = str.c_str();
|
||||||
|
mLen = str.size();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
String& operator=(const char* str)
|
||||||
|
{
|
||||||
|
mDat = str;
|
||||||
|
mLen = str ? strlen(str) : 0;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
operator const char*() const
|
||||||
|
{
|
||||||
|
return mDat;
|
||||||
|
}
|
||||||
|
bool operator<(const String& s) const
|
||||||
|
{
|
||||||
|
int r = strncmp(mDat, s.mDat, mLen < s.mLen ? mLen : s.mLen);
|
||||||
|
return r < 0 || (r == 0 && mLen < s.mLen);
|
||||||
|
}
|
||||||
|
bool operator==(const char* s) const
|
||||||
|
{
|
||||||
|
return s ? (mLen == (int)strlen(s) && strncmp(mDat, s, mLen) == 0) : mLen == 0;
|
||||||
|
}
|
||||||
|
bool operator==(const String& s) const
|
||||||
|
{
|
||||||
|
return mLen == s.mLen && (mLen > 0 ? (strncmp(mDat, s.mDat, mLen) == 0) : true);
|
||||||
|
}
|
||||||
|
bool operator!=(const String& s) const
|
||||||
|
{
|
||||||
|
return !operator==(s);
|
||||||
|
}
|
||||||
|
bool equal(const String& s, bool ignoreCase= false) const
|
||||||
|
{
|
||||||
|
if (ignoreCase) {
|
||||||
|
return mLen == s.mLen && (mLen > 0 ? (strncasecmp(mDat, s.mDat, mLen) == 0) : true);
|
||||||
|
}
|
||||||
|
return mLen == s.mLen && (mLen > 0 ? (strncmp(mDat, s.mDat, mLen) == 0) : true);
|
||||||
|
}
|
||||||
|
bool empty() const
|
||||||
|
{
|
||||||
|
return mLen == 0;
|
||||||
|
}
|
||||||
|
const char* data() const
|
||||||
|
{
|
||||||
|
return mDat;
|
||||||
|
}
|
||||||
|
int length() const
|
||||||
|
{
|
||||||
|
return mLen;
|
||||||
|
}
|
||||||
|
bool hasPrefix(const String& s) const
|
||||||
|
{
|
||||||
|
return s.mLen <= mLen ? (memcmp(mDat, s.mDat, s.mLen) == 0): false;
|
||||||
|
}
|
||||||
|
String commonPrefix(const String& s) const
|
||||||
|
{
|
||||||
|
int cnt = 0;
|
||||||
|
int len = mLen < s.mLen ? mLen : s.mLen;
|
||||||
|
while (cnt < len && mDat[cnt] == s.mDat[cnt]) {
|
||||||
|
++cnt;
|
||||||
|
}
|
||||||
|
return String(mDat, cnt);
|
||||||
|
}
|
||||||
|
bool toInt(int& v) const
|
||||||
|
{
|
||||||
|
v = 0;
|
||||||
|
int i = 0;
|
||||||
|
int sign = 1;
|
||||||
|
if (i > 0) {
|
||||||
|
if (mDat[0] == '+') {
|
||||||
|
++i;
|
||||||
|
} else if (mDat[0] == '+') {
|
||||||
|
sign = -1;
|
||||||
|
++i;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for ( ; i < mLen; ++i) {
|
||||||
|
if (mDat[i] >= '0' && mDat[i] <= '9') {
|
||||||
|
v = v * 10 + (mDat[i] - '0');
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v *= sign;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
protected:
|
||||||
|
int mLen;
|
||||||
|
const char* mDat;
|
||||||
|
};
|
||||||
|
|
||||||
|
class StringCaseCmp
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
bool operator()(const String& s1, const String& s2) const
|
||||||
|
{
|
||||||
|
int r = strncasecmp(s1.data(), s2.data(), s1.length() < s2.length() ? s1.length() : s2.length());
|
||||||
|
return r < 0 || (r == 0 && s1.length() < s2.length());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <int Size>
|
||||||
|
class SString : public String
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SString():
|
||||||
|
String(mBuf, 0)
|
||||||
|
{
|
||||||
|
mBuf[0] = '\0';
|
||||||
|
}
|
||||||
|
SString(const char* str):
|
||||||
|
String(mBuf, 0)
|
||||||
|
{
|
||||||
|
set(str);
|
||||||
|
}
|
||||||
|
SString(const char* dat, int len):
|
||||||
|
String(mBuf, 0)
|
||||||
|
{
|
||||||
|
set(dat, len);
|
||||||
|
}
|
||||||
|
SString(const std::string& str)
|
||||||
|
{
|
||||||
|
set(str.c_str(), str.length());
|
||||||
|
}
|
||||||
|
SString(const SString& str)
|
||||||
|
{
|
||||||
|
set(str.mDat, str.mLen);
|
||||||
|
}
|
||||||
|
template<class T>
|
||||||
|
SString(const T& str)
|
||||||
|
{
|
||||||
|
set(str.data(), str.length());
|
||||||
|
}
|
||||||
|
SString& operator=(const SString& str)
|
||||||
|
{
|
||||||
|
if (this != &str) {
|
||||||
|
set(str.data(), str.length());
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
template<class T>
|
||||||
|
SString& operator=(const T& str)
|
||||||
|
{
|
||||||
|
set(str.data(), str.length());
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
void clear()
|
||||||
|
{
|
||||||
|
mLen = 0;
|
||||||
|
mBuf[0] = '\0';
|
||||||
|
}
|
||||||
|
int size() const
|
||||||
|
{
|
||||||
|
return Size;
|
||||||
|
}
|
||||||
|
bool set(const char* str)
|
||||||
|
{
|
||||||
|
return set(str, str ? strlen(str) : 0);
|
||||||
|
}
|
||||||
|
bool set(const char* dat, int len)
|
||||||
|
{
|
||||||
|
mDat = mBuf;
|
||||||
|
if ((mLen = len < Size ? len : Size) > 0 ) {
|
||||||
|
memcpy(mBuf, dat, mLen);
|
||||||
|
}
|
||||||
|
mBuf[mLen < 0 ? 0 : mLen] = '\0';
|
||||||
|
return len <= Size;
|
||||||
|
}
|
||||||
|
bool printf(const char* fmt, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap, fmt);
|
||||||
|
bool ret = vprintf(fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
bool vprintf(const char* fmt, va_list ap)
|
||||||
|
{
|
||||||
|
mDat = mBuf;
|
||||||
|
int len = vsnprintf(mBuf, Size, fmt, ap);
|
||||||
|
mLen = len < Size ? len : Size;
|
||||||
|
return mLen == len;
|
||||||
|
}
|
||||||
|
bool strftime(const char* fmt, time_t t)
|
||||||
|
{
|
||||||
|
struct tm m;
|
||||||
|
localtime_r(&t, &m);
|
||||||
|
int ret = ::strftime(mBuf, Size, fmt, &m);
|
||||||
|
return ret > 0 && ret < Size;
|
||||||
|
}
|
||||||
|
bool append(char c)
|
||||||
|
{
|
||||||
|
if (mLen < Size) {
|
||||||
|
if (mLen < 0) {
|
||||||
|
mLen = 0;
|
||||||
|
}
|
||||||
|
mBuf[mLen++] = c;
|
||||||
|
mBuf[mLen] = '\0';
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool append(const char* str)
|
||||||
|
{
|
||||||
|
return append(str, strlen(str));
|
||||||
|
}
|
||||||
|
bool append(const char* dat, int len)
|
||||||
|
{
|
||||||
|
if (dat && len >= 0) {
|
||||||
|
if (mLen < 0) {
|
||||||
|
mLen = 0;
|
||||||
|
}
|
||||||
|
int room = Size - mLen;
|
||||||
|
memcpy(mBuf + mLen, dat, len < room ? len : room);
|
||||||
|
mLen += len < room ? len : room;
|
||||||
|
mBuf[mLen] = '\0';
|
||||||
|
return len <= room;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool printHex(const char* dat, int len)
|
||||||
|
{
|
||||||
|
mDat = mBuf;
|
||||||
|
int i;
|
||||||
|
int n = 0;
|
||||||
|
for (i = 0; i < len && n < Size; ++i) {
|
||||||
|
switch (dat[i]) {
|
||||||
|
case '\r':
|
||||||
|
if (n + 1 < Size) {
|
||||||
|
mBuf[n++] = '\\';
|
||||||
|
mBuf[n++] = 'r';
|
||||||
|
} else {
|
||||||
|
i = len;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '\n':
|
||||||
|
if (n + 1 < Size) {
|
||||||
|
mBuf[n++] = '\\';
|
||||||
|
mBuf[n++] = 'n';
|
||||||
|
} else {
|
||||||
|
i = len;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (isgraph(dat[i])) {
|
||||||
|
mBuf[n++] = dat[i];
|
||||||
|
} else if (n + 4 < Size) {
|
||||||
|
snprintf(mBuf + n, Size - n, "\\x%02X", (int)dat[i]);
|
||||||
|
n += 4;
|
||||||
|
} else {
|
||||||
|
i = len;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mBuf[n] = '\0';
|
||||||
|
mLen = n;
|
||||||
|
return mLen < Size;
|
||||||
|
}
|
||||||
|
protected:
|
||||||
|
char mBuf[Size + 1];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
59
src/Subscribe.cpp
Normal file
59
src/Subscribe.cpp
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Subscribe.h"
|
||||||
|
|
||||||
|
Subscribe::Subscribe():
|
||||||
|
mPendSub(0),
|
||||||
|
mSub(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
SubscribeParser::Status SubscribeParser::parse(const Segment& body, int& chs)
|
||||||
|
{
|
||||||
|
SegmentStr<128> str(body);
|
||||||
|
Status st = Unknown;
|
||||||
|
chs = 0;
|
||||||
|
if (str.hasPrefix("*3\r\n$7\r\nmessage\r\n")) {
|
||||||
|
st = Message;
|
||||||
|
} else if (str.hasPrefix("*4\r\n$8\r\npmessage\r\n")) {
|
||||||
|
st = Pmessage;
|
||||||
|
} else if (str.hasPrefix("*3\r\n$9\r\nsubscribe\r\n")) {
|
||||||
|
st = Subscribe;
|
||||||
|
chs = -1;
|
||||||
|
} else if (str.hasPrefix("*3\r\n$10\r\npsubscribe\r\n")) {
|
||||||
|
st = Psubscribe;
|
||||||
|
chs = -1;
|
||||||
|
} else if (str.hasPrefix("*3\r\n$11\r\nunsubscribe\r\n")) {
|
||||||
|
st = Unsubscribe;
|
||||||
|
chs = -1;
|
||||||
|
} else if (str.hasPrefix("*3\r\n$12\r\npunsubscribe\r\n")) {
|
||||||
|
st = Punsubscribe;
|
||||||
|
chs = -1;
|
||||||
|
} else if (str.hasPrefix("-")) {
|
||||||
|
st = Error;
|
||||||
|
} else if (str.hasPrefix("$")) {
|
||||||
|
st = String;
|
||||||
|
}
|
||||||
|
if (chs < 0) {
|
||||||
|
if (!str.complete()) {
|
||||||
|
Segment tmp(body);
|
||||||
|
tmp.rewind();
|
||||||
|
tmp.cut(body.length() - 12);
|
||||||
|
str.set(tmp);
|
||||||
|
}
|
||||||
|
const char* p = str.data() + str.length();
|
||||||
|
for (int i = 0; i < str.length(); ++i) {
|
||||||
|
if (*--p == ':') {
|
||||||
|
chs = atoi(p + 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return st;
|
||||||
|
}
|
||||||
63
src/Subscribe.h
Normal file
63
src/Subscribe.h
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_SUBSCRIBE_H_
|
||||||
|
#define _PREDIXY_SUBSCRIBE_H_
|
||||||
|
|
||||||
|
#include "Predixy.h"
|
||||||
|
|
||||||
|
class Subscribe
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static const int16_t MaxSub = 32000;
|
||||||
|
public:
|
||||||
|
Subscribe();
|
||||||
|
int incrPendSub()
|
||||||
|
{
|
||||||
|
return mPendSub < MaxSub ? ++mPendSub : 0;
|
||||||
|
}
|
||||||
|
int decrPendSub()
|
||||||
|
{
|
||||||
|
return mPendSub > 0 ? --mPendSub : 0;
|
||||||
|
}
|
||||||
|
bool inPendSub() const
|
||||||
|
{
|
||||||
|
return mPendSub > 0;
|
||||||
|
}
|
||||||
|
void setSub(int chs)
|
||||||
|
{
|
||||||
|
mSub = chs;
|
||||||
|
}
|
||||||
|
bool inSub(bool includePend) const
|
||||||
|
{
|
||||||
|
return mSub > 0 || (includePend && mPendSub > 0);
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
int16_t mPendSub;
|
||||||
|
int16_t mSub;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SubscribeParser
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum Status
|
||||||
|
{
|
||||||
|
Unknown,
|
||||||
|
Subscribe,
|
||||||
|
Psubscribe,
|
||||||
|
Unsubscribe,
|
||||||
|
Punsubscribe,
|
||||||
|
Message,
|
||||||
|
Pmessage,
|
||||||
|
Error,
|
||||||
|
String
|
||||||
|
};
|
||||||
|
public:
|
||||||
|
static Status parse(const Segment& body, int& chs);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
60
src/Sync.h
Normal file
60
src/Sync.h
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_SYNC_H_
|
||||||
|
#define _PREDIXY_SYNC_H_
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_SINGLE_THREAD_
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
#define Atomic std::atomic
|
||||||
|
#define AtomicInt std::atomic<int>
|
||||||
|
#define AtomicLong std::atomic<long>
|
||||||
|
#define AtomicCAS(v, o, n) v.compare_exchange_strong(o, n)
|
||||||
|
#define Mutex std::mutex
|
||||||
|
#define UniqueLock std::unique_lock
|
||||||
|
#define TryLockTag std::try_to_lock_t()
|
||||||
|
|
||||||
|
|
||||||
|
#else //_PREDIXY_SINGLE_THREAD_
|
||||||
|
|
||||||
|
#define AtomicInt int
|
||||||
|
#define AtomicLong long
|
||||||
|
#define AtomicCAS(v, oldVal, newVal) (v == (oldVal) ? v = (newVal), true : false)
|
||||||
|
#define TryLockTag ""
|
||||||
|
|
||||||
|
class Mutex
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void lock() const
|
||||||
|
{
|
||||||
|
}
|
||||||
|
void unlock() const
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class M>
|
||||||
|
class UniqueLock
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
template<class... A>
|
||||||
|
UniqueLock(A&&... args)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
operator bool() const
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif //_PREDIXY_MULTI_THREAD_
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
62
src/Timer.cpp
Normal file
62
src/Timer.cpp
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <iostream>
|
||||||
|
#include "Timer.h"
|
||||||
|
|
||||||
|
Mutex TimerPoint::Mtx;
|
||||||
|
TimerPoint* TimerPoint::Head = nullptr;
|
||||||
|
TimerPoint* TimerPoint::Tail = nullptr;
|
||||||
|
int TimerPoint::PointCnt = 0;
|
||||||
|
|
||||||
|
TimerPoint::TimerPoint(const char* key):
|
||||||
|
mKey(key),
|
||||||
|
mElapsed(0),
|
||||||
|
mCount(0),
|
||||||
|
mNext(nullptr)
|
||||||
|
{
|
||||||
|
UniqueLock<Mutex> lck(Mtx);
|
||||||
|
++PointCnt;
|
||||||
|
if (Tail) {
|
||||||
|
Tail->mNext = this;
|
||||||
|
Tail = this;
|
||||||
|
} else {
|
||||||
|
Head = Tail = this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#define StaticPointLen 1024
|
||||||
|
void TimerPoint::report()
|
||||||
|
{
|
||||||
|
TimerPoint* points0[StaticPointLen];
|
||||||
|
TimerPoint** points = points0;
|
||||||
|
int cnt = PointCnt;
|
||||||
|
if (cnt <= 0) {
|
||||||
|
return;
|
||||||
|
} else if (cnt > StaticPointLen) {
|
||||||
|
points = new TimerPoint*[cnt];
|
||||||
|
}
|
||||||
|
int i = 0;
|
||||||
|
for (TimerPoint* p = Head; p && i < cnt; p = p->mNext) {
|
||||||
|
points[i++] = p;
|
||||||
|
}
|
||||||
|
std::sort(points, points + cnt,
|
||||||
|
[](TimerPoint* p1, TimerPoint* p2)
|
||||||
|
{return p1->elapsed() > p2->elapsed();});
|
||||||
|
printf("%16s %12s %8s %s\n","Total(us)", "Count", "Avg(us)", "Point" );
|
||||||
|
for (i = 0; i < cnt; ++i) {
|
||||||
|
auto p = points[i];
|
||||||
|
printf("%16ld %12ld %8ld %s\n",
|
||||||
|
p->elapsed(), p->count(),
|
||||||
|
p->elapsed()/p->count(), p->key());
|
||||||
|
}
|
||||||
|
if (points != points0) {
|
||||||
|
delete[] points;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
109
src/Timer.h
Normal file
109
src/Timer.h
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* predixy - A high performance and full features proxy for redis.
|
||||||
|
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREDIXY_TIMER_H_
|
||||||
|
#define _PREDIXY_TIMER_H_
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <chrono>
|
||||||
|
#include "Sync.h"
|
||||||
|
|
||||||
|
class TimerPoint
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TimerPoint(const char* key);
|
||||||
|
const char* key() const
|
||||||
|
{
|
||||||
|
return mKey;
|
||||||
|
}
|
||||||
|
long elapsed() const
|
||||||
|
{
|
||||||
|
return mElapsed;
|
||||||
|
}
|
||||||
|
long count() const
|
||||||
|
{
|
||||||
|
return mCount;
|
||||||
|
}
|
||||||
|
void add(long v)
|
||||||
|
{
|
||||||
|
mElapsed += v;
|
||||||
|
++mCount;
|
||||||
|
}
|
||||||
|
static void report();
|
||||||
|
private:
|
||||||
|
const char* mKey;
|
||||||
|
AtomicLong mElapsed;
|
||||||
|
AtomicLong mCount;
|
||||||
|
TimerPoint* mNext;
|
||||||
|
private:
|
||||||
|
static Mutex Mtx;
|
||||||
|
static TimerPoint* Head;
|
||||||
|
static TimerPoint* Tail;
|
||||||
|
static int PointCnt;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Timer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Timer():
|
||||||
|
mKey(nullptr),
|
||||||
|
mStart(now())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
Timer(TimerPoint* key):
|
||||||
|
mKey(key),
|
||||||
|
mStart(now())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
~Timer()
|
||||||
|
{
|
||||||
|
if (mKey) {
|
||||||
|
long v = elapsed();
|
||||||
|
if (v >= 0) {
|
||||||
|
mKey->add(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void restart()
|
||||||
|
{
|
||||||
|
mStart = now();
|
||||||
|
}
|
||||||
|
long stop()
|
||||||
|
{
|
||||||
|
long ret = elapsed();
|
||||||
|
mStart = -1;
|
||||||
|
if (ret >= 0 && mKey) {
|
||||||
|
mKey->add(ret);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
long elapsed() const
|
||||||
|
{
|
||||||
|
return mStart < 0 ? -1 : now() - mStart;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
static long now()
|
||||||
|
{
|
||||||
|
using namespace std::chrono;
|
||||||
|
return duration_cast<microseconds>(steady_clock::now().time_since_epoch()).count();
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
TimerPoint* mKey;
|
||||||
|
long mStart;
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef _PREDIXY_TIMER_STATS_
|
||||||
|
|
||||||
|
#define FuncCallTimer() static TimerPoint _timer_point_tttt_(__PRETTY_FUNCTION__);\
|
||||||
|
Timer _tiemr_tttt_(&_timer_point_tttt_)
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
#define FuncCallTimer()
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user