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
|
||||
|
||||
Copyright (c) 2017, joyieldInc
|
||||
Copyright (c) 2017, Joyield, Inc. <joyield.com@gmail.com>
|
||||
All rights reserved.
|
||||
|
||||
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