Compare commits
238
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c194dae58 | ||
|
|
7a4c4682f2 | ||
|
|
054ed87d85 | ||
|
|
8dcec3bf25 | ||
|
|
7dc811b8cb | ||
|
|
0f3be53d7d | ||
|
|
4e5b1d3851 | ||
|
|
27c8f84c66 | ||
|
|
32463852be | ||
|
|
567d575dbe | ||
|
|
a7982fed9e | ||
|
|
b590cedff1 | ||
|
|
02d71527d8 | ||
|
|
112569d12d | ||
|
|
82c6e9c297 | ||
|
|
fcf862d22b | ||
|
|
16d227ef2d | ||
|
|
ac1eda60b0 | ||
|
|
598387b970 | ||
|
|
8789dc82aa | ||
|
|
7f90bcfaee | ||
|
|
860e3ade2d | ||
|
|
0042a2d737 | ||
|
|
0811e89c80 | ||
|
|
fe71355df9 | ||
|
|
33d00ff7d5 | ||
|
|
c60a0f588e | ||
|
|
942354f925 | ||
|
|
cd7a497164 | ||
|
|
5fca35da3f | ||
|
|
d260b9a499 | ||
|
|
45df42c9fe | ||
|
|
6e326c81dc | ||
|
|
83533b622e | ||
|
|
913090ec63 | ||
|
|
51501514e9 | ||
|
|
09843df1dd | ||
|
|
4f8cf6a23c | ||
|
|
0d119ad3a3 | ||
|
|
6a1275bcab | ||
|
|
3769dbd722 | ||
|
|
afc151d3ba | ||
|
|
6ac57f16cb | ||
|
|
61a707af39 | ||
|
|
5cb0558e8c | ||
|
|
e5dbbc20f1 | ||
|
|
1f036c6587 | ||
|
|
7dd1e95288 | ||
|
|
ecfc0583da | ||
|
|
ff8d85885e | ||
|
|
d89db584fa | ||
|
|
1f2af715cb | ||
|
|
78c05caf5e | ||
|
|
10e4b58312 | ||
|
|
de6ba77bc4 | ||
|
|
5d64f21792 | ||
|
|
86277eee4c | ||
|
|
24430cb820 | ||
|
|
604d26ffde | ||
|
|
338e44a6e7 | ||
|
|
de77addb36 | ||
|
|
9f517b920e | ||
|
|
d0f710ea09 | ||
|
|
d0d72c19d3 | ||
|
|
da49700d5a | ||
|
|
ba07e1f505 | ||
|
|
8ec9d6ef69 | ||
|
|
deff2338fa | ||
|
|
2ee7dd362c | ||
|
|
fddd64e610 | ||
|
|
d275513b04 | ||
|
|
2618476032 | ||
|
|
c924df8c83 | ||
|
|
01f36192c8 | ||
|
|
6e258c76bf | ||
|
|
355d8cb0b5 | ||
|
|
f1e072c57d | ||
|
|
a244985d1d | ||
|
|
b83a52c6f8 | ||
|
|
20dad91bdf | ||
|
|
d548f3daad | ||
|
|
992fe21706 | ||
|
|
2f170ad733 | ||
|
|
29a87f23b1 | ||
|
|
0882fe5e43 | ||
|
|
7e5cf2660c | ||
|
|
91867a5ea2 | ||
|
|
1c30cad1ab | ||
|
|
bd50301f3e | ||
|
|
760dec3a6c | ||
|
|
e8852d782d | ||
|
|
1f84b0648b | ||
|
|
73f01b0606 | ||
|
|
552a6fb39d | ||
|
|
5045ff843a | ||
|
|
c92283770d | ||
|
|
44d4b87f1f | ||
|
|
cebac46589 | ||
|
|
68495c46fe | ||
|
|
3dffb8e21e | ||
|
|
1c53928fca | ||
|
|
660a0cdf02 | ||
|
|
01fc59b787 | ||
|
|
ff8187844a | ||
|
|
1dabf116d8 | ||
|
|
5b97a708fc | ||
|
|
17f41d49b4 | ||
|
|
8514f3c035 | ||
|
|
3fa46ef069 | ||
|
|
3c2fe3f88d | ||
|
|
687054849e | ||
|
|
577b084e3c | ||
|
|
6a9764d183 | ||
|
|
c138dc7da4 | ||
|
|
1b905277bb | ||
|
|
cf7a138684 | ||
|
|
edfd6ae76b | ||
|
|
408e12dcaa | ||
|
|
dfeb899b2d | ||
|
|
2b886275e9 | ||
|
|
93db956e06 | ||
|
|
27fc6199ca | ||
|
|
0b01578d2e | ||
|
|
920c45b818 | ||
|
|
cf6c3f4b04 | ||
|
|
fb90934c47 | ||
|
|
207ca3cedd | ||
|
|
97d3b7dc8d | ||
|
|
abdbfc14c0 | ||
|
|
724376a0df | ||
|
|
476f044db6 | ||
|
|
8b2449dca0 | ||
|
|
763d6165ea | ||
|
|
2e18193f5b | ||
|
|
7f9c973467 | ||
|
|
366e872d4d | ||
|
|
4d8f13ddc3 | ||
|
|
3e518093b6 | ||
|
|
99cec798bb | ||
|
|
ccf96e197b | ||
|
|
8f0a41be98 | ||
|
|
63b29390b2 | ||
|
|
46aa0e2472 | ||
|
|
07d9e35637 | ||
|
|
2e12082b3e | ||
|
|
08532b1e99 | ||
|
|
771ea6e1a6 | ||
|
|
c5802e3f4a | ||
|
|
eab8382d18 | ||
|
|
efc77ad91c | ||
|
|
a3886e29a6 | ||
|
|
db912c5a93 | ||
|
|
2165ac5573 | ||
|
|
f8224a4f90 | ||
|
|
679de7ae2f | ||
|
|
efc3f1783d | ||
|
|
0b8c9eac68 | ||
|
|
4c36024f51 | ||
|
|
8e1db6a05f | ||
|
|
d3882605c0 | ||
|
|
3d6eade781 | ||
|
|
6b017e615f | ||
|
|
3164869d78 | ||
|
|
b38c299886 | ||
|
|
5b40d6e656 | ||
|
|
8c3b630561 | ||
|
|
4484e3b591 | ||
|
|
453a906fc9 | ||
|
|
de5d4be994 | ||
|
|
69bfdcf7d4 | ||
|
|
699465c31e | ||
|
|
45332cc47b | ||
|
|
2d2c17b075 | ||
|
|
048c0f0f37 | ||
|
|
3eb3cc9da4 | ||
|
|
74f6ee8dd5 | ||
|
|
7a55d72f28 | ||
|
|
4e4b3b943c | ||
|
|
61e2547675 | ||
|
|
595b5974f8 | ||
|
|
58418d7c3e | ||
|
|
c02bda324f | ||
|
|
1afef16d88 | ||
|
|
7377aacd84 | ||
|
|
eece2d528c | ||
|
|
eb5aeaf138 | ||
|
|
1a6281f52f | ||
|
|
c2571b314a | ||
|
|
97aeda9828 | ||
|
|
ca9d961073 | ||
|
|
2446fbb3bb | ||
|
|
970e813be2 | ||
|
|
a36d524562 | ||
|
|
bbaf76ea2f | ||
|
|
669f302e16 | ||
|
|
71791e7a8e | ||
|
|
2f91d7ada4 | ||
|
|
5ef8f9531d | ||
|
|
3ce30a6906 | ||
|
|
65e79a8272 | ||
|
|
ef8bc13d0d | ||
|
|
0ffeadd290 | ||
|
|
db0e263b42 | ||
|
|
d526d09e41 | ||
|
|
9738e587da | ||
|
|
da67b5f59a | ||
|
|
825d2904d1 | ||
|
|
3becef9ee6 | ||
|
|
2ccf6e81a6 | ||
|
|
e18b59ae7e | ||
|
|
8ff1353766 | ||
|
|
99677e89d0 | ||
|
|
f43502fb0e | ||
|
|
ebb07fb4bb | ||
|
|
c13c4080e5 | ||
|
|
360664c9c8 | ||
|
|
7ac1b364fe | ||
|
|
1b2b8cbbde | ||
|
|
f487bbbfe2 | ||
|
|
4b2499cf2f | ||
|
|
01a0b09886 | ||
|
|
90419b5681 | ||
|
|
ae7db3fe83 | ||
|
|
454f4bfd28 | ||
|
|
1408d147e4 | ||
|
|
78055bd4ca | ||
|
|
736d447380 | ||
|
|
71b6f64f2a | ||
|
|
c4e93cadfb | ||
|
|
0540df22ba | ||
|
|
9ac5be2ebe | ||
|
|
b902c1413e | ||
|
|
54e9f28922 | ||
|
|
dd889d884f | ||
|
|
1443a814d0 | ||
|
|
076ff11c06 | ||
|
|
4769dc7826 | ||
|
|
039357e471 |
+1
-3
@@ -1,4 +1,3 @@
|
||||
.*.swp
|
||||
*.o
|
||||
*.rdb
|
||||
*.log
|
||||
@@ -8,6 +7,7 @@ redis-benchmark
|
||||
redis-check-dump
|
||||
redis-check-aof
|
||||
doc-tools
|
||||
mkrelease.sh
|
||||
release
|
||||
myredis.conf
|
||||
misc/*
|
||||
@@ -19,5 +19,3 @@ release.h
|
||||
src/transfer.sh
|
||||
src/configs
|
||||
src/redis-server.dSYM
|
||||
redis.ds
|
||||
src/redis.conf
|
||||
|
||||
+48
-8
@@ -1,15 +1,55 @@
|
||||
This is Redis 2.3.x.
|
||||
Redis 2.4 release notes
|
||||
|
||||
It was forked from 2.2 branch a few weeks ago, and is going to be the playground
|
||||
for many new developments, especially related to replacing the VM with something
|
||||
better.
|
||||
Migrating from 2.2 to 2.4
|
||||
=========================
|
||||
|
||||
VM is already completely removed from this release, and instead diskstore was
|
||||
implemented. You can read more about it here:
|
||||
Redis 2.2 is mostly a strict subset of 2.4.
|
||||
The only thing you should be aware is that you can't use .rdb and AOF files
|
||||
generated with 2.4 into a 2.2 instance.
|
||||
|
||||
http://groups.google.com/group/redis-db/browse_thread/thread/d444bc786689bde9
|
||||
2.4 slaves can be attached to 2.2 masters, but not the contrary.
|
||||
|
||||
This Redis version is not intented for production environments.
|
||||
From the point of view of the API Redis 2.4 only adds new commands
|
||||
(other commands now accepts a variable number of arguments) so you don't need
|
||||
to modify your program in order to use Redis 2.4.
|
||||
|
||||
---------
|
||||
CHANGELOG
|
||||
---------
|
||||
|
||||
What's new in Redis 2.3.5 (2.4 Release Candidate 1)
|
||||
===================================================
|
||||
|
||||
This is the first Release Candidate of Redis 2.4, in our experience the
|
||||
server is very stable, however it is always better to closely monitor your
|
||||
server if you switch to 2.4 RC1. Also make sure to perform a backup of your
|
||||
old data set before switching from 2.2 to 2.4.
|
||||
|
||||
WHAT'S NEW IN REDIS 2.4 compared to the 2.2 version?
|
||||
====================================================
|
||||
|
||||
* Specially encoded sorted sets, now small sorted sets will use little memory.
|
||||
* Native persistence of specially encoded data types (ziplists, zipmaps,
|
||||
intsets). Many data sets will be saved and loaded an order of magnitude
|
||||
faster.
|
||||
* Variadic versions of commands: SADD, HDEL, SREM, ZREM, ZADD, L/RPUSH.
|
||||
* Jemalloc support (enabled by default for Linux build) in order to
|
||||
avoid fragmentation issues.
|
||||
* Reduced memory usage while saving.
|
||||
* More info fields (peak memory, fork time, ...)
|
||||
* OBJECT command for objects introspection.
|
||||
* CLIENT command for clients introspection.
|
||||
* Non blocking slave -> master connection.
|
||||
* Better redis-cli connection handling. New redis-cli features.
|
||||
* Better redis-benchmark, now able to benchmark user provided commands.
|
||||
* Colorized Make.
|
||||
* VM deprecated. Still supported but with a big warning... don't use it.
|
||||
* Many speed optimizations and bug fixes.
|
||||
|
||||
Credits: Where not specified the implementation and design are done by
|
||||
Salvatore Sanfilippo and Pieter Noordhuis. Thanks to VMware for making all
|
||||
this possible. Also many thanks to all the other contributors and the amazing
|
||||
community we have.
|
||||
|
||||
Cheers,
|
||||
Salvatore
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
CLUSTER README
|
||||
==============
|
||||
|
||||
Redis Cluster is currenty a work in progress, however there are a few things
|
||||
that you can do already with it to see how it works.
|
||||
|
||||
The following guide show you how to setup a three nodes cluster and issue some
|
||||
basic command against it.
|
||||
|
||||
... WORK IN PROGRESS ...
|
||||
|
||||
1) Show MIGRATE
|
||||
2) Show CLUSTER MEET
|
||||
3) Show link status detection with CLUSTER NODES
|
||||
4) Show how to add slots with CLUSTER ADDSLOTS
|
||||
5) Show redirection
|
||||
6) Show cluster down
|
||||
|
||||
... WORK IN PROGRESS ...
|
||||
|
||||
TODO
|
||||
====
|
||||
|
||||
*** WARNING: all the following problably has some meaning only for
|
||||
*** me (antirez), most info are not updated, so please consider this file
|
||||
*** as a private TODO list / brainstorming.
|
||||
|
||||
- disconnect FAIL clients after some pong idle time.
|
||||
|
||||
---------------------------------
|
||||
|
||||
* Majority rule: the cluster con continue when there are all the hash slots covered AND when there are the majority of masters.
|
||||
* Shutdown on request rule: when a node sees many connections closed or even a timeout longer than usual on almost all the other nodes, it will usually wait for the normal timeout before to change the state, unless it receives a query from a client: in such a case it will put itself into error status.
|
||||
|
||||
--------------------------------
|
||||
|
||||
* When asked for a key that is not in a node's business it will reply:
|
||||
|
||||
-ASK 1.2.3.4:6379 (in case we want the client to ask just one time)
|
||||
-MOVED <slotid> 1.2.3.4:6379 (in case the hash slot is permanently moved)
|
||||
|
||||
So with -ASK a client should just retry the query against this new node, a single time.
|
||||
|
||||
With -MOVED the client should update its hash slots table to reflect the fact that now the specified node is the one to contact for the specified hash slot.
|
||||
|
||||
* Nodes communicate using a binary protocol.
|
||||
|
||||
* Node failure detection.
|
||||
|
||||
1) Every node contains information about all the other nodes:
|
||||
- If this node is believed to work ok or not
|
||||
- The hash slots for which this node is responsible
|
||||
- If the node is a master or a slave
|
||||
- If it is a slave, the slave of which node
|
||||
- if it is a master, the list of slave nodes
|
||||
- The slaves are ordered for "<ip>:<port>" string from lower to higher
|
||||
ordered lexicographically. When a master is down, the cluster will
|
||||
try to elect the first slave in the list.
|
||||
|
||||
2) Every node also contains the unix time where every other node was
|
||||
reported to work properly (that is, it replied to a ping or any other
|
||||
protocol request correctly). For every node we also store the timestamp
|
||||
at which we sent the latest ping, so we can easily compute the current
|
||||
lag.
|
||||
|
||||
3) From time to time a node pings a random node, selected among the nodes
|
||||
with the least recent "alive" time stamp. Three random nodes are selected
|
||||
and the one with lower alive time stamp is pinged.
|
||||
|
||||
4) The ping packet contains also information about a few random nodes
|
||||
alive time stamp. So that the receiver of the ping will update the
|
||||
alive table if the received alive timestamp is more recent the
|
||||
one present in the node local table.
|
||||
|
||||
In the ping packet every node "gossip" information is somethig like
|
||||
this:
|
||||
|
||||
<ip>:<port>:<status>:<pingsent_timestamp>:<pongreceived_timestamp>
|
||||
|
||||
status is OK, POSSIBLE_FAILURE, FAILURE.
|
||||
|
||||
5) The node replies to ping with a pong packet, that also contains a random
|
||||
selections of nodes timestamps.
|
||||
|
||||
A given node thinks another node may be in a failure state once there is a
|
||||
ping timeout bigger than 30 seconds (configurable).
|
||||
|
||||
When a possible failure is detected the node performs the following action:
|
||||
|
||||
1) Is the average between all the other nodes big? For instance bigger
|
||||
than 30 seconds / 2 = 15 seconds? Probably *we* are disconnected.
|
||||
In such a case we don't trust our lag data, and reset all the
|
||||
timestamps of sent ping to zero. This way when we'll reconnect there
|
||||
is no risk that we'll claim many nodes are down, taking inappropriate
|
||||
actions.
|
||||
|
||||
2) Messages from nodes marked as failed are *always* ignored by the other
|
||||
nodes. A new node needs to be "introduced" by a good online node.
|
||||
|
||||
3) If we are well connected (that is, condition "1" is not true) and a
|
||||
node timeout is > 30 seconds, we mark the node as POSSIBLE_FAILURE
|
||||
(a flat in the cluster node structure). Every time we sent a ping
|
||||
to another node we inform this other nodes that we detected this
|
||||
condition, as already stated.
|
||||
|
||||
4) Once a node receives a POSSIBLE_FAILURE status for a node that is
|
||||
already marked as POSSIBLE_FAILURE locally, it sends a message
|
||||
to all the other nodes of type NODE_FAILURE_DETECTED, communicating the
|
||||
ip/port of the specified node.
|
||||
|
||||
All the nodes need to update the status of this node setting it into
|
||||
FAILURE.
|
||||
|
||||
5) If the computer in FAILURE state is a master node, what is needed is
|
||||
to perform a Slave Election.
|
||||
|
||||
SLAVE ELECTION
|
||||
|
||||
1) The slave election is performed by the first slave (with slaves ordered
|
||||
lexicographically). Actually it is the first functioning slave, so if
|
||||
the first slave is marked as failing the next slave will perform the
|
||||
election and so forth. Such a slave is called the "Successor".
|
||||
|
||||
2) The Successor starts checking that all the nodes in the cluster already
|
||||
marked the master in FAILURE state. If at least one node does not agree
|
||||
no action is performed.
|
||||
|
||||
3) If all the nodes agree that the master is failing, the Successor does
|
||||
the following:
|
||||
|
||||
a) It will send a SUCCESSION message to all the other nodes, that will
|
||||
upgrade the hash slot tables accordingly. It will make sure that all
|
||||
the nodes are updated and if some node did not received the message
|
||||
it will keep trying.
|
||||
b) Once all nodes not marked as FAILURE accepted the SUCCESSION message
|
||||
it will update his own table and will start acting as a master
|
||||
accepting write queries.
|
||||
c) Every node receiving the succession message, if not already informed
|
||||
of the change will broadcast the same message to other three random
|
||||
nodes. No action is performed if the specified host was already marked
|
||||
as the master node.
|
||||
d) A node that was a slave of the original master that failed will
|
||||
switch master to the new one once the SUCCESSION message is received.
|
||||
|
||||
RANDOM
|
||||
|
||||
1) When selecting a slave, the system will try to pick one with an IP different than the master and other slaves, if possible.
|
||||
|
||||
2) The PING packet also contains information about the local configuration checksum. This is the SHA1 of the current configuration, without the bits that normally change form one node to another (like latest ping reply, failure status of nodes, and so forth). From time to time the local config SHA1 is checked against the list of the other nodes, and if there is a mismatch between our configuration and the most common one that lasts for more than N seconds, the most common configuration is asked and retrieved from another node. The event is logged.
|
||||
|
||||
3) Every time a node updates its internal cluster configuration, it dumps such a config in the cluster.conf file. On startup the configuration is reloaded.
|
||||
Nodes can share the cluster configuration when needed (for instance if SHA1 does not match) using this exact same format.
|
||||
|
||||
CLIENTS
|
||||
|
||||
- Clients may be configured to use slaves to perform reads, when read-after-write consistency is not required.
|
||||
@@ -1,30 +1 @@
|
||||
To compile Redis, do the following:
|
||||
|
||||
cd src; make
|
||||
|
||||
The compilation will produce a redis-server binary.
|
||||
|
||||
To install Redis, use
|
||||
|
||||
make install
|
||||
|
||||
and all the binaries will be installed on /usr/local/bin.
|
||||
|
||||
Alternatively:
|
||||
|
||||
make PREFIX=/some/other/directory install
|
||||
|
||||
to have the binaries in /some/other/directory/bin.
|
||||
|
||||
Run the server using the following command line:
|
||||
|
||||
/path/to/redis-server
|
||||
|
||||
This will start a Redis server with the default configuration.
|
||||
|
||||
Otherwise if you want to provide your configuration use:
|
||||
|
||||
/path/to/redis-server /path/to/redis.conf
|
||||
|
||||
You can find an example redis.conf file in the root directory
|
||||
of this source distribution.
|
||||
Please check the README file.
|
||||
|
||||
@@ -2,10 +2,7 @@ Where to find complete Redis documentation?
|
||||
-------------------------------------------
|
||||
|
||||
This README is just a fast "quick start" document. You can find more detailed
|
||||
documentation here:
|
||||
|
||||
1) http://code.google.com/p/redis
|
||||
2) Check the 'doc' directory. doc/README.html is a good starting point :)
|
||||
documentation at http://redis.io
|
||||
|
||||
Building Redis
|
||||
--------------
|
||||
@@ -23,26 +20,37 @@ You can run a 32 bit Redis binary using:
|
||||
|
||||
% make 32bit
|
||||
|
||||
After you build Redis is a good idea to test it, using:
|
||||
After building Redis is a good idea to test it, using:
|
||||
|
||||
% make test
|
||||
|
||||
Buliding using tcmalloc
|
||||
-----------------------
|
||||
NOTE: if after building Redis with a 32 bit target you need to rebuild it
|
||||
with a 64 bit target you need to perform a "make clean" in the root
|
||||
directory of the Redis distribution.
|
||||
|
||||
tcmalloc is a fast and space efficient implementation (for little objects)
|
||||
of malloc(). Compiling Redis with it can improve performances and memeory
|
||||
usage. You can read more about it here:
|
||||
Allocator
|
||||
---------
|
||||
|
||||
http://goog-perftools.sourceforge.net/doc/tcmalloc.html
|
||||
By default Redis compiles and links against jemalloc under Linux, since
|
||||
glibc malloc() has memory fragmentation problems.
|
||||
|
||||
In order to compile Redis with tcmalloc support install tcmalloc on your system
|
||||
and then use:
|
||||
To force a libc malloc() build use:
|
||||
|
||||
% make USE_TCMALLOC=yes
|
||||
make FORCE_LIBC_MALLOC=yes
|
||||
|
||||
Note that you can pass any other target to make, as long as you append
|
||||
USE_TCMALLOC=yes at the end.
|
||||
In all the other non Linux systems the libc malloc() is used by default.
|
||||
|
||||
On Mac OS X you can force a jemalloc based build using the following:
|
||||
|
||||
make USE_JEMALLOC=yes
|
||||
|
||||
Verbose build
|
||||
-------------
|
||||
|
||||
Redis will build with a user friendly colorized output by default.
|
||||
If you want to see a more verbose output use the following:
|
||||
|
||||
make V=1
|
||||
|
||||
Running Redis
|
||||
-------------
|
||||
@@ -80,7 +88,7 @@ then in another terminal try the following:
|
||||
|
||||
You can find the list of all the available commands here:
|
||||
|
||||
http://code.google.com/p/redis/wiki/CommandReference
|
||||
http://redis.io/commands
|
||||
|
||||
Enjoy!
|
||||
|
||||
|
||||
@@ -1,85 +1,4 @@
|
||||
Redis TODO
|
||||
----------
|
||||
This is a stable release! No TODO file here.
|
||||
Please check the TODO file in the master branch on github.
|
||||
|
||||
WARNING: are you a possible Redis contributor?
|
||||
Before implementing what is listed what is listed in this file
|
||||
please drop a message in the Redis google group or chat with
|
||||
antirez or pietern on irc.freenode.org #redis to check if the work
|
||||
is already in progress and if the feature is still interesting for
|
||||
us, and *how* exactly this can be implemented to have good changes
|
||||
of a merge. Otherwise it is probably wasted work! Thank you
|
||||
|
||||
|
||||
API CHANGES
|
||||
===========
|
||||
|
||||
* Turn commands into variadic versions when it makes sense, that is, when
|
||||
the variable number of arguments represent values, and there is no conflict
|
||||
with the return value of the command.
|
||||
|
||||
CLUSTER
|
||||
=======
|
||||
|
||||
* Implement rehashing and cluster check in redis-trib.
|
||||
* Reimplement MIGRATE / RESTORE to use just in memory buffers (no disk at
|
||||
all). This will require touching a lot of the RDB stuff around, but we may
|
||||
hand with faster persistence for RDB.
|
||||
* Implement the slave nodes semantics and election.
|
||||
* Allow redis-trib to create a cluster-wide snapshot (using SYNC).
|
||||
* Allow redis-trib to restore a cluster-wide snapshot (implement UPLOAD?).
|
||||
|
||||
APPEND ONLY FILE
|
||||
================
|
||||
|
||||
* in AOF rewirte use HMSET to rewrite small hashes instead of multiple calls
|
||||
to HSET.
|
||||
|
||||
OPTIMIZATIONS
|
||||
=============
|
||||
|
||||
* Avoid COW due to incrementing the dict iterators counter.
|
||||
* SORT: Don't copy the list into a vector when BY argument is constant.
|
||||
* Write the hash table size of every db in the dump, so that Redis can resize the hash table just one time when loading a big DB.
|
||||
* Read-only mode for slaves.
|
||||
* Redis big lists as linked lists of small ziplists?
|
||||
Possibly a simple heuristic that join near nodes when some node gets smaller than the low_level, and split it into two if gets bigger than high_level.
|
||||
|
||||
REPORTING
|
||||
=========
|
||||
|
||||
* Better INFO output with sections.
|
||||
|
||||
RANDOM
|
||||
======
|
||||
|
||||
* Clients should be closed as far as the output buffer list is bigger than a given number of elements (configurable in redis.conf)
|
||||
* Should the redis default configuration, and the default redis.conf, just bind 127.0.0.1?
|
||||
|
||||
KNOWN BUGS
|
||||
==========
|
||||
|
||||
* What happens in the following scenario:
|
||||
1) We are reading an AOF file.
|
||||
2) SETEX FOO 5 BAR
|
||||
3) APPEND FOO ZAP
|
||||
What happens if between 1 and 2 for some reason (system under huge load
|
||||
or alike) too many time passes? We should prevent expires while the
|
||||
AOF is loading.
|
||||
* #519: Slave may have expired keys that were never read in the master (so a DEL
|
||||
is not sent in the replication channel) but are already expired since
|
||||
a lot of time. Maybe after a given delay that is undoubltly greater than
|
||||
the replication link latency we should expire this key on the slave on
|
||||
access?
|
||||
|
||||
DISKSTORE TODO
|
||||
==============
|
||||
|
||||
* Fix FLUSHALL/FLUSHDB: the queue of pending reads/writes should be handled.
|
||||
* Check that 00/00 and ff/ff exist at startup, otherwise exit with error.
|
||||
* Implement sync flush option, where data is written synchronously on disk when a command is executed.
|
||||
* Implement MULTI/EXEC as transaction abstract API to diskstore.c, with transaction_start, transaction_end, and a journal to recover.
|
||||
* Stop BGSAVE thread on shutdown and any other condition where the child is killed during normal bgsave.
|
||||
* Fix RANDOMKEY to really do something interesting
|
||||
* Fix DBSIZE to really do something interesting
|
||||
* Add a DEBUG command to check if an entry is or not in memory currently
|
||||
* dscache.c near 236, kobj = createStringObject... we could use static obj.
|
||||
https://github.com/antirez/redis/raw/master/TODO
|
||||
|
||||
+70
-41
@@ -292,51 +292,80 @@ appendfsync everysec
|
||||
# "no" that is the safest pick from the point of view of durability.
|
||||
no-appendfsync-on-rewrite no
|
||||
|
||||
# Automatic rewrite of the append only file.
|
||||
# Redis is able to automatically rewrite the log file implicitly calling
|
||||
# BGREWRITEAOF when the AOF log size will growth by the specified percentage.
|
||||
#
|
||||
# This is how it works: Redis remembers the size of the AOF file after the
|
||||
# latest rewrite (or if no rewrite happened since the restart, the size of
|
||||
# the AOF at startup is used).
|
||||
#
|
||||
# This base size is compared to the current size. If the current size is
|
||||
# bigger than the specified percentage, the rewrite is triggered. Also
|
||||
# you need to specify a minimal size for the AOF file to be rewritten, this
|
||||
# is useful to avoid rewriting the AOF file even if the percentage increase
|
||||
# is reached but it is still pretty small.
|
||||
#
|
||||
# Specify a precentage of zero in order to disable the automatic AOF
|
||||
# rewrite feature.
|
||||
################################ VIRTUAL MEMORY ###############################
|
||||
|
||||
auto-aof-rewrite-percentage 100
|
||||
auto-aof-rewrite-min-size 64mb
|
||||
### WARNING! Virtual Memory is deprecated in Redis 2.4
|
||||
### The use of Virtual Memory is strongly discouraged.
|
||||
|
||||
#################################### DISK STORE ###############################
|
||||
# Virtual Memory allows Redis to work with datasets bigger than the actual
|
||||
# amount of RAM needed to hold the whole dataset in memory.
|
||||
# In order to do so very used keys are taken in memory while the other keys
|
||||
# are swapped into a swap file, similarly to what operating systems do
|
||||
# with memory pages.
|
||||
#
|
||||
# To enable VM just set 'vm-enabled' to yes, and set the following three
|
||||
# VM parameters accordingly to your needs.
|
||||
|
||||
# When disk store is active Redis works as an on-disk database, where memory
|
||||
# is only used as a object cache.
|
||||
#
|
||||
# This mode is good for datasets that are bigger than memory, and in general
|
||||
# when you want to trade speed for:
|
||||
#
|
||||
# - less memory used
|
||||
# - immediate server restart
|
||||
# - per key durability, without need for backgrond savig
|
||||
#
|
||||
# On the other hand, with disk store enabled MULTI/EXEC are no longer
|
||||
# transactional from the point of view of the persistence on disk, that is,
|
||||
# Redis transactions will still guarantee that commands are either processed
|
||||
# all or nothing, but there is no guarantee that all the keys are flushed
|
||||
# on disk in an atomic way.
|
||||
#
|
||||
# Of course with disk store enabled Redis is not as fast as it is when
|
||||
# working with just the memory back end.
|
||||
vm-enabled no
|
||||
# vm-enabled yes
|
||||
|
||||
diskstore-enabled no
|
||||
diskstore-path redis.ds
|
||||
cache-max-memory 0
|
||||
cache-flush-delay 0
|
||||
# This is the path of the Redis swap file. As you can guess, swap files
|
||||
# can't be shared by different Redis instances, so make sure to use a swap
|
||||
# file for every redis process you are running. Redis will complain if the
|
||||
# swap file is already in use.
|
||||
#
|
||||
# The best kind of storage for the Redis swap file (that's accessed at random)
|
||||
# is a Solid State Disk (SSD).
|
||||
#
|
||||
# *** WARNING *** if you are using a shared hosting the default of putting
|
||||
# the swap file under /tmp is not secure. Create a dir with access granted
|
||||
# only to Redis user and configure Redis to create the swap file there.
|
||||
vm-swap-file /tmp/redis.swap
|
||||
|
||||
# vm-max-memory configures the VM to use at max the specified amount of
|
||||
# RAM. Everything that deos not fit will be swapped on disk *if* possible, that
|
||||
# is, if there is still enough contiguous space in the swap file.
|
||||
#
|
||||
# With vm-max-memory 0 the system will swap everything it can. Not a good
|
||||
# default, just specify the max amount of RAM you can in bytes, but it's
|
||||
# better to leave some margin. For instance specify an amount of RAM
|
||||
# that's more or less between 60 and 80% of your free RAM.
|
||||
vm-max-memory 0
|
||||
|
||||
# Redis swap files is split into pages. An object can be saved using multiple
|
||||
# contiguous pages, but pages can't be shared between different objects.
|
||||
# So if your page is too big, small objects swapped out on disk will waste
|
||||
# a lot of space. If you page is too small, there is less space in the swap
|
||||
# file (assuming you configured the same number of total swap file pages).
|
||||
#
|
||||
# If you use a lot of small objects, use a page size of 64 or 32 bytes.
|
||||
# If you use a lot of big objects, use a bigger page size.
|
||||
# If unsure, use the default :)
|
||||
vm-page-size 32
|
||||
|
||||
# Number of total memory pages in the swap file.
|
||||
# Given that the page table (a bitmap of free/used pages) is taken in memory,
|
||||
# every 8 pages on disk will consume 1 byte of RAM.
|
||||
#
|
||||
# The total swap size is vm-page-size * vm-pages
|
||||
#
|
||||
# With the default of 32-bytes memory pages and 134217728 pages Redis will
|
||||
# use a 4 GB swap file, that will use 16 MB of RAM for the page table.
|
||||
#
|
||||
# It's better to use the smallest acceptable value for your application,
|
||||
# but the default is large in order to work in most conditions.
|
||||
vm-pages 134217728
|
||||
|
||||
# Max number of VM I/O threads running at the same time.
|
||||
# This threads are used to read/write data from/to swap file, since they
|
||||
# also encode and decode objects from disk to memory or the reverse, a bigger
|
||||
# number of threads can help with big objects even if they can't help with
|
||||
# I/O itself as the physical device may not be able to couple with many
|
||||
# reads/writes operations at the same time.
|
||||
#
|
||||
# The special value of 0 turn off threaded I/O and enables the blocking
|
||||
# Virtual Memory implementation.
|
||||
vm-max-threads 4
|
||||
|
||||
############################### ADVANCED CONFIG ###############################
|
||||
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
[Note: this is the Redis manifesto, for general information about
|
||||
installing and running Redis read the README file instead.]
|
||||
|
||||
Redis Manifesto
|
||||
===============
|
||||
|
||||
1 - A DSL for Abstract Data Types. Redis is a DSL (Domain Specific Language) that manipulates abstract data types and implemented as a TCP daemon. Commands manipulate a key space where keys are binary-safe strings and values are different kinds of abstract data types. Every data type represents an abstract version of a fundamental data structure. For instance Redis Lists are an abstract representation of linked lists. In Redis, the essence of a data type isn't just the kind of operations that the data types support, but also the space and time complexity of the data type and the operations performed upon it.
|
||||
|
||||
2 - Memory storage is #1. The Redis data set, composed of defined key-value pairs, is primarily stored in the computer's memory. The amount of memory in all kinds of computers, including entry-level servers, is increasing significantly each year. Memory is fast, and allows Redis to have very predictable performance. Datasets composed of 10k or 40 millions keys will perform similarly. Complex data types like Redis Sorted Sets are easy to implement and manipulate in memory with good performance, making Redis very simple. Redis will continue to explore alternative options (where data can be optionally stored on disk, say) but the main goal of the project remains the development of an in-memory database.
|
||||
|
||||
3 - Fundamental data structures for a fundamental API. The Redis API is a direct consequence of fundamental data structures. APIs can often be arbitrary but not an API that resembles the nature of fundamental data structures. If we ever meet intelligent life forms from another part of the universe, they'll likely know, understand and recognize the same basic data structures we have in our computer science books. Redis will avoid intermediate layers in API, so that the complexity is obvious and more complex operations can be performed as the sum of the basic operations.
|
||||
|
||||
4 - Code is like a poem; it's not just something we write to reach some practical result. Sometimes people that are far from the Redis philosophy suggest using other code written by other authors (frequently in other languages) in order to implement something Redis currently lacks. But to us this is like if Shakespeare decided to end Enrico IV using the Paradiso from the Divina Commedia. Is using any external code a bad idea? Not at all. Like in "One Thousand and One Nights" smaller self contained stories are embedded in a bigger story, we'll be happy to use beautiful self contained libraries when needed. At the same time, when writing the Redis story we're trying to write smaller stories that will fit in to other code.
|
||||
|
||||
5 - We're against complexity. We believe designing systems is a fight against complexity. We'll accept to fight the complexity when it's worthwhile but we'll try hard to recognize when a small feature is not worth 1000s of lines of code. Most of the time the best way to fight complexity is by not creating it at all.
|
||||
|
||||
6 - Two levels of API. The Redis API has two levels: 1) a subset of the API fits naturally into a distributed version of Redis and 2) a more complex API that supports multi-key operations. Both are useful if used judiciously but there's no way to make the more complex multi-keys API distributed in an opaque way without violating our other principles. We don't want to provide the illusion of something that will work magically when actually it can't in all cases. Instead we'll provide commands to quickly migrate keys from one instance to another to perform multi-key operations and expose the tradeoffs to the user.
|
||||
|
||||
7 - We optimize for joy. We believe writing code is a lot of hard work, and the only way it can be worth is by enjoying it. When there is no longer joy in writing code, the best thing to do is stop. To prevent this, we'll avoid taking paths that will make Redis less of a joy to develop.
|
||||
|
||||
+73
-75
@@ -6,6 +6,18 @@ release_hdr := $(shell sh -c './mkreleasehdr.sh')
|
||||
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
|
||||
OPTIMIZATION?=-O2
|
||||
|
||||
CCCOLOR="\033[34m"
|
||||
LINKCOLOR="\033[34;1m"
|
||||
SRCCOLOR="\033[33m"
|
||||
BINCOLOR="\033[37;1m"
|
||||
MAKECOLOR="\033[32;1m"
|
||||
ENDCOLOR="\033[0m"
|
||||
|
||||
ifndef V
|
||||
QUIET_CC = @echo ' ' $(CCCOLOR)CC$(ENDCOLOR) $(SRCCOLOR)$@$(ENDCOLOR);
|
||||
QUIET_LINK = @echo ' ' $(LINKCOLOR)LINK$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR);
|
||||
endif
|
||||
|
||||
ifeq ($(uname_S),Linux)
|
||||
ifneq ($(FORCE_LIBC_MALLOC),yes)
|
||||
USE_JEMALLOC=yes
|
||||
@@ -15,21 +27,21 @@ endif
|
||||
ifeq ($(uname_S),SunOS)
|
||||
CFLAGS?=-std=c99 -pedantic $(OPTIMIZATION) -Wall -W -D__EXTENSIONS__ -D_XPG6
|
||||
CCLINK?=-ldl -lnsl -lsocket -lm -lpthread
|
||||
DEBUG?=-g -ggdb
|
||||
DEBUG?=-g -ggdb
|
||||
else
|
||||
CFLAGS?=-std=c99 -pedantic $(OPTIMIZATION) -Wall -W $(ARCH) $(PROF)
|
||||
CCLINK?=-lm -pthread
|
||||
DEBUG?=-g -rdynamic -ggdb
|
||||
DEBUG?=-g -rdynamic -ggdb
|
||||
endif
|
||||
|
||||
ifeq ($(USE_TCMALLOC),yes)
|
||||
ALLOD_DEPS=
|
||||
ALLOC_DEP=
|
||||
ALLOC_LINK=-ltcmalloc
|
||||
ALLOC_FLAGS=-DUSE_TCMALLOC
|
||||
endif
|
||||
|
||||
ifeq ($(USE_TCMALLOC_MINIMAL),yes)
|
||||
ALLOD_DEPS=
|
||||
ALLOC_DEP=
|
||||
ALLOC_LINK=-ltcmalloc_minimal
|
||||
ALLOC_FLAGS=-DUSE_TCMALLOC
|
||||
endif
|
||||
@@ -40,28 +52,13 @@ ifeq ($(USE_JEMALLOC),yes)
|
||||
ALLOC_FLAGS=-DUSE_JEMALLOC -I../deps/jemalloc/include
|
||||
endif
|
||||
|
||||
CCLINK+= $(ALLOC_LINK)
|
||||
CFLAGS+= $(ALLOC_FLAGS)
|
||||
|
||||
CCOPT= $(CFLAGS) $(ARCH) $(PROF)
|
||||
|
||||
PREFIX= /usr/local
|
||||
INSTALL_BIN= $(PREFIX)/bin
|
||||
INSTALL= cp -p
|
||||
|
||||
CCCOLOR="\033[34m"
|
||||
LINKCOLOR="\033[34;1m"
|
||||
SRCCOLOR="\033[33m"
|
||||
BINCOLOR="\033[37;1m"
|
||||
MAKECOLOR="\033[32;1m"
|
||||
ENDCOLOR="\033[0m"
|
||||
|
||||
ifndef V
|
||||
QUIET_CC = @printf ' %b %b\n' $(CCCOLOR)CC$(ENDCOLOR) $(SRCCOLOR)$@$(ENDCOLOR);
|
||||
QUIET_LINK = @printf ' %b %b\n' $(LINKCOLOR)LINK$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR);
|
||||
endif
|
||||
|
||||
OBJ = adlist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o dscache.o pubsub.o multi.o debug.o sort.o intset.o syncio.o diskstore.o cluster.o crc16.o endian.o
|
||||
OBJ = adlist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o vm.o pubsub.o multi.o debug.o sort.o intset.o syncio.o
|
||||
BENCHOBJ = ae.o anet.o redis-benchmark.o sds.o adlist.o zmalloc.o
|
||||
CLIOBJ = anet.o sds.o adlist.o redis-cli.o zmalloc.o release.o
|
||||
CHECKDUMPOBJ = redis-check-dump.o lzf_c.o lzf_d.o
|
||||
@@ -80,123 +77,124 @@ all: redis-benchmark redis-cli redis-check-dump redis-check-aof redis-server
|
||||
|
||||
# Deps (use make dep to generate this)
|
||||
adlist.o: adlist.c adlist.h zmalloc.h
|
||||
ae.o: ae.c ae.h zmalloc.h config.h ae_kqueue.c
|
||||
ae.o: ae.c ae.h config.h zmalloc.h ae_epoll.c ae_kqueue.c ae_select.c
|
||||
ae_epoll.o: ae_epoll.c
|
||||
ae_kqueue.o: ae_kqueue.c
|
||||
ae_select.o: ae_select.c
|
||||
anet.o: anet.c fmacros.h anet.h
|
||||
aof.o: aof.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h util.h
|
||||
cluster.o: cluster.c redis.h fmacros.h config.h ae.h sds.h dict.h \
|
||||
adlist.h zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h util.h
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
|
||||
config.o: config.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h util.h
|
||||
crc16.o: crc16.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h util.h
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
|
||||
db.o: db.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h util.h
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
|
||||
debug.o: debug.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h util.h sha1.h
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h sha1.h
|
||||
dict.o: dict.c fmacros.h dict.h zmalloc.h
|
||||
diskstore.o: diskstore.c redis.h fmacros.h config.h ae.h sds.h dict.h \
|
||||
adlist.h zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h util.h \
|
||||
sha1.h
|
||||
dscache.o: dscache.c redis.h fmacros.h config.h ae.h sds.h dict.h \
|
||||
adlist.h zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h util.h
|
||||
intset.o: intset.c intset.h zmalloc.h
|
||||
endian.o: endian.c
|
||||
intset.o: intset.c intset.h zmalloc.h endian.h
|
||||
lzf_c.o: lzf_c.c lzfP.h
|
||||
lzf_d.o: lzf_d.c lzfP.h
|
||||
multi.o: multi.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h util.h
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
|
||||
networking.o: networking.c redis.h fmacros.h config.h ae.h sds.h dict.h \
|
||||
adlist.h zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h util.h
|
||||
adlist.h zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
|
||||
object.o: object.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h util.h
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
|
||||
pqsort.o: pqsort.c
|
||||
pubsub.o: pubsub.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h util.h
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
|
||||
rdb.o: rdb.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h util.h lzf.h
|
||||
redis-benchmark.o: redis-benchmark.c fmacros.h ae.h \
|
||||
../deps/hiredis/hiredis.h sds.h adlist.h zmalloc.h
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h lzf.h
|
||||
redis-benchmark.o: redis-benchmark.c fmacros.h ae.c ae.h ae_epoll.c \
|
||||
ae_kqueue.c ae_select.c config.h zmalloc.h ae_kqueue.c adlist.c adlist.h \
|
||||
sds.c sds.h
|
||||
redis-check-aof.o: redis-check-aof.c fmacros.h config.h
|
||||
redis-check-dump.o: redis-check-dump.c lzf.h
|
||||
redis-cli.o: redis-cli.c fmacros.h version.h ../deps/hiredis/hiredis.h \
|
||||
sds.h zmalloc.h ../deps/linenoise/linenoise.h help.h
|
||||
redis-cli.o: redis-cli.c fmacros.h version.h help.h sds.c sds.h
|
||||
redis.o: redis.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h util.h \
|
||||
asciilogo.h
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
|
||||
release.o: release.c release.h
|
||||
replication.o: replication.c redis.h fmacros.h config.h ae.h sds.h dict.h \
|
||||
adlist.h zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h util.h
|
||||
adlist.h zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
|
||||
sds.o: sds.c sds.h zmalloc.h
|
||||
sha1.o: sha1.c sha1.h config.h
|
||||
sha1.o: sha1.c sha1.h
|
||||
sort.o: sort.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h util.h pqsort.h
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h pqsort.h
|
||||
syncio.o: syncio.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h util.h
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
|
||||
t_hash.o: t_hash.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h util.h
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
|
||||
t_list.o: t_list.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h util.h
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
|
||||
t_set.o: t_set.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h util.h
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
|
||||
t_string.o: t_string.c redis.h fmacros.h config.h ae.h sds.h dict.h \
|
||||
adlist.h zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h util.h
|
||||
adlist.h zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
|
||||
t_zset.o: t_zset.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h util.h
|
||||
util.o: util.c fmacros.h util.h
|
||||
ziplist.o: ziplist.c zmalloc.h util.h ziplist.h endian.h
|
||||
zipmap.o: zipmap.c zmalloc.h endian.h
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
|
||||
util.o: util.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
|
||||
vm.o: vm.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
|
||||
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
|
||||
ziplist.o: ziplist.c zmalloc.h ziplist.h
|
||||
zipmap.o: zipmap.c zmalloc.h
|
||||
zmalloc.o: zmalloc.c config.h zmalloc.h
|
||||
|
||||
.PHONY: dependencies
|
||||
|
||||
dependencies:
|
||||
@printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)hiredis$(ENDCOLOR)
|
||||
@echo $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)hiredis$(ENDCOLOR)
|
||||
@cd ../deps/hiredis && $(MAKE) static ARCH="$(ARCH)"
|
||||
@printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)linenoise$(ENDCOLOR)
|
||||
@echo $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)linenoise$(ENDCOLOR)
|
||||
@cd ../deps/linenoise && $(MAKE) ARCH="$(ARCH)"
|
||||
|
||||
../deps/jemalloc/lib/libjemalloc.a:
|
||||
cd ../deps/jemalloc && ./configure $(JEMALLOC_CFLAGS) --with-jemalloc-prefix=je_ --enable-cc-silence && $(MAKE) lib/libjemalloc.a
|
||||
|
||||
redis-server: $(OBJ)
|
||||
$(QUIET_CC)$(CC) -o $(PRGNAME) $(CCOPT) $(DEBUG) $(OBJ) $(CCLINK) $(ALLOC_LINK)
|
||||
|
||||
redis-benchmark: dependencies $(BENCHOBJ)
|
||||
@cd ../deps/hiredis && $(MAKE) static
|
||||
$(QUIET_LINK)$(CC) -o $(BENCHPRGNAME) $(CCOPT) $(DEBUG) $(BENCHOBJ) ../deps/hiredis/libhiredis.a $(CCLINK) $(ALLOC_LINK)
|
||||
@echo $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)jemalloc$(ENDCOLOR)
|
||||
@cd ../deps/jemalloc && ./configure $(JEMALLOC_CFLAGS) --with-jemalloc-prefix=je_ --enable-cc-silence && $(MAKE) lib/libjemalloc.a
|
||||
|
||||
redis-benchmark.o:
|
||||
$(QUIET_CC)$(CC) -c $(CFLAGS) -I../deps/hiredis $(DEBUG) $(COMPILE_TIME) $<
|
||||
|
||||
redis-cli: dependencies $(CLIOBJ)
|
||||
$(QUIET_LINK)$(CC) -o $(CLIPRGNAME) $(CCOPT) $(DEBUG) $(CLIOBJ) ../deps/hiredis/libhiredis.a ../deps/linenoise/linenoise.o $(CCLINK) $(ALLOC_LINK)
|
||||
redis-benchmark: dependencies $(BENCHOBJ)
|
||||
@echo $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)hiredis$(ENDCOLOR)
|
||||
@cd ../deps/hiredis && $(MAKE) static ARCH="$(ARCH)"
|
||||
$(QUIET_LINK)$(CC) -o $(BENCHPRGNAME) $(CCOPT) $(DEBUG) $(BENCHOBJ) ../deps/hiredis/libhiredis.a $(CCLINK) $(ALLOC_LINK)
|
||||
|
||||
redis-cli.o:
|
||||
$(QUIET_CC)$(CC) -c $(CFLAGS) -I../deps/hiredis -I../deps/linenoise $(DEBUG) $(COMPILE_TIME) $<
|
||||
|
||||
redis-cli: dependencies $(CLIOBJ)
|
||||
$(QUIET_LINK)$(CC) -o $(CLIPRGNAME) $(CCOPT) $(DEBUG) $(CLIOBJ) ../deps/hiredis/libhiredis.a ../deps/linenoise/linenoise.o $(CCLINK) $(ALLOC_LINK)
|
||||
|
||||
redis-check-dump.o:
|
||||
$(QUIET_CC)$(CC) -c $(CFLAGS) $(DEBUG) $(COMPILE_TIME) $<
|
||||
|
||||
redis-check-dump: $(CHECKDUMPOBJ)
|
||||
$(QUIET_LINK)$(CC) -o $(CHECKDUMPPRGNAME) $(CCOPT) $(DEBUG) $(CHECKDUMPOBJ) $(CCLINK) $(ALLOC_LINK)
|
||||
$(QUIET_LINK)$(CC) -o $(CHECKDUMPPRGNAME) $(CCOPT) $(DEBUG) $(CHECKDUMPOBJ) $(CCLINK)
|
||||
|
||||
redis-check-aof.o:
|
||||
$(QUIET_CC)$(CC) -c $(CFLAGS) $(DEBUG) $(COMPILE_TIME) $<
|
||||
|
||||
redis-check-aof: $(CHECKAOFOBJ)
|
||||
$(QUIET_LINK)$(CC) -o $(CHECKAOFPRGNAME) $(CCOPT) $(DEBUG) $(CHECKAOFOBJ) $(CCLINK) $(ALLOC_LINK)
|
||||
$(QUIET_LINK)$(CC) -o $(CHECKAOFPRGNAME) $(CCOPT) $(DEBUG) $(CHECKAOFOBJ) $(CCLINK)
|
||||
|
||||
redis-server: $(OBJ)
|
||||
$(QUIET_LINK)$(CC) -o $(PRGNAME) $(CCOPT) $(DEBUG) $(OBJ) $(CCLINK) $(ALLOC_LINK)
|
||||
|
||||
# Because the jemalloc.h header is generated as a part of the jemalloc build
|
||||
# process, building it should complete before building any other object.
|
||||
%.o: %.c $(ALLOC_DEP)
|
||||
$(QUIET_CC)$(CC) -c $(CFLAGS) $(DEBUG) $(COMPILE_TIME) $<
|
||||
$(QUIET_CC)$(CC) -c $(CFLAGS) $(ALLOC_FLAGS) $(DEBUG) $(COMPILE_TIME) $<
|
||||
|
||||
clean:
|
||||
rm -rf $(PRGNAME) $(BENCHPRGNAME) $(CLIPRGNAME) $(CHECKDUMPPRGNAME) $(CHECKAOFPRGNAME) *.o *.gcda *.gcno *.gcov
|
||||
|
||||
dep:
|
||||
$(CC) -MM *.c -I ../deps/hiredis -I ../deps/linenoise
|
||||
$(CC) -MM *.c
|
||||
|
||||
test: redis-server
|
||||
@(cd ..; (which tclsh >/dev/null && tclsh tests/test_helper.tcl --tags "${TAGS}" --file "${FILE}") || echo "You need to install Tcl in order to run tests.")
|
||||
(cd ..; tclsh8.5 tests/test_helper.tcl --tags "${TAGS}" --file "${FILE}")
|
||||
|
||||
bench:
|
||||
./redis-benchmark
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
#include <sys/resource.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
void aofUpdateCurrentSize(void);
|
||||
|
||||
/* Called when the user switches from "appendonly yes" to "appendonly no"
|
||||
* at runtime using the CONFIG command. */
|
||||
void stopAppendOnly(void) {
|
||||
@@ -21,15 +19,15 @@ void stopAppendOnly(void) {
|
||||
server.appendseldb = -1;
|
||||
server.appendonly = 0;
|
||||
/* rewrite operation in progress? kill it, wait child exit */
|
||||
if (server.bgrewritechildpid != -1) {
|
||||
if (server.bgsavechildpid != -1) {
|
||||
int statloc;
|
||||
|
||||
if (kill(server.bgrewritechildpid,SIGKILL) != -1)
|
||||
if (kill(server.bgsavechildpid,SIGKILL) != -1)
|
||||
wait3(&statloc,0,NULL);
|
||||
/* reset the buffer accumulating changes while the child saves */
|
||||
sdsfree(server.bgrewritebuf);
|
||||
server.bgrewritebuf = sdsempty();
|
||||
server.bgrewritechildpid = -1;
|
||||
server.bgsavechildpid = -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +82,6 @@ void flushAppendOnlyFile(void) {
|
||||
}
|
||||
sdsfree(server.aofbuf);
|
||||
server.aofbuf = sdsempty();
|
||||
server.appendonly_current_size += nwritten;
|
||||
|
||||
/* Don't Fsync if no-appendfsync-on-rewrite is set to yes and we have
|
||||
* childs performing heavy I/O on disk. */
|
||||
@@ -224,7 +221,6 @@ int loadAppendOnlyFile(char *filename) {
|
||||
long loops = 0;
|
||||
|
||||
if (fp && redis_fstat(fileno(fp),&sb) != -1 && sb.st_size == 0) {
|
||||
server.appendonly_current_size = 0;
|
||||
fclose(fp);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
@@ -248,6 +244,7 @@ int loadAppendOnlyFile(char *filename) {
|
||||
char buf[128];
|
||||
sds argsds;
|
||||
struct redisCommand *cmd;
|
||||
int force_swapout;
|
||||
|
||||
/* Serve the clients from time to time */
|
||||
if (!(loops++ % 1000)) {
|
||||
@@ -293,6 +290,17 @@ int loadAppendOnlyFile(char *filename) {
|
||||
for (j = 0; j < fakeClient->argc; j++)
|
||||
decrRefCount(fakeClient->argv[j]);
|
||||
zfree(fakeClient->argv);
|
||||
|
||||
/* Handle swapping while loading big datasets when VM is on */
|
||||
force_swapout = 0;
|
||||
if ((zmalloc_used_memory() - server.vm_max_memory) > 1024*1024*32)
|
||||
force_swapout = 1;
|
||||
|
||||
if (server.vm_enabled && force_swapout) {
|
||||
while (zmalloc_used_memory() > server.vm_max_memory) {
|
||||
if (vmSwapOneObjectBlocking() == REDIS_ERR) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* This point can only be reached when EOF is reached without errors.
|
||||
@@ -303,8 +311,6 @@ int loadAppendOnlyFile(char *filename) {
|
||||
freeFakeClient(fakeClient);
|
||||
server.appendonly = appendonly;
|
||||
stopLoading();
|
||||
aofUpdateCurrentSize();
|
||||
server.auto_aofrewrite_base_size = server.appendonly_current_size;
|
||||
return REDIS_OK;
|
||||
|
||||
readerr:
|
||||
@@ -342,7 +348,7 @@ int rewriteAppendOnlyFile(char *filename) {
|
||||
redisDb *db = server.db+j;
|
||||
dict *d = db->dict;
|
||||
if (dictSize(d) == 0) continue;
|
||||
di = dictGetSafeIterator(d);
|
||||
di = dictGetIterator(d);
|
||||
if (!di) {
|
||||
fclose(fp);
|
||||
return REDIS_ERR;
|
||||
@@ -354,14 +360,25 @@ int rewriteAppendOnlyFile(char *filename) {
|
||||
|
||||
/* Iterate this DB writing every entry */
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sds keystr;
|
||||
sds keystr = dictGetEntryKey(de);
|
||||
robj key, *o;
|
||||
time_t expiretime;
|
||||
int swapped;
|
||||
|
||||
keystr = dictGetEntryKey(de);
|
||||
o = dictGetEntryVal(de);
|
||||
initStaticStringObject(key,keystr);
|
||||
|
||||
/* If the value for this key is swapped, load a preview in memory.
|
||||
* We use a "swapped" flag to remember if we need to free the
|
||||
* value object instead to just increment the ref count anyway
|
||||
* in order to avoid copy-on-write of pages if we are forked() */
|
||||
if (!server.vm_enabled || o->storage == REDIS_VM_MEMORY ||
|
||||
o->storage == REDIS_VM_SWAPPING) {
|
||||
swapped = 0;
|
||||
} else {
|
||||
o = vmPreviewObject(o);
|
||||
swapped = 1;
|
||||
}
|
||||
expiretime = getExpire(db,&key);
|
||||
|
||||
/* Save the key and associated value */
|
||||
@@ -530,6 +547,7 @@ int rewriteAppendOnlyFile(char *filename) {
|
||||
if (fwriteBulkObject(fp,&key) == 0) goto werr;
|
||||
if (fwriteBulkLongLong(fp,expiretime) == 0) goto werr;
|
||||
}
|
||||
if (swapped) decrRefCount(o);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
@@ -574,15 +592,13 @@ int rewriteAppendOnlyFileBackground(void) {
|
||||
long long start;
|
||||
|
||||
if (server.bgrewritechildpid != -1) return REDIS_ERR;
|
||||
if (server.ds_enabled != 0) {
|
||||
redisLog(REDIS_WARNING,"BGREWRITEAOF called with diskstore enabled: AOF is not supported when diskstore is enabled. Operation not performed.");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
if (server.vm_enabled) waitEmptyIOJobsQueue();
|
||||
start = ustime();
|
||||
if ((childpid = fork()) == 0) {
|
||||
char tmpfile[256];
|
||||
|
||||
/* Child */
|
||||
if (server.vm_enabled) vmReopenSwapFile();
|
||||
if (server.ipfd > 0) close(server.ipfd);
|
||||
if (server.sofd > 0) close(server.sofd);
|
||||
snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid());
|
||||
@@ -617,10 +633,9 @@ int rewriteAppendOnlyFileBackground(void) {
|
||||
void bgrewriteaofCommand(redisClient *c) {
|
||||
if (server.bgrewritechildpid != -1) {
|
||||
addReplyError(c,"Background append only file rewriting already in progress");
|
||||
} else if (server.bgsavechildpid != -1) {
|
||||
server.aofrewrite_scheduled = 1;
|
||||
addReplyStatus(c,"Background append only file rewriting scheduled");
|
||||
} else if (rewriteAppendOnlyFileBackground() == REDIS_OK) {
|
||||
return;
|
||||
}
|
||||
if (rewriteAppendOnlyFileBackground() == REDIS_OK) {
|
||||
addReplyStatus(c,"Background append only file rewriting started");
|
||||
} else {
|
||||
addReply(c,shared.err);
|
||||
@@ -634,24 +649,12 @@ void aofRemoveTempFile(pid_t childpid) {
|
||||
unlink(tmpfile);
|
||||
}
|
||||
|
||||
/* Update the server.appendonly_current_size filed explicitly using stat(2)
|
||||
* to check the size of the file. This is useful after a rewrite or after
|
||||
* a restart, normally the size is updated just adding the write length
|
||||
* to the current lenght, that is much faster. */
|
||||
void aofUpdateCurrentSize(void) {
|
||||
struct redis_stat sb;
|
||||
|
||||
if (redis_fstat(server.appendfd,&sb) == -1) {
|
||||
redisLog(REDIS_WARNING,"Unable to check the AOF length: %s",
|
||||
strerror(errno));
|
||||
} else {
|
||||
server.appendonly_current_size = sb.st_size;
|
||||
}
|
||||
}
|
||||
|
||||
/* A background append only file rewriting (BGREWRITEAOF) terminated its work.
|
||||
* Handle this. */
|
||||
void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
||||
void backgroundRewriteDoneHandler(int statloc) {
|
||||
int exitcode = WEXITSTATUS(statloc);
|
||||
int bysignal = WIFSIGNALED(statloc);
|
||||
|
||||
if (!bysignal && exitcode == 0) {
|
||||
int fd;
|
||||
char tmpfile[256];
|
||||
@@ -689,8 +692,6 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
||||
if (server.appendfsync != APPENDFSYNC_NO) aof_fsync(fd);
|
||||
server.appendseldb = -1; /* Make sure it will issue SELECT */
|
||||
redisLog(REDIS_NOTICE,"The new append only file was selected for future appends.");
|
||||
aofUpdateCurrentSize();
|
||||
server.auto_aofrewrite_base_size = server.appendonly_current_size;
|
||||
} else {
|
||||
/* If append only is disabled we just generate a dump in this
|
||||
* format. Why not? */
|
||||
@@ -701,7 +702,7 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
||||
} else {
|
||||
redisLog(REDIS_WARNING,
|
||||
"Background append only file rewriting terminated by signal %d",
|
||||
bysignal);
|
||||
WTERMSIG(statloc));
|
||||
}
|
||||
cleanup:
|
||||
sdsfree(server.bgrewritebuf);
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
char *ascii_logo =
|
||||
" _._ \n"
|
||||
" _.-``__ ''-._ \n"
|
||||
" _.-`` `. `_. ''-._ Redis %s (%s/%d) %s bit\n"
|
||||
" .-`` .-```. ```\\/ _.,_ ''-._ \n"
|
||||
" ( ' , .-` | `, ) Running in %s mode\n"
|
||||
" |`-._`-...-` __...-.``-._|'` _.-'| Port: %d\n"
|
||||
" | `-._ `._ / _.-' | PID: %ld\n"
|
||||
" `-._ `-._ `-./ _.-' _.-' \n"
|
||||
" |`-._`-._ `-.__.-' _.-'_.-'| \n"
|
||||
" | `-._`-._ _.-'_.-' | http://redis.io \n"
|
||||
" `-._ `-._`-.__.-'_.-' _.-' \n"
|
||||
" |`-._`-._ `-.__.-' _.-'_.-'| \n"
|
||||
" | `-._`-._ _.-'_.-' | \n"
|
||||
" `-._ `-._`-.__.-'_.-' _.-' \n"
|
||||
" `-._ `-.__.-' _.-' \n"
|
||||
" `-._ _.-' \n"
|
||||
" `-.__.-' \n\n";
|
||||
-1766
File diff suppressed because it is too large
Load Diff
+28
-46
@@ -30,6 +30,7 @@ void loadServerConfig(char *filename) {
|
||||
char buf[REDIS_CONFIGLINE_MAX+1], *err = NULL;
|
||||
int linenum = 0;
|
||||
sds line = NULL;
|
||||
int really_use_vm = 0;
|
||||
|
||||
if (filename[0] == '-' && filename[1] == '\0')
|
||||
fp = stdin;
|
||||
@@ -231,18 +232,6 @@ void loadServerConfig(char *filename) {
|
||||
err = "argument must be 'no', 'always' or 'everysec'";
|
||||
goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"auto-aof-rewrite-percentage") &&
|
||||
argc == 2)
|
||||
{
|
||||
server.auto_aofrewrite_perc = atoi(argv[1]);
|
||||
if (server.auto_aofrewrite_perc < 0) {
|
||||
err = "Invalid negative percentage for AOF auto rewrite";
|
||||
goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"auto-aof-rewrite-min-size") &&
|
||||
argc == 2)
|
||||
{
|
||||
server.auto_aofrewrite_min_size = memtoll(argv[1],NULL);
|
||||
} else if (!strcasecmp(argv[0],"requirepass") && argc == 2) {
|
||||
server.requirepass = zstrdup(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"pidfile") && argc == 2) {
|
||||
@@ -251,18 +240,25 @@ void loadServerConfig(char *filename) {
|
||||
} else if (!strcasecmp(argv[0],"dbfilename") && argc == 2) {
|
||||
zfree(server.dbfilename);
|
||||
server.dbfilename = zstrdup(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"diskstore-enabled") && argc == 2) {
|
||||
if ((server.ds_enabled = yesnotoi(argv[1])) == -1) {
|
||||
} else if (!strcasecmp(argv[0],"vm-enabled") && argc == 2) {
|
||||
if ((server.vm_enabled = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"diskstore-path") && argc == 2) {
|
||||
sdsfree(server.ds_path);
|
||||
server.ds_path = sdsnew(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"cache-max-memory") && argc == 2) {
|
||||
server.cache_max_memory = memtoll(argv[1],NULL);
|
||||
} else if (!strcasecmp(argv[0],"cache-flush-delay") && argc == 2) {
|
||||
server.cache_flush_delay = atoi(argv[1]);
|
||||
if (server.cache_flush_delay < 0) server.cache_flush_delay = 0;
|
||||
} else if (!strcasecmp(argv[0],"really-use-vm") && argc == 2) {
|
||||
if ((really_use_vm = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"vm-swap-file") && argc == 2) {
|
||||
zfree(server.vm_swap_file);
|
||||
server.vm_swap_file = zstrdup(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"vm-max-memory") && argc == 2) {
|
||||
server.vm_max_memory = memtoll(argv[1],NULL);
|
||||
} else if (!strcasecmp(argv[0],"vm-page-size") && argc == 2) {
|
||||
server.vm_page_size = memtoll(argv[1], NULL);
|
||||
} else if (!strcasecmp(argv[0],"vm-pages") && argc == 2) {
|
||||
server.vm_pages = memtoll(argv[1], NULL);
|
||||
} else if (!strcasecmp(argv[0],"vm-max-threads") && argc == 2) {
|
||||
server.vm_max_threads = strtoll(argv[1], NULL, 10);
|
||||
} else if (!strcasecmp(argv[0],"hash-max-zipmap-entries") && argc == 2) {
|
||||
server.hash_max_zipmap_entries = memtoll(argv[1], NULL);
|
||||
} else if (!strcasecmp(argv[0],"hash-max-zipmap-value") && argc == 2) {
|
||||
@@ -301,13 +297,6 @@ void loadServerConfig(char *filename) {
|
||||
err = "Target command name already exists"; goto loaderr;
|
||||
}
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"cluster-enabled") && argc == 2) {
|
||||
if ((server.cluster_enabled = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"cluster-config-file") && argc == 2) {
|
||||
zfree(server.cluster.configfile);
|
||||
server.cluster.configfile = zstrdup(argv[1]);
|
||||
} else {
|
||||
err = "Bad directive or wrong number of arguments"; goto loaderr;
|
||||
}
|
||||
@@ -317,6 +306,7 @@ void loadServerConfig(char *filename) {
|
||||
sdsfree(line);
|
||||
}
|
||||
if (fp != stdin) fclose(fp);
|
||||
if (server.vm_enabled && !really_use_vm) goto vm_warning;
|
||||
return;
|
||||
|
||||
loaderr:
|
||||
@@ -325,6 +315,15 @@ loaderr:
|
||||
fprintf(stderr, ">>> '%s'\n", line);
|
||||
fprintf(stderr, "%s\n", err);
|
||||
exit(1);
|
||||
|
||||
vm_warning:
|
||||
fprintf(stderr, "\nARE YOU SURE YOU WANT TO USE VM?\n\n");
|
||||
fprintf(stderr, "Redis Virtual Memory is going to be deprecated soon,\n");
|
||||
fprintf(stderr, "we think you should NOT use it, but use Redis only if\n");
|
||||
fprintf(stderr, "your data is suitable for an in-memory database.\n");
|
||||
fprintf(stderr, "If you *really* want VM add this in the config file:\n");
|
||||
fprintf(stderr, "\n really-use-vm yes\n\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
@@ -407,12 +406,6 @@ void configSetCommand(redisClient *c) {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"auto-aof-rewrite-percentage")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
|
||||
server.auto_aofrewrite_perc = ll;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"auto-aof-rewrite-min-size")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
|
||||
server.auto_aofrewrite_min_size = ll;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"save")) {
|
||||
int vlen, j;
|
||||
sds *v = sdssplitlen(o->ptr,sdslen(o->ptr)," ",1,&vlen);
|
||||
@@ -599,16 +592,6 @@ void configGetCommand(redisClient *c) {
|
||||
sdsfree(buf);
|
||||
matches++;
|
||||
}
|
||||
if (stringmatch(pattern,"auto-aof-rewrite-percentage",0)) {
|
||||
addReplyBulkCString(c,"auto-aof-rewrite-percentage");
|
||||
addReplyBulkLongLong(c,server.auto_aofrewrite_perc);
|
||||
matches++;
|
||||
}
|
||||
if (stringmatch(pattern,"auto-aof-rewrite-min-size",0)) {
|
||||
addReplyBulkCString(c,"auto-aof-rewrite-min-size");
|
||||
addReplyBulkLongLong(c,server.auto_aofrewrite_min_size);
|
||||
matches++;
|
||||
}
|
||||
if (stringmatch(pattern,"slave-serve-stale-data",0)) {
|
||||
addReplyBulkCString(c,"slave-serve-stale-data");
|
||||
addReplyBulkCString(c,server.repl_serve_stale_data ? "yes" : "no");
|
||||
@@ -666,7 +649,6 @@ void configCommand(redisClient *c) {
|
||||
server.stat_numcommands = 0;
|
||||
server.stat_numconnections = 0;
|
||||
server.stat_expiredkeys = 0;
|
||||
resetCommandTableStats();
|
||||
addReply(c,shared.ok);
|
||||
} else {
|
||||
addReplyError(c,
|
||||
|
||||
+6
-57
@@ -5,7 +5,7 @@
|
||||
#include <AvailabilityMacros.h>
|
||||
#endif
|
||||
|
||||
/* Define redis_fstat to fstat or fstat64() */
|
||||
/* define redis_fstat to fstat or fstat64() */
|
||||
#if defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_6)
|
||||
#define redis_fstat fstat64
|
||||
#define redis_stat stat64
|
||||
@@ -14,22 +14,22 @@
|
||||
#define redis_stat stat
|
||||
#endif
|
||||
|
||||
/* Test for proc filesystem */
|
||||
/* test for proc filesystem */
|
||||
#ifdef __linux__
|
||||
#define HAVE_PROCFS 1
|
||||
#endif
|
||||
|
||||
/* Test for task_info() */
|
||||
/* test for task_info() */
|
||||
#if defined(__APPLE__)
|
||||
#define HAVE_TASKINFO 1
|
||||
#endif
|
||||
|
||||
/* Test for backtrace() */
|
||||
/* test for backtrace() */
|
||||
#if defined(__APPLE__) || defined(__linux__)
|
||||
#define HAVE_BACKTRACE 1
|
||||
#endif
|
||||
|
||||
/* Test for polling API */
|
||||
/* test for polling API */
|
||||
#ifdef __linux__
|
||||
#define HAVE_EPOLL 1
|
||||
#endif
|
||||
@@ -38,62 +38,11 @@
|
||||
#define HAVE_KQUEUE 1
|
||||
#endif
|
||||
|
||||
/* Define aof_fsync to fdatasync() in Linux and fsync() for all the rest */
|
||||
/* define aof_fsync to fdatasync() in Linux and fsync() for all the rest */
|
||||
#ifdef __linux__
|
||||
#define aof_fsync fdatasync
|
||||
#else
|
||||
#define aof_fsync fsync
|
||||
#endif
|
||||
|
||||
/* Byte ordering detection */
|
||||
#include <sys/types.h> /* This will likely define BYTE_ORDER */
|
||||
|
||||
#ifndef BYTE_ORDER
|
||||
#if (BSD >= 199103)
|
||||
# include <machine/endian.h>
|
||||
#else
|
||||
#if defined(linux) || defined(__linux__)
|
||||
# include <endian.h>
|
||||
#else
|
||||
#define LITTLE_ENDIAN 1234 /* least-significant byte first (vax, pc) */
|
||||
#define BIG_ENDIAN 4321 /* most-significant byte first (IBM, net) */
|
||||
#define PDP_ENDIAN 3412 /* LSB first in word, MSW first in long (pdp)*/
|
||||
|
||||
#if defined(vax) || defined(ns32000) || defined(sun386) || defined(__i386__) || \
|
||||
defined(MIPSEL) || defined(_MIPSEL) || defined(BIT_ZERO_ON_RIGHT) || \
|
||||
defined(__alpha__) || defined(__alpha)
|
||||
#define BYTE_ORDER LITTLE_ENDIAN
|
||||
#endif
|
||||
|
||||
#if defined(sel) || defined(pyr) || defined(mc68000) || defined(sparc) || \
|
||||
defined(is68k) || defined(tahoe) || defined(ibm032) || defined(ibm370) || \
|
||||
defined(MIPSEB) || defined(_MIPSEB) || defined(_IBMR2) || defined(DGUX) ||\
|
||||
defined(apollo) || defined(__convex__) || defined(_CRAY) || \
|
||||
defined(__hppa) || defined(__hp9000) || \
|
||||
defined(__hp9000s300) || defined(__hp9000s700) || \
|
||||
defined (BIT_ZERO_ON_LEFT) || defined(m68k) || defined(__sparc)
|
||||
#define BYTE_ORDER BIG_ENDIAN
|
||||
#endif
|
||||
#endif /* linux */
|
||||
#endif /* BSD */
|
||||
#endif /* BYTE_ORDER */
|
||||
|
||||
#if defined(__BYTE_ORDER) && !defined(BYTE_ORDER)
|
||||
#if (__BYTE_ORDER == __LITTLE_ENDIAN)
|
||||
#define BYTE_ORDER LITTLE_ENDIAN
|
||||
#else
|
||||
#define BYTE_ORDER BIG_ENDIAN
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if !defined(BYTE_ORDER) || \
|
||||
(BYTE_ORDER != BIG_ENDIAN && BYTE_ORDER != LITTLE_ENDIAN)
|
||||
/* you must determine what the correct bit order is for
|
||||
* your compiler - the next line is an intentional error
|
||||
* which will force your compiles to bomb until you fix
|
||||
* the above macros.
|
||||
*/
|
||||
#error "Undefined or invalid BYTE_ORDER"
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
-74
@@ -1,74 +0,0 @@
|
||||
#include "redis.h"
|
||||
|
||||
/*
|
||||
* Copyright 2001-2010 Georges Menie (www.menie.org)
|
||||
* Copyright 2010 Salvatore Sanfilippo (adapted to Redis coding style)
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the University of California, Berkeley nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/* CRC16 implementation acording to CCITT standards */
|
||||
|
||||
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 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;
|
||||
}
|
||||
@@ -2,42 +2,10 @@
|
||||
|
||||
#include <signal.h>
|
||||
|
||||
void SlotToKeyAdd(robj *key);
|
||||
void SlotToKeyDel(robj *key);
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* C-level DB API
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
/* Important notes on lookup and disk store.
|
||||
*
|
||||
* When disk store is enabled on lookup we can have different cases.
|
||||
*
|
||||
* a) The key is in memory:
|
||||
* - If the key is not in IO_SAVEINPROG state we can access it.
|
||||
* As if it's just IO_SAVE this means we have the key in the IO queue
|
||||
* but can't be accessed by the IO thread (it requires to be
|
||||
* translated into an IO Job by the cache cron function.)
|
||||
* - If the key is in IO_SAVEINPROG we can't touch the key and have
|
||||
* to blocking wait completion of operations.
|
||||
* b) The key is not in memory:
|
||||
* - If it's marked as non existing on disk as well (negative cache)
|
||||
* we don't need to perform the disk access.
|
||||
* - if the key MAY EXIST, but is not in memory, and it is marked as IO_SAVE
|
||||
* then the key can only be a deleted one. As IO_SAVE keys are never
|
||||
* evicted (dirty state), so the only possibility is that key was deleted.
|
||||
* - if the key MAY EXIST we need to blocking load it.
|
||||
* We check that the key is not in IO_SAVEINPROG state before accessing
|
||||
* the disk object. If it is in this state, we wait.
|
||||
*/
|
||||
|
||||
void lookupWaitBusyKey(redisDb *db, robj *key) {
|
||||
/* FIXME: wait just for this key, not everything */
|
||||
waitEmptyIOJobsQueue();
|
||||
processAllPendingIOJobs();
|
||||
redisAssert((cacheScheduleIOGetFlags(db,key) & REDIS_IO_SAVEINPROG) == 0);
|
||||
}
|
||||
|
||||
robj *lookupKey(redisDb *db, robj *key) {
|
||||
dictEntry *de = dictFind(db->dict,key->ptr);
|
||||
if (de) {
|
||||
@@ -49,51 +17,29 @@ robj *lookupKey(redisDb *db, robj *key) {
|
||||
if (server.bgsavechildpid == -1 && server.bgrewritechildpid == -1)
|
||||
val->lru = server.lruclock;
|
||||
|
||||
if (server.ds_enabled &&
|
||||
cacheScheduleIOGetFlags(db,key) & REDIS_IO_SAVEINPROG)
|
||||
{
|
||||
/* Need to wait for the key to get unbusy */
|
||||
redisLog(REDIS_DEBUG,"Lookup found a key in SAVEINPROG state. Waiting. (Key was in the cache)");
|
||||
lookupWaitBusyKey(db,key);
|
||||
if (server.vm_enabled) {
|
||||
if (val->storage == REDIS_VM_MEMORY ||
|
||||
val->storage == REDIS_VM_SWAPPING)
|
||||
{
|
||||
/* If we were swapping the object out, cancel the operation */
|
||||
if (val->storage == REDIS_VM_SWAPPING)
|
||||
vmCancelThreadedIOJob(val);
|
||||
} else {
|
||||
int notify = (val->storage == REDIS_VM_LOADING);
|
||||
|
||||
/* Our value was swapped on disk. Bring it at home. */
|
||||
redisAssert(val->type == REDIS_VMPOINTER);
|
||||
val = vmLoadObject(val);
|
||||
dictGetEntryVal(de) = val;
|
||||
|
||||
/* Clients blocked by the VM subsystem may be waiting for
|
||||
* this key... */
|
||||
if (notify) handleClientsBlockedOnSwappedKey(db,key);
|
||||
}
|
||||
}
|
||||
server.stat_keyspace_hits++;
|
||||
return val;
|
||||
} else {
|
||||
time_t expire;
|
||||
robj *val;
|
||||
|
||||
/* Key not found in the in memory hash table, but if disk store is
|
||||
* enabled we may have this key on disk. If so load it in memory
|
||||
* in a blocking way. */
|
||||
if (server.ds_enabled && cacheKeyMayExist(db,key)) {
|
||||
long flags = cacheScheduleIOGetFlags(db,key);
|
||||
|
||||
/* They key is not in cache, but it has a SAVE op in queue?
|
||||
* The only possibility is that the key was deleted, since
|
||||
* dirty keys are not evicted. */
|
||||
if (flags & REDIS_IO_SAVE) {
|
||||
server.stat_keyspace_misses++;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* At this point we need to blocking load the key in memory.
|
||||
* The first thing we do is waiting here if the key is busy. */
|
||||
if (flags & REDIS_IO_SAVEINPROG) {
|
||||
redisLog(REDIS_DEBUG,"Lookup found a key in SAVEINPROG state. Waiting (while force loading).");
|
||||
lookupWaitBusyKey(db,key);
|
||||
}
|
||||
|
||||
redisLog(REDIS_DEBUG,"Force loading key %s via lookup", key->ptr);
|
||||
val = dsGet(db,key,&expire);
|
||||
if (val) {
|
||||
dbAdd(db,key,val);
|
||||
if (expire != -1) setExpire(db,key,expire);
|
||||
server.stat_keyspace_hits++;
|
||||
return val;
|
||||
} else {
|
||||
cacheSetKeyDoesNotExist(db,key);
|
||||
}
|
||||
}
|
||||
server.stat_keyspace_misses++;
|
||||
return NULL;
|
||||
}
|
||||
@@ -121,47 +67,34 @@ robj *lookupKeyWriteOrReply(redisClient *c, robj *key, robj *reply) {
|
||||
return o;
|
||||
}
|
||||
|
||||
/* Add the key to the DB. It's up to the caller to increment the reference
|
||||
* counte of the value if needed.
|
||||
*
|
||||
* The program is aborted if the key already exists. */
|
||||
void dbAdd(redisDb *db, robj *key, robj *val) {
|
||||
sds copy = sdsdup(key->ptr);
|
||||
int retval = dictAdd(db->dict, copy, val);
|
||||
|
||||
redisAssert(retval == REDIS_OK);
|
||||
if (server.ds_enabled) cacheSetKeyMayExist(db,key);
|
||||
if (server.cluster_enabled) SlotToKeyAdd(key);
|
||||
}
|
||||
|
||||
/* Overwrite an existing key with a new value. Incrementing the reference
|
||||
* count of the new value is up to the caller.
|
||||
* This function does not modify the expire time of the existing key.
|
||||
*
|
||||
* The program is aborted if the key was not already present. */
|
||||
void dbOverwrite(redisDb *db, robj *key, robj *val) {
|
||||
struct dictEntry *de = dictFind(db->dict,key->ptr);
|
||||
|
||||
redisAssert(de != NULL);
|
||||
dictReplace(db->dict, key->ptr, val);
|
||||
if (server.ds_enabled) cacheSetKeyMayExist(db,key);
|
||||
/* Add the key to the DB. If the key already exists REDIS_ERR is returned,
|
||||
* otherwise REDIS_OK is returned, and the caller should increment the
|
||||
* refcount of 'val'. */
|
||||
int dbAdd(redisDb *db, robj *key, robj *val) {
|
||||
/* Perform a lookup before adding the key, as we need to copy the
|
||||
* key value. */
|
||||
if (dictFind(db->dict, key->ptr) != NULL) {
|
||||
return REDIS_ERR;
|
||||
} else {
|
||||
sds copy = sdsdup(key->ptr);
|
||||
dictAdd(db->dict, copy, val);
|
||||
return REDIS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
/* High level Set operation. This function can be used in order to set
|
||||
* a key, whatever it was existing or not, to a new object.
|
||||
/* If the key does not exist, this is just like dbAdd(). Otherwise
|
||||
* the value associated to the key is replaced with the new one.
|
||||
*
|
||||
* 1) The ref count of the value object is incremented.
|
||||
* 2) clients WATCHing for the destination key notified.
|
||||
* 3) The expire time of the key is reset (the key is made persistent). */
|
||||
void setKey(redisDb *db, robj *key, robj *val) {
|
||||
if (lookupKeyWrite(db,key) == NULL) {
|
||||
dbAdd(db,key,val);
|
||||
* On update (key already existed) 0 is returned. Otherwise 1. */
|
||||
int dbReplace(redisDb *db, robj *key, robj *val) {
|
||||
if (dictFind(db->dict,key->ptr) == NULL) {
|
||||
sds copy = sdsdup(key->ptr);
|
||||
dictAdd(db->dict, copy, val);
|
||||
return 1;
|
||||
} else {
|
||||
dbOverwrite(db,key,val);
|
||||
dictReplace(db->dict, key->ptr, val);
|
||||
return 0;
|
||||
}
|
||||
incrRefCount(val);
|
||||
removeExpire(db,key);
|
||||
touchWatchedKey(db,key);
|
||||
}
|
||||
|
||||
int dbExists(redisDb *db, robj *key) {
|
||||
@@ -196,27 +129,18 @@ robj *dbRandomKey(redisDb *db) {
|
||||
|
||||
/* Delete a key, value, and associated expiration entry if any, from the DB */
|
||||
int dbDelete(redisDb *db, robj *key) {
|
||||
/* If diskstore is enabled make sure to awake waiting clients for this key
|
||||
* as it is not really useful to wait for a key already deleted to be
|
||||
* loaded from disk. */
|
||||
if (server.ds_enabled) {
|
||||
handleClientsBlockedOnSwappedKey(db,key);
|
||||
cacheSetKeyDoesNotExist(db,key);
|
||||
}
|
||||
|
||||
/* If VM is enabled make sure to awake waiting clients for this key:
|
||||
* deleting the key will kill the I/O thread bringing the key from swap
|
||||
* to memory, so the client will never be notified and unblocked if we
|
||||
* don't do it now. */
|
||||
if (server.vm_enabled) handleClientsBlockedOnSwappedKey(db,key);
|
||||
/* Deleting an entry from the expires dict will not free the sds of
|
||||
* the key, because it is shared with the main dictionary. */
|
||||
if (dictSize(db->expires) > 0) dictDelete(db->expires,key->ptr);
|
||||
if (dictDelete(db->dict,key->ptr) == DICT_OK) {
|
||||
if (server.cluster_enabled) SlotToKeyDel(key);
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
return dictDelete(db->dict,key->ptr) == DICT_OK;
|
||||
}
|
||||
|
||||
/* Empty the whole database.
|
||||
* If diskstore is enabled this function will just flush the in-memory cache. */
|
||||
/* Empty the whole database */
|
||||
long long emptyDb() {
|
||||
int j;
|
||||
long long removed = 0;
|
||||
@@ -225,7 +149,6 @@ long long emptyDb() {
|
||||
removed += dictSize(server.db[j].dict);
|
||||
dictEmpty(server.db[j].dict);
|
||||
dictEmpty(server.db[j].expires);
|
||||
if (server.ds_enabled) dictEmpty(server.db[j].io_negcache);
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
@@ -237,50 +160,27 @@ int selectDb(redisClient *c, int id) {
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Hooks for key space changes.
|
||||
*
|
||||
* Every time a key in the database is modified the function
|
||||
* signalModifiedKey() is called.
|
||||
*
|
||||
* Every time a DB is flushed the function signalFlushDb() is called.
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
void signalModifiedKey(redisDb *db, robj *key) {
|
||||
touchWatchedKey(db,key);
|
||||
if (server.ds_enabled)
|
||||
cacheScheduleIO(db,key,REDIS_IO_SAVE);
|
||||
}
|
||||
|
||||
void signalFlushedDb(int dbid) {
|
||||
touchWatchedKeysOnFlush(dbid);
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Type agnostic commands operating on the key space
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
void flushdbCommand(redisClient *c) {
|
||||
server.dirty += dictSize(c->db->dict);
|
||||
signalFlushedDb(c->db->id);
|
||||
touchWatchedKeysOnFlush(c->db->id);
|
||||
dictEmpty(c->db->dict);
|
||||
dictEmpty(c->db->expires);
|
||||
if (server.ds_enabled) dsFlushDb(c->db->id);
|
||||
addReply(c,shared.ok);
|
||||
}
|
||||
|
||||
void flushallCommand(redisClient *c) {
|
||||
signalFlushedDb(-1);
|
||||
touchWatchedKeysOnFlush(-1);
|
||||
server.dirty += emptyDb();
|
||||
addReply(c,shared.ok);
|
||||
if (server.bgsavechildpid != -1) {
|
||||
kill(server.bgsavechildpid,SIGKILL);
|
||||
rdbRemoveTempFile(server.bgsavechildpid);
|
||||
}
|
||||
if (server.ds_enabled)
|
||||
dsFlushDb(-1);
|
||||
else
|
||||
rdbSave(server.dbfilename);
|
||||
rdbSave(server.dbfilename);
|
||||
server.dirty++;
|
||||
}
|
||||
|
||||
@@ -288,22 +188,10 @@ void delCommand(redisClient *c) {
|
||||
int deleted = 0, j;
|
||||
|
||||
for (j = 1; j < c->argc; j++) {
|
||||
if (server.ds_enabled) {
|
||||
lookupKeyRead(c->db,c->argv[j]);
|
||||
/* FIXME: this can be optimized a lot, no real need to load
|
||||
* a possibly huge value. */
|
||||
}
|
||||
if (dbDelete(c->db,c->argv[j])) {
|
||||
signalModifiedKey(c->db,c->argv[j]);
|
||||
touchWatchedKey(c->db,c->argv[j]);
|
||||
server.dirty++;
|
||||
deleted++;
|
||||
} else if (server.ds_enabled) {
|
||||
if (cacheKeyMayExist(c->db,c->argv[j]) &&
|
||||
dsExists(c->db,c->argv[j]))
|
||||
{
|
||||
cacheScheduleIO(c->db,c->argv[j],REDIS_IO_SAVE);
|
||||
deleted = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
addReplyLongLong(c,deleted);
|
||||
@@ -321,10 +209,6 @@ void existsCommand(redisClient *c) {
|
||||
void selectCommand(redisClient *c) {
|
||||
int id = atoi(c->argv[1]->ptr);
|
||||
|
||||
if (server.cluster_enabled && id != 0) {
|
||||
addReplyError(c,"SELECT is not allowed in cluster mode");
|
||||
return;
|
||||
}
|
||||
if (selectDb(c,id) == REDIS_ERR) {
|
||||
addReplyError(c,"invalid DB index");
|
||||
} else {
|
||||
@@ -399,6 +283,30 @@ void typeCommand(redisClient *c) {
|
||||
addReplyStatus(c,type);
|
||||
}
|
||||
|
||||
void saveCommand(redisClient *c) {
|
||||
if (server.bgsavechildpid != -1) {
|
||||
addReplyError(c,"Background save already in progress");
|
||||
return;
|
||||
}
|
||||
if (rdbSave(server.dbfilename) == REDIS_OK) {
|
||||
addReply(c,shared.ok);
|
||||
} else {
|
||||
addReply(c,shared.err);
|
||||
}
|
||||
}
|
||||
|
||||
void bgsaveCommand(redisClient *c) {
|
||||
if (server.bgsavechildpid != -1) {
|
||||
addReplyError(c,"Background save already in progress");
|
||||
return;
|
||||
}
|
||||
if (rdbSaveBackground(server.dbfilename) == REDIS_OK) {
|
||||
addReplyStatus(c,"Background saving started");
|
||||
} else {
|
||||
addReply(c,shared.err);
|
||||
}
|
||||
}
|
||||
|
||||
void shutdownCommand(redisClient *c) {
|
||||
if (prepareForShutdown() == REDIS_OK)
|
||||
exit(0);
|
||||
@@ -418,19 +326,17 @@ void renameGenericCommand(redisClient *c, int nx) {
|
||||
return;
|
||||
|
||||
incrRefCount(o);
|
||||
if (lookupKeyWrite(c->db,c->argv[2]) != NULL) {
|
||||
if (dbAdd(c->db,c->argv[2],o) == REDIS_ERR) {
|
||||
if (nx) {
|
||||
decrRefCount(o);
|
||||
addReply(c,shared.czero);
|
||||
return;
|
||||
}
|
||||
dbOverwrite(c->db,c->argv[2],o);
|
||||
} else {
|
||||
dbAdd(c->db,c->argv[2],o);
|
||||
dbReplace(c->db,c->argv[2],o);
|
||||
}
|
||||
dbDelete(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[2]);
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
touchWatchedKey(c->db,c->argv[2]);
|
||||
server.dirty++;
|
||||
addReply(c,nx ? shared.cone : shared.ok);
|
||||
}
|
||||
@@ -448,11 +354,6 @@ void moveCommand(redisClient *c) {
|
||||
redisDb *src, *dst;
|
||||
int srcid;
|
||||
|
||||
if (server.cluster_enabled) {
|
||||
addReplyError(c,"MOVE is not allowed in cluster mode");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Obtain source and target DB pointers */
|
||||
src = c->db;
|
||||
srcid = c->db->id;
|
||||
@@ -477,12 +378,11 @@ void moveCommand(redisClient *c) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Return zero if the key already exists in the target DB */
|
||||
if (lookupKeyWrite(dst,c->argv[1]) != NULL) {
|
||||
/* Try to add the element to the target DB */
|
||||
if (dbAdd(dst,c->argv[1],o) == REDIS_ERR) {
|
||||
addReply(c,shared.czero);
|
||||
return;
|
||||
}
|
||||
dbAdd(dst,c->argv[1],o);
|
||||
incrRefCount(o);
|
||||
|
||||
/* OK! key moved, free the entry in the source DB */
|
||||
@@ -595,13 +495,13 @@ void expireGenericCommand(redisClient *c, robj *key, robj *param, long offset) {
|
||||
if (seconds <= 0) {
|
||||
if (dbDelete(c->db,key)) server.dirty++;
|
||||
addReply(c, shared.cone);
|
||||
signalModifiedKey(c->db,key);
|
||||
touchWatchedKey(c->db,key);
|
||||
return;
|
||||
} else {
|
||||
time_t when = time(NULL)+seconds;
|
||||
setExpire(c->db,key,when);
|
||||
addReply(c,shared.cone);
|
||||
signalModifiedKey(c->db,key);
|
||||
touchWatchedKey(c->db,key);
|
||||
server.dirty++;
|
||||
return;
|
||||
}
|
||||
@@ -618,7 +518,6 @@ void expireatCommand(redisClient *c) {
|
||||
void ttlCommand(redisClient *c) {
|
||||
time_t expire, ttl = -1;
|
||||
|
||||
if (server.ds_enabled) lookupKeyRead(c->db,c->argv[1]);
|
||||
expire = getExpire(c->db,c->argv[1]);
|
||||
if (expire != -1) {
|
||||
ttl = (expire-time(NULL));
|
||||
@@ -642,108 +541,3 @@ void persistCommand(redisClient *c) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* API to get key arguments from commands
|
||||
* ---------------------------------------------------------------------------*/
|
||||
|
||||
int *getKeysUsingCommandTable(struct redisCommand *cmd,robj **argv, int argc, int *numkeys) {
|
||||
int j, i = 0, last, *keys;
|
||||
REDIS_NOTUSED(argv);
|
||||
|
||||
if (cmd->firstkey == 0) {
|
||||
*numkeys = 0;
|
||||
return NULL;
|
||||
}
|
||||
last = cmd->lastkey;
|
||||
if (last < 0) last = argc+last;
|
||||
keys = zmalloc(sizeof(int)*((last - cmd->firstkey)+1));
|
||||
for (j = cmd->firstkey; j <= last; j += cmd->keystep) {
|
||||
redisAssert(j < argc);
|
||||
keys[i++] = j;
|
||||
}
|
||||
*numkeys = i;
|
||||
return keys;
|
||||
}
|
||||
|
||||
int *getKeysFromCommand(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags) {
|
||||
if (cmd->getkeys_proc) {
|
||||
return cmd->getkeys_proc(cmd,argv,argc,numkeys,flags);
|
||||
} else {
|
||||
return getKeysUsingCommandTable(cmd,argv,argc,numkeys);
|
||||
}
|
||||
}
|
||||
|
||||
void getKeysFreeResult(int *result) {
|
||||
zfree(result);
|
||||
}
|
||||
|
||||
int *noPreloadGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags) {
|
||||
if (flags & REDIS_GETKEYS_PRELOAD) {
|
||||
*numkeys = 0;
|
||||
return NULL;
|
||||
} else {
|
||||
return getKeysUsingCommandTable(cmd,argv,argc,numkeys);
|
||||
}
|
||||
}
|
||||
|
||||
int *renameGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags) {
|
||||
if (flags & REDIS_GETKEYS_PRELOAD) {
|
||||
int *keys = zmalloc(sizeof(int));
|
||||
*numkeys = 1;
|
||||
keys[0] = 1;
|
||||
return keys;
|
||||
} else {
|
||||
return getKeysUsingCommandTable(cmd,argv,argc,numkeys);
|
||||
}
|
||||
}
|
||||
|
||||
int *zunionInterGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags) {
|
||||
int i, num, *keys;
|
||||
REDIS_NOTUSED(cmd);
|
||||
REDIS_NOTUSED(flags);
|
||||
|
||||
num = atoi(argv[2]->ptr);
|
||||
/* Sanity check. Don't return any key if the command is going to
|
||||
* reply with syntax error. */
|
||||
if (num > (argc-3)) {
|
||||
*numkeys = 0;
|
||||
return NULL;
|
||||
}
|
||||
keys = zmalloc(sizeof(int)*num);
|
||||
for (i = 0; i < num; i++) keys[i] = 3+i;
|
||||
*numkeys = num;
|
||||
return keys;
|
||||
}
|
||||
|
||||
/* Slot to Key API. This is used by Redis Cluster in order to obtain in
|
||||
* a fast way a key that belongs to a specified hash slot. This is useful
|
||||
* while rehashing the cluster. */
|
||||
void SlotToKeyAdd(robj *key) {
|
||||
unsigned int hashslot = keyHashSlot(key->ptr,sdslen(key->ptr));
|
||||
|
||||
zslInsert(server.cluster.slots_to_keys,hashslot,key);
|
||||
incrRefCount(key);
|
||||
}
|
||||
|
||||
void SlotToKeyDel(robj *key) {
|
||||
unsigned int hashslot = keyHashSlot(key->ptr,sdslen(key->ptr));
|
||||
|
||||
zslDelete(server.cluster.slots_to_keys,hashslot,key);
|
||||
}
|
||||
|
||||
unsigned int GetKeysInSlot(unsigned int hashslot, robj **keys, unsigned int count) {
|
||||
zskiplistNode *n;
|
||||
zrangespec range;
|
||||
int j = 0;
|
||||
|
||||
range.min = range.max = hashslot;
|
||||
range.minex = range.maxex = 0;
|
||||
|
||||
n = zslFirstInRange(server.cluster.slots_to_keys, range);
|
||||
while(n && n->score == hashslot && count--) {
|
||||
keys[j++] = n->obj;
|
||||
n = n->level[0].forward;
|
||||
}
|
||||
return j;
|
||||
}
|
||||
|
||||
+56
-35
@@ -80,7 +80,7 @@ void computeDatasetDigest(unsigned char *final) {
|
||||
redisDb *db = server.db+j;
|
||||
|
||||
if (dictSize(db->dict) == 0) continue;
|
||||
di = dictGetIterator(db->dict);
|
||||
di = dictGetSafeIterator(db->dict);
|
||||
|
||||
/* hash the DB id, so the same dataset moved in a different
|
||||
* DB will lead to a different digest */
|
||||
@@ -100,7 +100,12 @@ void computeDatasetDigest(unsigned char *final) {
|
||||
mixDigest(digest,key,sdslen(key));
|
||||
|
||||
/* Make sure the key is loaded if VM is active */
|
||||
o = dictGetEntryVal(de);
|
||||
o = lookupKeyRead(db,keyobj);
|
||||
if (o == NULL) {
|
||||
/* Key expired on lookup? Try the next one. */
|
||||
decrRefCount(keyobj);
|
||||
continue;
|
||||
}
|
||||
|
||||
aux = htonl(o->type);
|
||||
mixDigest(digest,&aux,sizeof(aux));
|
||||
@@ -212,26 +217,7 @@ void computeDatasetDigest(unsigned char *final) {
|
||||
void debugCommand(redisClient *c) {
|
||||
if (!strcasecmp(c->argv[1]->ptr,"segfault")) {
|
||||
*((char*)-1) = 'x';
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"flushcache")) {
|
||||
if (!server.ds_enabled) {
|
||||
addReplyError(c, "DEBUG FLUSHCACHE called with diskstore off.");
|
||||
return;
|
||||
} else if (server.bgsavethread != (pthread_t) -1) {
|
||||
addReplyError(c, "Can't flush cache while BGSAVE is in progress.");
|
||||
return;
|
||||
} else {
|
||||
/* To flush the whole cache we need to wait for everything to
|
||||
* be flushed on disk... */
|
||||
cacheForcePointInTime();
|
||||
emptyDb();
|
||||
addReply(c,shared.ok);
|
||||
return;
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"reload")) {
|
||||
if (server.ds_enabled) {
|
||||
addReply(c,shared.ok);
|
||||
return;
|
||||
}
|
||||
if (rdbSave(server.dbfilename) != REDIS_OK) {
|
||||
addReply(c,shared.err);
|
||||
return;
|
||||
@@ -252,25 +238,62 @@ void debugCommand(redisClient *c) {
|
||||
redisLog(REDIS_WARNING,"Append Only File loaded by DEBUG LOADAOF");
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"object") && c->argc == 3) {
|
||||
dictEntry *de;
|
||||
dictEntry *de = dictFind(c->db->dict,c->argv[2]->ptr);
|
||||
robj *val;
|
||||
char *strenc;
|
||||
|
||||
if (server.ds_enabled) lookupKeyRead(c->db,c->argv[2]);
|
||||
if ((de = dictFind(c->db->dict,c->argv[2]->ptr)) == NULL) {
|
||||
if (!de) {
|
||||
addReply(c,shared.nokeyerr);
|
||||
return;
|
||||
}
|
||||
val = dictGetEntryVal(de);
|
||||
strenc = strEncoding(val->encoding);
|
||||
if (!server.vm_enabled || (val->storage == REDIS_VM_MEMORY ||
|
||||
val->storage == REDIS_VM_SWAPPING)) {
|
||||
char *strenc;
|
||||
|
||||
addReplyStatusFormat(c,
|
||||
"Value at:%p refcount:%d "
|
||||
"encoding:%s serializedlength:%lld "
|
||||
"lru:%d lru_seconds_idle:%lu",
|
||||
(void*)val, val->refcount,
|
||||
strenc, (long long) rdbSavedObjectLen(val),
|
||||
val->lru, estimateObjectIdleTime(val));
|
||||
strenc = strEncoding(val->encoding);
|
||||
addReplyStatusFormat(c,
|
||||
"Value at:%p refcount:%d "
|
||||
"encoding:%s serializedlength:%lld "
|
||||
"lru:%d lru_seconds_idle:%lu",
|
||||
(void*)val, val->refcount,
|
||||
strenc, (long long) rdbSavedObjectLen(val),
|
||||
val->lru, estimateObjectIdleTime(val));
|
||||
} else {
|
||||
vmpointer *vp = (vmpointer*) val;
|
||||
addReplyStatusFormat(c,
|
||||
"Value swapped at: page %llu "
|
||||
"using %llu pages",
|
||||
(unsigned long long) vp->page,
|
||||
(unsigned long long) vp->usedpages);
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"swapin") && c->argc == 3) {
|
||||
lookupKeyRead(c->db,c->argv[2]);
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"swapout") && c->argc == 3) {
|
||||
dictEntry *de = dictFind(c->db->dict,c->argv[2]->ptr);
|
||||
robj *val;
|
||||
vmpointer *vp;
|
||||
|
||||
if (!server.vm_enabled) {
|
||||
addReplyError(c,"Virtual Memory is disabled");
|
||||
return;
|
||||
}
|
||||
if (!de) {
|
||||
addReply(c,shared.nokeyerr);
|
||||
return;
|
||||
}
|
||||
val = dictGetEntryVal(de);
|
||||
/* Swap it */
|
||||
if (val->storage != REDIS_VM_MEMORY) {
|
||||
addReplyError(c,"This key is not in memory");
|
||||
} else if (val->refcount != 1) {
|
||||
addReplyError(c,"Object is shared");
|
||||
} else if ((vp = vmSwapObjectBlocking(val)) != NULL) {
|
||||
dictGetEntryVal(de) = vp;
|
||||
addReply(c,shared.ok);
|
||||
} else {
|
||||
addReply(c,shared.err);
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"populate") && c->argc == 3) {
|
||||
long keys, j;
|
||||
robj *key, *val;
|
||||
@@ -317,12 +340,10 @@ void _redisAssert(char *estr, char *file, int line) {
|
||||
}
|
||||
|
||||
void _redisPanic(char *msg, char *file, int line) {
|
||||
redisLog(REDIS_WARNING,"------------------------------------------------");
|
||||
redisLog(REDIS_WARNING,"!!! Software Failure. Press left mouse button to continue");
|
||||
redisLog(REDIS_WARNING,"Guru Meditation: %s #%s:%d",msg,file,line);
|
||||
#ifdef HAVE_BACKTRACE
|
||||
redisLog(REDIS_WARNING,"(forcing SIGSEGV in order to print the stack trace)");
|
||||
redisLog(REDIS_WARNING,"------------------------------------------------");
|
||||
*((char*)-1) = 'x';
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -203,7 +203,6 @@ int dictRehash(dict *d, int n) {
|
||||
|
||||
/* Note that rehashidx can't overflow as we are sure there are more
|
||||
* elements because ht[0].used != 0 */
|
||||
assert(d->ht[0].size > (unsigned)d->rehashidx);
|
||||
while(d->ht[0].table[d->rehashidx] == NULL) d->rehashidx++;
|
||||
de = d->ht[0].table[d->rehashidx];
|
||||
/* Move all the keys in this bucket from the old to the new hash HT */
|
||||
|
||||
-509
@@ -1,509 +0,0 @@
|
||||
/* diskstore.c implements a very simple disk backed key-value store used
|
||||
* by Redis for the "disk" backend. This implementation uses the filesystem
|
||||
* to store key/value pairs. Every file represents a given key.
|
||||
*
|
||||
* The key path is calculated using the SHA1 of the key name. For instance
|
||||
* the key "foo" is stored as a file name called:
|
||||
*
|
||||
* /0b/ee/0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33
|
||||
*
|
||||
* The couples of characters from the hex output of SHA1 are also used
|
||||
* to locate two two levels of directories to store the file (as most
|
||||
* filesystems are not able to handle too many files in a single dir).
|
||||
*
|
||||
* In the end there are 65536 final directories (256 directories inside
|
||||
* every 256 top level directories), so that with 1 billion of files every
|
||||
* directory will contain in the average 15258 entires, that is ok with
|
||||
* most filesystems implementation.
|
||||
*
|
||||
* Note that since Redis supports multiple databases, the actual key name
|
||||
* is:
|
||||
*
|
||||
* /0b/ee/<dbid>_0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33
|
||||
*
|
||||
* so for instance if the key is inside DB 0:
|
||||
*
|
||||
* /0b/ee/0_0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33
|
||||
*
|
||||
* The actaul implementation of this disk store is highly dependant to the
|
||||
* filesystem implementation itself. This implementation may be replaced by
|
||||
* a B+TREE implementation in future implementations.
|
||||
*
|
||||
* Data ok every key is serialized using the same format used for .rdb
|
||||
* serialization. Everything is serialized on every entry: key name,
|
||||
* ttl information in case of keys with an associated expire time, and the
|
||||
* serialized value itself.
|
||||
*
|
||||
* Because the format is the same of the .rdb files it is trivial to create
|
||||
* an .rdb file starting from this format just by mean of scanning the
|
||||
* directories and concatenating entries, with the sole addition of an
|
||||
* .rdb header at the start and the end-of-db opcode at the end.
|
||||
*
|
||||
* -------------------------------------------------------------------------
|
||||
*
|
||||
* Copyright (c) 2010-2011, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "redis.h"
|
||||
#include "sha1.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <dirent.h>
|
||||
|
||||
int create256dir(char *prefix) {
|
||||
char buf[1024];
|
||||
int j;
|
||||
|
||||
for (j = 0; j < 256; j++) {
|
||||
snprintf(buf,sizeof(buf),"%s%02x",prefix,j);
|
||||
if (mkdir(buf,0755) == -1) {
|
||||
redisLog(REDIS_WARNING,"Error creating dir %s for diskstore: %s",
|
||||
buf,strerror(errno));
|
||||
return REDIS_ERR;
|
||||
}
|
||||
}
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
int dsOpen(void) {
|
||||
struct stat sb;
|
||||
int retval, j;
|
||||
char *path = server.ds_path;
|
||||
char buf[1024];
|
||||
|
||||
if ((retval = stat(path,&sb) == -1) && errno != ENOENT) {
|
||||
redisLog(REDIS_WARNING, "Error opening disk store at %s: %s",
|
||||
path, strerror(errno));
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
/* Directory already in place. Assume everything is ok. */
|
||||
if (retval == 0 && S_ISDIR(sb.st_mode)) {
|
||||
redisLog(REDIS_NOTICE,"Disk store %s exists", path);
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/* File exists but it's not a directory */
|
||||
if (retval == 0 && !S_ISDIR(sb.st_mode)) {
|
||||
redisLog(REDIS_WARNING,"Disk store at %s is not a directory", path);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
/* New disk store, create the directory structure now, as creating
|
||||
* them in a lazy way is not a good idea, after very few insertions
|
||||
* we'll need most of the 65536 directories anyway. */
|
||||
redisLog(REDIS_NOTICE,"Disk store %s does not exist: creating", path);
|
||||
if (mkdir(path,0755) == -1) {
|
||||
redisLog(REDIS_WARNING,"Disk store init failed creating dir %s: %s",
|
||||
path, strerror(errno));
|
||||
return REDIS_ERR;
|
||||
}
|
||||
/* Create the top level 256 directories */
|
||||
snprintf(buf,sizeof(buf),"%s/",path);
|
||||
if (create256dir(buf) == REDIS_ERR) return REDIS_ERR;
|
||||
|
||||
/* For every 256 top level dir, create 256 nested dirs */
|
||||
for (j = 0; j < 256; j++) {
|
||||
snprintf(buf,sizeof(buf),"%s/%02x/",path,j);
|
||||
if (create256dir(buf) == REDIS_ERR) return REDIS_ERR;
|
||||
}
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
int dsClose(void) {
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/* Convert key into full path for this object. Dirty but hopefully
|
||||
* is fast enough. Returns the length of the returned path. */
|
||||
int dsKeyToPath(redisDb *db, char *buf, robj *key) {
|
||||
SHA1_CTX ctx;
|
||||
unsigned char hash[20];
|
||||
char hex[40], digits[] = "0123456789abcdef";
|
||||
int j, l;
|
||||
char *origbuf = buf;
|
||||
|
||||
SHA1Init(&ctx);
|
||||
SHA1Update(&ctx,key->ptr,sdslen(key->ptr));
|
||||
SHA1Final(hash,&ctx);
|
||||
|
||||
/* Convert the hash into hex format */
|
||||
for (j = 0; j < 20; j++) {
|
||||
hex[j*2] = digits[(hash[j]&0xF0)>>4];
|
||||
hex[(j*2)+1] = digits[hash[j]&0x0F];
|
||||
}
|
||||
|
||||
/* Create the object path. Start with server.ds_path that's the root dir */
|
||||
l = sdslen(server.ds_path);
|
||||
memcpy(buf,server.ds_path,l);
|
||||
buf += l;
|
||||
*buf++ = '/';
|
||||
|
||||
/* Then add xx/yy/ that is the two level directories */
|
||||
buf[0] = hex[0];
|
||||
buf[1] = hex[1];
|
||||
buf[2] = '/';
|
||||
buf[3] = hex[2];
|
||||
buf[4] = hex[3];
|
||||
buf[5] = '/';
|
||||
buf += 6;
|
||||
|
||||
/* Add the database number followed by _ and finall the SHA1 hex */
|
||||
l = ll2string(buf,64,db->id);
|
||||
buf += l;
|
||||
buf[0] = '_';
|
||||
memcpy(buf+1,hex,40);
|
||||
buf[41] = '\0';
|
||||
return (buf-origbuf)+41;
|
||||
}
|
||||
|
||||
int dsSet(redisDb *db, robj *key, robj *val, time_t expire) {
|
||||
char buf[1024], buf2[1024];
|
||||
FILE *fp;
|
||||
int retval, len;
|
||||
|
||||
len = dsKeyToPath(db,buf,key);
|
||||
memcpy(buf2,buf,len);
|
||||
snprintf(buf2+len,sizeof(buf2)-len,"-%ld-%ld",(long)time(NULL),(long)val);
|
||||
while ((fp = fopen(buf2,"w")) == NULL) {
|
||||
if (errno == ENOSPC) {
|
||||
redisLog(REDIS_WARNING,"Diskstore: No space left on device. Please make room and wait 30 seconds for Redis to continue.");
|
||||
sleep(30);
|
||||
} else {
|
||||
redisLog(REDIS_WARNING,"diskstore error opening %s: %s",
|
||||
buf2, strerror(errno));
|
||||
redisPanic("Unrecoverable diskstore error. Exiting.");
|
||||
}
|
||||
}
|
||||
if ((retval = rdbSaveKeyValuePair(fp,key,val,expire,time(NULL))) == -1)
|
||||
return REDIS_ERR;
|
||||
fclose(fp);
|
||||
if (retval == 0) {
|
||||
/* Expired key. Unlink failing not critical */
|
||||
unlink(buf);
|
||||
unlink(buf2);
|
||||
} else {
|
||||
/* Use rename for atomic updadte of value */
|
||||
if (rename(buf2,buf) == -1) {
|
||||
redisLog(REDIS_WARNING,"rename(2) returned an error: %s",
|
||||
strerror(errno));
|
||||
redisPanic("Unrecoverable diskstore error. Exiting.");
|
||||
}
|
||||
}
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
robj *dsGet(redisDb *db, robj *key, time_t *expire) {
|
||||
char buf[1024];
|
||||
int type;
|
||||
time_t expiretime = -1; /* -1 means: no expire */
|
||||
robj *dskey; /* Key as loaded from disk. */
|
||||
robj *val;
|
||||
FILE *fp;
|
||||
|
||||
dsKeyToPath(db,buf,key);
|
||||
fp = fopen(buf,"r");
|
||||
if (fp == NULL && errno == ENOENT) return NULL; /* No such key */
|
||||
if (fp == NULL) {
|
||||
redisLog(REDIS_WARNING,"Disk store failed opening %s: %s",
|
||||
buf, strerror(errno));
|
||||
goto readerr;
|
||||
}
|
||||
|
||||
if ((type = rdbLoadType(fp)) == -1) goto readerr;
|
||||
if (type == REDIS_EXPIRETIME) {
|
||||
if ((expiretime = rdbLoadTime(fp)) == -1) goto readerr;
|
||||
/* We read the time so we need to read the object type again */
|
||||
if ((type = rdbLoadType(fp)) == -1) goto readerr;
|
||||
}
|
||||
/* Read key */
|
||||
if ((dskey = rdbLoadStringObject(fp)) == NULL) goto readerr;
|
||||
/* Read value */
|
||||
if ((val = rdbLoadObject(type,fp)) == NULL) goto readerr;
|
||||
fclose(fp);
|
||||
|
||||
/* The key we asked, and the key returned, must be the same */
|
||||
redisAssert(equalStringObjects(key,dskey));
|
||||
|
||||
/* Check if the key already expired */
|
||||
decrRefCount(dskey);
|
||||
if (expiretime != -1 && expiretime < time(NULL)) {
|
||||
decrRefCount(val);
|
||||
unlink(buf); /* This failing is non critical here */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Everything ok... */
|
||||
*expire = expiretime;
|
||||
return val;
|
||||
|
||||
readerr:
|
||||
redisLog(REDIS_WARNING,"Read error reading reading %s. Corrupted key?",
|
||||
buf);
|
||||
redisPanic("Unrecoverable error reading from disk store");
|
||||
return NULL; /* unreached */
|
||||
}
|
||||
|
||||
int dsDel(redisDb *db, robj *key) {
|
||||
char buf[1024];
|
||||
|
||||
dsKeyToPath(db,buf,key);
|
||||
if (unlink(buf) == -1) {
|
||||
if (errno == ENOENT) {
|
||||
return REDIS_ERR;
|
||||
} else {
|
||||
redisLog(REDIS_WARNING,"Disk store can't remove %s: %s",
|
||||
buf, strerror(errno));
|
||||
redisPanic("Unrecoverable Disk store errore. Existing.");
|
||||
return REDIS_ERR; /* unreached */
|
||||
}
|
||||
} else {
|
||||
return REDIS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
int dsExists(redisDb *db, robj *key) {
|
||||
char buf[1024];
|
||||
|
||||
dsKeyToPath(db,buf,key);
|
||||
return access(buf,R_OK) == 0;
|
||||
}
|
||||
|
||||
int dsGetDbidFromFilename(char *path) {
|
||||
char id[64];
|
||||
char *p = strchr(path,'_');
|
||||
int len = (p - path);
|
||||
|
||||
redisAssert(p != NULL && len < 64);
|
||||
memcpy(id,path,len);
|
||||
id[len] = '\0';
|
||||
return atoi(id);
|
||||
}
|
||||
|
||||
void dsFlushOneDir(char *path, int dbid) {
|
||||
DIR *dir;
|
||||
struct dirent *dp, de;
|
||||
|
||||
dir = opendir(path);
|
||||
if (dir == NULL) {
|
||||
redisLog(REDIS_WARNING,"Disk store can't open dir %s: %s",
|
||||
path, strerror(errno));
|
||||
redisPanic("Unrecoverable Disk store errore. Existing.");
|
||||
}
|
||||
while(1) {
|
||||
char buf[1024];
|
||||
|
||||
readdir_r(dir,&de,&dp);
|
||||
if (dp == NULL) break;
|
||||
if (dp->d_name[0] == '.') continue;
|
||||
|
||||
/* Check if we need to remove this entry accordingly to the
|
||||
* DB number. */
|
||||
if (dbid != -1 && dsGetDbidFromFilename(dp->d_name)) continue;
|
||||
|
||||
/* Finally unlink the file */
|
||||
snprintf(buf,1024,"%s/%s",path,dp->d_name);
|
||||
if (unlink(buf) == -1) {
|
||||
redisLog(REDIS_WARNING,
|
||||
"Can't unlink %s: %s", buf, strerror(errno));
|
||||
redisPanic("Unrecoverable Disk store errore. Existing.");
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
|
||||
void dsFlushDb(int dbid) {
|
||||
char buf[1024];
|
||||
int j, i;
|
||||
|
||||
redisLog(REDIS_NOTICE,"Flushing diskstore DB (%d)",dbid);
|
||||
for (j = 0; j < 256; j++) {
|
||||
for (i = 0; i < 256; i++) {
|
||||
snprintf(buf,1024,"%s/%02x/%02x",server.ds_path,j,i);
|
||||
dsFlushOneDir(buf,dbid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void dsRdbSaveSetState(int state) {
|
||||
pthread_mutex_lock(&server.bgsavethread_mutex);
|
||||
server.bgsavethread_state = state;
|
||||
pthread_mutex_unlock(&server.bgsavethread_mutex);
|
||||
}
|
||||
|
||||
void *dsRdbSave_thread(void *arg) {
|
||||
char tmpfile[256], *filename = (char*)arg;
|
||||
struct dirent *dp, de;
|
||||
int j, i, last_dbid = -1;
|
||||
FILE *fp;
|
||||
|
||||
/* Change state to ACTIVE, to signal there is a saving thead working. */
|
||||
redisLog(REDIS_NOTICE,"Diskstore BGSAVE thread started");
|
||||
dsRdbSaveSetState(REDIS_BGSAVE_THREAD_ACTIVE);
|
||||
|
||||
snprintf(tmpfile,256,"temp-%d.rdb", (int) getpid());
|
||||
fp = fopen(tmpfile,"w");
|
||||
if (!fp) {
|
||||
redisLog(REDIS_WARNING, "Failed opening .rdb for saving: %s",
|
||||
strerror(errno));
|
||||
dsRdbSaveSetState(REDIS_BGSAVE_THREAD_DONE_ERR);
|
||||
return NULL;
|
||||
}
|
||||
if (fwrite("REDIS0001",9,1,fp) == 0) goto werr;
|
||||
|
||||
sleep(5);
|
||||
|
||||
/* Scan all diskstore dirs looking for keys */
|
||||
for (j = 0; j < 256; j++) {
|
||||
for (i = 0; i < 256; i++) {
|
||||
DIR *dir;
|
||||
char buf[1024];
|
||||
|
||||
/* For every directory, collect all the keys */
|
||||
snprintf(buf,sizeof(buf),"%s/%02x/%02x",server.ds_path,j,i);
|
||||
if ((dir = opendir(buf)) == NULL) {
|
||||
redisLog(REDIS_WARNING,"Disk store can't open dir %s: %s",
|
||||
buf, strerror(errno));
|
||||
goto werr;
|
||||
}
|
||||
|
||||
while(1) {
|
||||
char buf[1024];
|
||||
int dbid;
|
||||
FILE *entryfp;
|
||||
|
||||
readdir_r(dir,&de,&dp);
|
||||
if (dp == NULL) break;
|
||||
if (dp->d_name[0] == '.') continue;
|
||||
/* If there is a '-' char in the file name, it's a temp file */
|
||||
if (strchr(dp->d_name,'-') != NULL) continue;
|
||||
|
||||
/* Emit the SELECT DB opcode if needed. */
|
||||
dbid = dsGetDbidFromFilename(dp->d_name);
|
||||
if (dbid != last_dbid) {
|
||||
last_dbid = dbid;
|
||||
if (rdbSaveType(fp,REDIS_SELECTDB) == -1) goto werr;
|
||||
if (rdbSaveLen(fp,dbid) == -1) goto werr;
|
||||
}
|
||||
|
||||
/* Let's copy this file into the target .rdb */
|
||||
snprintf(buf,sizeof(buf),"%s/%02x/%02x/%s",
|
||||
server.ds_path,j,i,dp->d_name);
|
||||
if ((entryfp = fopen(buf,"r")) == NULL) {
|
||||
redisLog(REDIS_WARNING,"Can't open %s: %s",
|
||||
buf,strerror(errno));
|
||||
closedir(dir);
|
||||
goto werr;
|
||||
}
|
||||
while(1) {
|
||||
int nread = fread(buf,1,sizeof(buf),entryfp);
|
||||
|
||||
if (nread == 0) {
|
||||
if (ferror(entryfp)) {
|
||||
redisLog(REDIS_WARNING,"Error reading from file entry while performing BGSAVE for diskstore: %s", strerror(errno));
|
||||
closedir(dir);
|
||||
goto werr;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (fwrite(buf,1,nread,fp) != (unsigned)nread) {
|
||||
closedir(dir);
|
||||
goto werr;
|
||||
}
|
||||
}
|
||||
fclose(entryfp);
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
}
|
||||
|
||||
/* Output the end of file opcode */
|
||||
if (rdbSaveType(fp,REDIS_EOF) == -1) goto werr;
|
||||
|
||||
/* Make sure data will not remain on the OS's output buffers */
|
||||
fflush(fp);
|
||||
fsync(fileno(fp));
|
||||
fclose(fp);
|
||||
zfree(filename);
|
||||
|
||||
/* Use RENAME to make sure the DB file is changed atomically only
|
||||
* if the generate DB file is ok. */
|
||||
if (rename(tmpfile,filename) == -1) {
|
||||
redisLog(REDIS_WARNING,"Error moving temp DB file on the final destination: %s (diskstore)", strerror(errno));
|
||||
unlink(tmpfile);
|
||||
dsRdbSaveSetState(REDIS_BGSAVE_THREAD_DONE_ERR);
|
||||
return NULL;
|
||||
}
|
||||
redisLog(REDIS_NOTICE,"DB saved on disk by diskstore thread");
|
||||
dsRdbSaveSetState(REDIS_BGSAVE_THREAD_DONE_OK);
|
||||
return NULL;
|
||||
|
||||
werr:
|
||||
zfree(filename);
|
||||
fclose(fp);
|
||||
unlink(tmpfile);
|
||||
dsRdbSaveSetState(REDIS_BGSAVE_THREAD_DONE_ERR);
|
||||
redisLog(REDIS_WARNING,"Write error saving DB on disk: %s", strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int dsRdbSaveBackground(char *filename) {
|
||||
pthread_t thread;
|
||||
|
||||
if (pthread_create(&thread,NULL,dsRdbSave_thread,zstrdup(filename)) != 0) {
|
||||
redisLog(REDIS_WARNING,"Can't create diskstore BGSAVE thread: %s",
|
||||
strerror(errno));
|
||||
return REDIS_ERR;
|
||||
} else {
|
||||
server.bgsavethread = thread;
|
||||
return REDIS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
int dsRdbSave(char *filename) {
|
||||
/* A blocking save is actually a non blocking save... just we wait
|
||||
* for it to terminate in a non-busy loop. */
|
||||
|
||||
redisLog(REDIS_NOTICE,"Starting a blocking SAVE (BGSAVE + blocking wait)");
|
||||
server.dirty_before_bgsave = server.dirty;
|
||||
if (dsRdbSaveBackground(filename) == REDIS_ERR) return REDIS_ERR;
|
||||
while(1) {
|
||||
usleep(1000);
|
||||
int state;
|
||||
|
||||
pthread_mutex_lock(&server.bgsavethread_mutex);
|
||||
state = server.bgsavethread_state;
|
||||
pthread_mutex_unlock(&server.bgsavethread_mutex);
|
||||
|
||||
if (state == REDIS_BGSAVE_THREAD_DONE_OK ||
|
||||
state == REDIS_BGSAVE_THREAD_DONE_ERR) break;
|
||||
}
|
||||
return REDIS_OK;
|
||||
}
|
||||
-1028
File diff suppressed because it is too large
Load Diff
+3
-3
@@ -186,6 +186,7 @@ void _addReplyStringToList(redisClient *c, char *s, size_t len) {
|
||||
|
||||
void addReply(redisClient *c, robj *obj) {
|
||||
if (_installWriteEvent(c) != REDIS_OK) return;
|
||||
redisAssert(!server.vm_enabled || obj->storage == REDIS_VM_MEMORY);
|
||||
|
||||
/* This is an important place where we can avoid copy-on-write
|
||||
* when there is a saving child running, avoiding touching the
|
||||
@@ -490,7 +491,7 @@ void freeClient(redisClient *c) {
|
||||
/* Remove from the list of clients waiting for swapped keys, or ready
|
||||
* to be restarted, but not yet woken up again. */
|
||||
if (c->flags & REDIS_IO_WAIT) {
|
||||
redisAssert(server.ds_enabled);
|
||||
redisAssert(server.vm_enabled);
|
||||
if (listLength(c->io_keys) == 0) {
|
||||
ln = listSearchKey(server.io_ready_clients,c);
|
||||
|
||||
@@ -504,7 +505,7 @@ void freeClient(redisClient *c) {
|
||||
dontWaitForSwappedKey(c,ln->value);
|
||||
}
|
||||
}
|
||||
server.cache_blocked_clients--;
|
||||
server.vm_blocked_clients--;
|
||||
}
|
||||
listRelease(c->io_keys);
|
||||
/* Master/slave cleanup.
|
||||
@@ -522,7 +523,6 @@ void freeClient(redisClient *c) {
|
||||
if (c->flags & REDIS_MASTER) {
|
||||
server.master = NULL;
|
||||
server.replstate = REDIS_REPL_CONNECT;
|
||||
server.repl_down_since = time(NULL);
|
||||
/* Since we lost the connection with the master, we should also
|
||||
* close the connection with all our slaves if we have any, so
|
||||
* when we'll resync with the master the other slaves will sync again
|
||||
|
||||
+32
-8
@@ -21,6 +21,7 @@ robj *createObject(int type, void *ptr) {
|
||||
/* The following is only needed if VM is active, but since the conditional
|
||||
* is probably more costly than initializing the field it's better to
|
||||
* have every field properly initialized anyway. */
|
||||
o->storage = REDIS_VM_MEMORY;
|
||||
return o;
|
||||
}
|
||||
|
||||
@@ -31,7 +32,6 @@ robj *createStringObject(char *ptr, size_t len) {
|
||||
robj *createStringObjectFromLongLong(long long value) {
|
||||
robj *o;
|
||||
if (value >= 0 && value < REDIS_SHARED_INTEGERS &&
|
||||
!server.ds_enabled &&
|
||||
pthread_equal(pthread_self(),server.mainthread)) {
|
||||
incrRefCount(shared.integers[value]);
|
||||
o = shared.integers[value];
|
||||
@@ -179,7 +179,30 @@ void incrRefCount(robj *o) {
|
||||
void decrRefCount(void *obj) {
|
||||
robj *o = obj;
|
||||
|
||||
/* Object is a swapped out value, or in the process of being loaded. */
|
||||
if (server.vm_enabled &&
|
||||
(o->storage == REDIS_VM_SWAPPED || o->storage == REDIS_VM_LOADING))
|
||||
{
|
||||
vmpointer *vp = obj;
|
||||
if (o->storage == REDIS_VM_LOADING) vmCancelThreadedIOJob(o);
|
||||
vmMarkPagesFree(vp->page,vp->usedpages);
|
||||
server.vm_stats_swapped_objects--;
|
||||
zfree(vp);
|
||||
return;
|
||||
}
|
||||
|
||||
if (o->refcount <= 0) redisPanic("decrRefCount against refcount <= 0");
|
||||
/* Object is in memory, or in the process of being swapped out.
|
||||
*
|
||||
* If the object is being swapped out, abort the operation on
|
||||
* decrRefCount even if the refcount does not drop to 0: the object
|
||||
* is referenced at least two times, as value of the key AND as
|
||||
* job->val in the iojob. So if we don't invalidate the iojob, when it is
|
||||
* done but the relevant key was removed in the meantime, the
|
||||
* complete jobs handler will not find the key about the job and the
|
||||
* assert will fail. */
|
||||
if (server.vm_enabled && o->storage == REDIS_VM_SWAPPING)
|
||||
vmCancelThreadedIOJob(o);
|
||||
if (o->refcount == 1) {
|
||||
switch(o->type) {
|
||||
case REDIS_STRING: freeStringObject(o); break;
|
||||
@@ -235,16 +258,16 @@ robj *tryObjectEncoding(robj *o) {
|
||||
/* Ok, this object can be encoded...
|
||||
*
|
||||
* Can I use a shared object? Only if the object is inside a given
|
||||
* range and if the back end in use is in-memory. For disk store every
|
||||
* object in memory used as value should be independent.
|
||||
* range and if this is the main thread, since when VM is enabled we
|
||||
* have the constraint that I/O thread should only handle non-shared
|
||||
* objects, in order to avoid race conditions (we don't have per-object
|
||||
* locking).
|
||||
*
|
||||
* Note that we also avoid using shared integers when maxmemory is used
|
||||
* because every object needs to have a private LRU field for the LRU
|
||||
* because very object needs to have a private LRU field for the LRU
|
||||
* algorithm to work well. */
|
||||
if (!server.ds_enabled &&
|
||||
server.maxmemory == 0 && value >= 0 && value < REDIS_SHARED_INTEGERS &&
|
||||
pthread_equal(pthread_self(),server.mainthread))
|
||||
{
|
||||
if (server.maxmemory == 0 && value >= 0 && value < REDIS_SHARED_INTEGERS &&
|
||||
pthread_equal(pthread_self(),server.mainthread)) {
|
||||
decrRefCount(o);
|
||||
incrRefCount(shared.integers[value]);
|
||||
return shared.integers[value];
|
||||
@@ -453,6 +476,7 @@ unsigned long estimateObjectIdleTime(robj *o) {
|
||||
robj *objectCommandLookup(redisClient *c, robj *key) {
|
||||
dictEntry *de;
|
||||
|
||||
if (server.vm_enabled) lookupKeyRead(c->db,key);
|
||||
if ((de = dictFind(c->db->dict,key->ptr)) == NULL) return NULL;
|
||||
return (robj*) dictGetEntryVal(de);
|
||||
}
|
||||
|
||||
@@ -245,7 +245,7 @@ int rdbSaveDoubleValue(FILE *fp, double val) {
|
||||
return rdbWriteRaw(fp,buf,len);
|
||||
}
|
||||
|
||||
/* Save a Redis object. Returns -1 on error, 0 on success. */
|
||||
/* Save a Redis object. */
|
||||
int rdbSaveObject(FILE *fp, robj *o) {
|
||||
int n, nwritten = 0;
|
||||
|
||||
@@ -370,38 +370,24 @@ off_t rdbSavedObjectLen(robj *o) {
|
||||
return len;
|
||||
}
|
||||
|
||||
/* Save a key-value pair, with expire time, type, key, value.
|
||||
* On error -1 is returned.
|
||||
* On success if the key was actaully saved 1 is returned, otherwise 0
|
||||
* is returned (the key was already expired). */
|
||||
int rdbSaveKeyValuePair(FILE *fp, robj *key, robj *val,
|
||||
time_t expiretime, time_t now)
|
||||
{
|
||||
int vtype;
|
||||
/* Return the number of pages required to save this object in the swap file */
|
||||
off_t rdbSavedObjectPages(robj *o) {
|
||||
off_t bytes = rdbSavedObjectLen(o);
|
||||
return (bytes+(server.vm_page_size-1))/server.vm_page_size;
|
||||
}
|
||||
|
||||
/* Save the expire time */
|
||||
if (expiretime != -1) {
|
||||
/* If this key is already expired skip it */
|
||||
if (expiretime < now) return 0;
|
||||
if (rdbSaveType(fp,REDIS_EXPIRETIME) == -1) return -1;
|
||||
if (rdbSaveTime(fp,expiretime) == -1) return -1;
|
||||
}
|
||||
/* Fix the object type if needed, to support saving zipmaps, ziplists,
|
||||
* and intsets, directly as blobs of bytes: they are already serialized. */
|
||||
vtype = val->type;
|
||||
if (vtype == REDIS_HASH && val->encoding == REDIS_ENCODING_ZIPMAP)
|
||||
vtype = REDIS_HASH_ZIPMAP;
|
||||
else if (vtype == REDIS_LIST && val->encoding == REDIS_ENCODING_ZIPLIST)
|
||||
vtype = REDIS_LIST_ZIPLIST;
|
||||
else if (vtype == REDIS_SET && val->encoding == REDIS_ENCODING_INTSET)
|
||||
vtype = REDIS_SET_INTSET;
|
||||
else if (vtype == REDIS_ZSET && val->encoding == REDIS_ENCODING_ZIPLIST)
|
||||
vtype = REDIS_ZSET_ZIPLIST;
|
||||
/* Save type, key, value */
|
||||
if (rdbSaveType(fp,vtype) == -1) return -1;
|
||||
if (rdbSaveStringObject(fp,key) == -1) return -1;
|
||||
if (rdbSaveObject(fp,val) == -1) return -1;
|
||||
return 1;
|
||||
int getObjectSaveType(robj *o) {
|
||||
/* Fix the type id for specially encoded data types */
|
||||
if (o->type == REDIS_HASH && o->encoding == REDIS_ENCODING_ZIPMAP)
|
||||
return REDIS_HASH_ZIPMAP;
|
||||
else if (o->type == REDIS_LIST && o->encoding == REDIS_ENCODING_ZIPLIST)
|
||||
return REDIS_LIST_ZIPLIST;
|
||||
else if (o->type == REDIS_SET && o->encoding == REDIS_ENCODING_INTSET)
|
||||
return REDIS_SET_INTSET;
|
||||
else if (o->type == REDIS_ZSET && o->encoding == REDIS_ENCODING_ZIPLIST)
|
||||
return REDIS_ZSET_ZIPLIST;
|
||||
else
|
||||
return o->type;
|
||||
}
|
||||
|
||||
/* Save the DB on disk. Return REDIS_ERR on error, REDIS_OK on success */
|
||||
@@ -413,16 +399,16 @@ int rdbSave(char *filename) {
|
||||
int j;
|
||||
time_t now = time(NULL);
|
||||
|
||||
if (server.ds_enabled) {
|
||||
cacheForcePointInTime();
|
||||
return dsRdbSave(filename);
|
||||
}
|
||||
/* Wait for I/O therads to terminate, just in case this is a
|
||||
* foreground-saving, to avoid seeking the swap file descriptor at the
|
||||
* same time. */
|
||||
if (server.vm_enabled)
|
||||
waitEmptyIOJobsQueue();
|
||||
|
||||
snprintf(tmpfile,256,"temp-%d.rdb", (int) getpid());
|
||||
fp = fopen(tmpfile,"w");
|
||||
if (!fp) {
|
||||
redisLog(REDIS_WARNING, "Failed opening .rdb for saving: %s",
|
||||
strerror(errno));
|
||||
redisLog(REDIS_WARNING, "Failed saving the DB: %s", strerror(errno));
|
||||
return REDIS_ERR;
|
||||
}
|
||||
if (fwrite("REDIS0002",9,1,fp) == 0) goto werr;
|
||||
@@ -430,7 +416,7 @@ int rdbSave(char *filename) {
|
||||
redisDb *db = server.db+j;
|
||||
dict *d = db->dict;
|
||||
if (dictSize(d) == 0) continue;
|
||||
di = dictGetSafeIterator(d);
|
||||
di = dictGetIterator(d);
|
||||
if (!di) {
|
||||
fclose(fp);
|
||||
return REDIS_ERR;
|
||||
@@ -444,11 +430,41 @@ int rdbSave(char *filename) {
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sds keystr = dictGetEntryKey(de);
|
||||
robj key, *o = dictGetEntryVal(de);
|
||||
time_t expire;
|
||||
time_t expiretime;
|
||||
|
||||
initStaticStringObject(key,keystr);
|
||||
expire = getExpire(db,&key);
|
||||
if (rdbSaveKeyValuePair(fp,&key,o,expire,now) == -1) goto werr;
|
||||
expiretime = getExpire(db,&key);
|
||||
|
||||
/* Save the expire time */
|
||||
if (expiretime != -1) {
|
||||
/* If this key is already expired skip it */
|
||||
if (expiretime < now) continue;
|
||||
if (rdbSaveType(fp,REDIS_EXPIRETIME) == -1) goto werr;
|
||||
if (rdbSaveTime(fp,expiretime) == -1) goto werr;
|
||||
}
|
||||
/* Save the key and associated value. This requires special
|
||||
* handling if the value is swapped out. */
|
||||
if (!server.vm_enabled || o->storage == REDIS_VM_MEMORY ||
|
||||
o->storage == REDIS_VM_SWAPPING) {
|
||||
int otype = getObjectSaveType(o);
|
||||
|
||||
/* Save type, key, value */
|
||||
if (rdbSaveType(fp,otype) == -1) goto werr;
|
||||
if (rdbSaveStringObject(fp,&key) == -1) goto werr;
|
||||
if (rdbSaveObject(fp,o) == -1) goto werr;
|
||||
} else {
|
||||
/* REDIS_VM_SWAPPED or REDIS_VM_LOADING */
|
||||
robj *po;
|
||||
/* Get a preview of the object in memory */
|
||||
po = vmPreviewObject(o);
|
||||
/* Save type, key, value */
|
||||
if (rdbSaveType(fp,getObjectSaveType(po)) == -1)
|
||||
goto werr;
|
||||
if (rdbSaveStringObject(fp,&key) == -1) goto werr;
|
||||
if (rdbSaveObject(fp,po) == -1) goto werr;
|
||||
/* Remove the loaded object from memory */
|
||||
decrRefCount(po);
|
||||
}
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
@@ -484,25 +500,20 @@ int rdbSaveBackground(char *filename) {
|
||||
pid_t childpid;
|
||||
long long start;
|
||||
|
||||
if (server.bgsavechildpid != -1 ||
|
||||
server.bgsavethread != (pthread_t) -1) return REDIS_ERR;
|
||||
|
||||
if (server.bgsavechildpid != -1) return REDIS_ERR;
|
||||
if (server.vm_enabled) waitEmptyIOJobsQueue();
|
||||
server.dirty_before_bgsave = server.dirty;
|
||||
|
||||
if (server.ds_enabled) {
|
||||
cacheForcePointInTime();
|
||||
return dsRdbSaveBackground(filename);
|
||||
}
|
||||
|
||||
start = ustime();
|
||||
if ((childpid = fork()) == 0) {
|
||||
int retval;
|
||||
|
||||
/* Child */
|
||||
if (server.vm_enabled) vmReopenSwapFile();
|
||||
if (server.ipfd > 0) close(server.ipfd);
|
||||
if (server.sofd > 0) close(server.sofd);
|
||||
retval = rdbSave(filename);
|
||||
_exit((retval == REDIS_OK) ? 0 : 1);
|
||||
if (rdbSave(filename) == REDIS_OK) {
|
||||
_exit(0);
|
||||
} else {
|
||||
_exit(1);
|
||||
}
|
||||
} else {
|
||||
/* Parent */
|
||||
server.stat_fork_time = ustime()-start;
|
||||
@@ -918,7 +929,8 @@ void stopLoading(void) {
|
||||
int rdbLoad(char *filename) {
|
||||
FILE *fp;
|
||||
uint32_t dbid;
|
||||
int type, rdbver;
|
||||
int type, retval, rdbver;
|
||||
int swap_all_values = 0;
|
||||
redisDb *db = server.db+0;
|
||||
char buf[1024];
|
||||
time_t expiretime, now = time(NULL);
|
||||
@@ -943,6 +955,8 @@ int rdbLoad(char *filename) {
|
||||
startLoading(fp);
|
||||
while(1) {
|
||||
robj *key, *val;
|
||||
int force_swapout;
|
||||
|
||||
expiretime = -1;
|
||||
|
||||
/* Serve the clients from time to time */
|
||||
@@ -981,12 +995,52 @@ int rdbLoad(char *filename) {
|
||||
continue;
|
||||
}
|
||||
/* Add the new object in the hash table */
|
||||
dbAdd(db,key,val);
|
||||
|
||||
retval = dbAdd(db,key,val);
|
||||
if (retval == REDIS_ERR) {
|
||||
redisLog(REDIS_WARNING,"Loading DB, duplicated key (%s) found! Unrecoverable error, exiting now.", key->ptr);
|
||||
exit(1);
|
||||
}
|
||||
/* Set the expire time if needed */
|
||||
if (expiretime != -1) setExpire(db,key,expiretime);
|
||||
|
||||
/* Handle swapping while loading big datasets when VM is on */
|
||||
|
||||
/* If we detecter we are hopeless about fitting something in memory
|
||||
* we just swap every new key on disk. Directly...
|
||||
* Note that's important to check for this condition before resorting
|
||||
* to random sampling, otherwise we may try to swap already
|
||||
* swapped keys. */
|
||||
if (swap_all_values) {
|
||||
dictEntry *de = dictFind(db->dict,key->ptr);
|
||||
|
||||
/* de may be NULL since the key already expired */
|
||||
if (de) {
|
||||
vmpointer *vp;
|
||||
val = dictGetEntryVal(de);
|
||||
|
||||
if (val->refcount == 1 &&
|
||||
(vp = vmSwapObjectBlocking(val)) != NULL)
|
||||
dictGetEntryVal(de) = vp;
|
||||
}
|
||||
decrRefCount(key);
|
||||
continue;
|
||||
}
|
||||
decrRefCount(key);
|
||||
|
||||
/* Flush data on disk once 32 MB of additional RAM are used... */
|
||||
force_swapout = 0;
|
||||
if ((zmalloc_used_memory() - server.vm_max_memory) > 1024*1024*32)
|
||||
force_swapout = 1;
|
||||
|
||||
/* If we have still some hope of having some value fitting memory
|
||||
* then we try random sampling. */
|
||||
if (!swap_all_values && server.vm_enabled && force_swapout) {
|
||||
while (zmalloc_used_memory() > server.vm_max_memory) {
|
||||
if (vmSwapOneObjectBlocking() == REDIS_ERR) break;
|
||||
}
|
||||
if (zmalloc_used_memory() > server.vm_max_memory)
|
||||
swap_all_values = 1; /* We are already using too much mem */
|
||||
}
|
||||
}
|
||||
fclose(fp);
|
||||
stopLoading();
|
||||
@@ -999,7 +1053,10 @@ eoferr: /* unexpected end of file is handled here with a fatal exit */
|
||||
}
|
||||
|
||||
/* A background saving child (BGSAVE) terminated its work. Handle this. */
|
||||
void backgroundSaveDoneHandler(int exitcode, int bysignal) {
|
||||
void backgroundSaveDoneHandler(int statloc) {
|
||||
int exitcode = WEXITSTATUS(statloc);
|
||||
int bysignal = WIFSIGNALED(statloc);
|
||||
|
||||
if (!bysignal && exitcode == 0) {
|
||||
redisLog(REDIS_NOTICE,
|
||||
"Background saving terminated with success");
|
||||
@@ -1009,37 +1066,11 @@ void backgroundSaveDoneHandler(int exitcode, int bysignal) {
|
||||
redisLog(REDIS_WARNING, "Background saving error");
|
||||
} else {
|
||||
redisLog(REDIS_WARNING,
|
||||
"Background saving terminated by signal %d", bysignal);
|
||||
"Background saving terminated by signal %d", WTERMSIG(statloc));
|
||||
rdbRemoveTempFile(server.bgsavechildpid);
|
||||
}
|
||||
server.bgsavechildpid = -1;
|
||||
server.bgsavethread = (pthread_t) -1;
|
||||
server.bgsavethread_state = REDIS_BGSAVE_THREAD_UNACTIVE;
|
||||
/* Possibly there are slaves waiting for a BGSAVE in order to be served
|
||||
* (the first stage of SYNC is a bulk transfer of dump.rdb) */
|
||||
updateSlavesWaitingBgsave(exitcode == 0 ? REDIS_OK : REDIS_ERR);
|
||||
}
|
||||
|
||||
void saveCommand(redisClient *c) {
|
||||
if (server.bgsavechildpid != -1 || server.bgsavethread != (pthread_t)-1) {
|
||||
addReplyError(c,"Background save already in progress");
|
||||
return;
|
||||
}
|
||||
if (rdbSave(server.dbfilename) == REDIS_OK) {
|
||||
addReply(c,shared.ok);
|
||||
} else {
|
||||
addReply(c,shared.err);
|
||||
}
|
||||
}
|
||||
|
||||
void bgsaveCommand(redisClient *c) {
|
||||
if (server.bgsavechildpid != -1 || server.bgsavethread != (pthread_t)-1) {
|
||||
addReplyError(c,"Background save already in progress");
|
||||
} else if (server.bgrewritechildpid != -1) {
|
||||
addReplyError(c,"Can't BGSAVE while AOF log rewriting is in progress");
|
||||
} else if (rdbSaveBackground(server.dbfilename) == REDIS_OK) {
|
||||
addReplyStatus(c,"Background saving started");
|
||||
} else {
|
||||
addReply(c,shared.err);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -469,9 +469,6 @@ static int cliSendCommand(int argc, char **argv, int repeat) {
|
||||
|
||||
output_raw = 0;
|
||||
if (!strcasecmp(command,"info") ||
|
||||
(argc == 2 && !strcasecmp(command,"cluster") &&
|
||||
(!strcasecmp(argv[1],"nodes") ||
|
||||
!strcasecmp(argv[1],"info"))) ||
|
||||
(argc == 2 && !strcasecmp(command,"client") &&
|
||||
!strcasecmp(argv[1],"list")))
|
||||
|
||||
|
||||
@@ -1,258 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require 'rubygems'
|
||||
require 'redis'
|
||||
|
||||
ClusterHashSlots = 4096
|
||||
|
||||
def xputs(s)
|
||||
printf s
|
||||
STDOUT.flush
|
||||
end
|
||||
|
||||
class ClusterNode
|
||||
def initialize(addr)
|
||||
s = addr.split(":")
|
||||
if s.length != 2
|
||||
puts "Invalid node name #{addr}"
|
||||
exit 1
|
||||
end
|
||||
@r = nil
|
||||
@host = s[0]
|
||||
@port = s[1]
|
||||
@slots = {}
|
||||
@dirty = false
|
||||
end
|
||||
|
||||
def to_s
|
||||
"#{@host}:#{@port}"
|
||||
end
|
||||
|
||||
def connect(o={})
|
||||
xputs "Connecting to node #{self}: "
|
||||
begin
|
||||
@r = Redis.new(:host => @host, :port => @port)
|
||||
@r.ping
|
||||
rescue
|
||||
puts "ERROR"
|
||||
puts "Sorry, can't connect to node #{self}"
|
||||
exit 1 if o[:abort]
|
||||
@r = nil
|
||||
end
|
||||
puts "OK"
|
||||
end
|
||||
|
||||
def assert_cluster
|
||||
info = @r.info
|
||||
if !info["cluster_enabled"] || info["cluster_enabled"].to_i == 0
|
||||
puts "Error: Node #{self} is not configured as a cluster node."
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
def assert_empty
|
||||
if !(@r.cluster("info").split("\r\n").index("cluster_known_nodes:1")) ||
|
||||
(@r.info['db0'])
|
||||
puts "Error: Node #{self} is not empty. Either the node already knows other nodes (check with nodes-info) or contains some key in database 0."
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
def add_slots(slots)
|
||||
slots.each{|s|
|
||||
@slots[s] = :new
|
||||
}
|
||||
@dirty = true
|
||||
end
|
||||
|
||||
def flush_node_config
|
||||
return if !@dirty
|
||||
new = []
|
||||
@slots.each{|s,val|
|
||||
if val == :new
|
||||
new << s
|
||||
@slots[s] = true
|
||||
end
|
||||
}
|
||||
@r.cluster("addslots",*new)
|
||||
@dirty = false
|
||||
end
|
||||
|
||||
def info_string
|
||||
# We want to display the hash slots assigned to this node
|
||||
# as ranges, like in: "1-5,8-9,20-25,30"
|
||||
#
|
||||
# Note: this could be easily written without side effects,
|
||||
# we use 'slots' just to split the computation into steps.
|
||||
|
||||
# First step: we want an increasing array of integers
|
||||
# for instance: [1,2,3,4,5,8,9,20,21,22,23,24,25,30]
|
||||
slots = @slots.keys.sort
|
||||
|
||||
# As we want to aggregate adiacent slots we convert all the
|
||||
# slot integers into ranges (with just one element)
|
||||
# So we have something like [1..1,2..2, ... and so forth.
|
||||
slots.map!{|x| x..x}
|
||||
|
||||
# Finally we group ranges with adiacent elements.
|
||||
slots = slots.reduce([]) {|a,b|
|
||||
if !a.empty? && b.first == (a[-1].last)+1
|
||||
a[0..-2] + [(a[-1].first)..(b.last)]
|
||||
else
|
||||
a + [b]
|
||||
end
|
||||
}
|
||||
|
||||
# Now our task is easy, we just convert ranges with just one
|
||||
# element into a number, and a real range into a start-end format.
|
||||
# Finally we join the array using the comma as separator.
|
||||
slots = slots.map{|x|
|
||||
x.count == 1 ? x.first.to_s : "#{x.first}-#{x.last}"
|
||||
}.join(",")
|
||||
|
||||
"#{self.to_s.ljust(25)} slots:#{slots}"
|
||||
end
|
||||
|
||||
def info
|
||||
{
|
||||
:host => @host,
|
||||
:port => @port,
|
||||
:slots => @slots,
|
||||
:dirty => @dirty
|
||||
}
|
||||
end
|
||||
|
||||
def is_dirty?
|
||||
@dirty
|
||||
end
|
||||
|
||||
def r
|
||||
@r
|
||||
end
|
||||
end
|
||||
|
||||
class RedisTrib
|
||||
def initialize
|
||||
@nodes = []
|
||||
end
|
||||
|
||||
def check_arity(req_args, num_args)
|
||||
if ((req_args > 0 and num_args != req_args) ||
|
||||
(req_args < 0 and num_args < req_args.abs))
|
||||
puts "Wrong number of arguments for specified sub command"
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
def add_node(node)
|
||||
@nodes << node
|
||||
end
|
||||
|
||||
def check_cluster
|
||||
puts "Performing Cluster Check (using node #{@nodes[0]})"
|
||||
show_nodes
|
||||
end
|
||||
|
||||
def alloc_slots
|
||||
slots_per_node = ClusterHashSlots/@nodes.length
|
||||
i = 0
|
||||
@nodes.each{|n|
|
||||
first = i*slots_per_node
|
||||
last = first+slots_per_node-1
|
||||
last = ClusterHashSlots-1 if i == @nodes.length-1
|
||||
n.add_slots first..last
|
||||
i += 1
|
||||
}
|
||||
end
|
||||
|
||||
def flush_nodes_config
|
||||
@nodes.each{|n|
|
||||
n.flush_node_config
|
||||
}
|
||||
end
|
||||
|
||||
def show_nodes
|
||||
@nodes.each{|n|
|
||||
puts n.info_string
|
||||
}
|
||||
end
|
||||
|
||||
def join_cluster
|
||||
# We use a brute force approach to make sure the node will meet
|
||||
# each other, that is, sending CLUSTER MEET messages to all the nodes
|
||||
# about the very same node.
|
||||
# Thanks to gossip this information should propagate across all the
|
||||
# cluster in a matter of seconds.
|
||||
first = false
|
||||
@nodes.each{|n|
|
||||
if !first then first = n.info; next; end # Skip the first node
|
||||
n.r.cluster("meet",first[:host],first[:port])
|
||||
}
|
||||
end
|
||||
|
||||
def yes_or_die(msg)
|
||||
print "#{msg} (type 'yes' to accept): "
|
||||
STDOUT.flush
|
||||
if !(STDIN.gets.chomp.downcase == "yes")
|
||||
puts "Aborting..."
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
# redis-trib subcommands implementations
|
||||
|
||||
def check_cluster_cmd
|
||||
node = ClusterNode.new(ARGV[1])
|
||||
node.connect(:abort => true)
|
||||
node.assert_cluster
|
||||
add_node(node)
|
||||
check_cluster
|
||||
end
|
||||
|
||||
def create_cluster_cmd
|
||||
puts "Creating cluster"
|
||||
ARGV[1..-1].each{|n|
|
||||
node = ClusterNode.new(n)
|
||||
node.connect(:abort => true)
|
||||
node.assert_cluster
|
||||
node.assert_empty
|
||||
add_node(node)
|
||||
}
|
||||
puts "Performing hash slots allocation on #{@nodes.length} nodes..."
|
||||
alloc_slots
|
||||
show_nodes
|
||||
yes_or_die "Can I set the above configuration?"
|
||||
flush_nodes_config
|
||||
puts "** Nodes configuration updated"
|
||||
puts "** Sending CLUSTER MEET messages to join the cluster"
|
||||
join_cluster
|
||||
check_cluster
|
||||
end
|
||||
end
|
||||
|
||||
COMMANDS={
|
||||
"create" => ["create_cluster_cmd", -2, "host1:port host2:port ... hostN:port"],
|
||||
"check" => ["check_cluster_cmd", 2, "host:port"]
|
||||
}
|
||||
|
||||
# Sanity check
|
||||
if ARGV.length == 0
|
||||
puts "Usage: redis-trib <command> <arguments ...>"
|
||||
puts
|
||||
COMMANDS.each{|k,v|
|
||||
puts " #{k.ljust(20)} #{v[2]}"
|
||||
}
|
||||
puts
|
||||
exit 1
|
||||
end
|
||||
|
||||
rt = RedisTrib.new
|
||||
cmd_spec = COMMANDS[ARGV[0].downcase]
|
||||
if !cmd_spec
|
||||
puts "Unknown redis-trib subcommand '#{ARGV[0]}'"
|
||||
exit 1
|
||||
end
|
||||
rt.check_arity(cmd_spec[1],ARGV.length)
|
||||
|
||||
# Dispatch
|
||||
rt.send(cmd_spec[0])
|
||||
+372
-627
File diff suppressed because it is too large
Load Diff
+239
-429
@@ -18,7 +18,6 @@
|
||||
#include <inttypes.h>
|
||||
#include <pthread.h>
|
||||
#include <syslog.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
#include "ae.h" /* Event driven programming library */
|
||||
#include "sds.h" /* Dynamic safe strings */
|
||||
@@ -50,8 +49,6 @@
|
||||
#define REDIS_SHARED_INTEGERS 10000
|
||||
#define REDIS_REPLY_CHUNK_BYTES (5*1500) /* 5 TCP packets with default MTU */
|
||||
#define REDIS_MAX_LOGMSG_LEN 1024 /* Default maximum length of syslog messages */
|
||||
#define REDIS_AUTO_AOFREWRITE_PERC 100
|
||||
#define REDIS_AUTO_AOFREWRITE_MIN_SIZE (1024*1024)
|
||||
|
||||
/* Hash table parameters */
|
||||
#define REDIS_HT_MINFILL 10 /* Minimal hash table fill 10% */
|
||||
@@ -124,18 +121,23 @@
|
||||
#define REDIS_RDB_ENC_INT32 2 /* 32 bit signed integer */
|
||||
#define REDIS_RDB_ENC_LZF 3 /* string compressed with FASTLZ */
|
||||
|
||||
/* Scheduled IO opeations flags. */
|
||||
#define REDIS_IO_LOAD 1
|
||||
#define REDIS_IO_SAVE 2
|
||||
#define REDIS_IO_LOADINPROG 4
|
||||
#define REDIS_IO_SAVEINPROG 8
|
||||
/* Virtual memory object->where field. */
|
||||
#define REDIS_VM_MEMORY 0 /* The object is on memory */
|
||||
#define REDIS_VM_SWAPPED 1 /* The object is on disk */
|
||||
#define REDIS_VM_SWAPPING 2 /* Redis is swapping this object on disk */
|
||||
#define REDIS_VM_LOADING 3 /* Redis is loading this object from disk */
|
||||
|
||||
/* Generic IO flags */
|
||||
#define REDIS_IO_ONLYLOADS 1
|
||||
#define REDIS_IO_ASAP 2
|
||||
|
||||
#define REDIS_MAX_COMPLETED_JOBS_PROCESSED 1
|
||||
/* Virtual memory static configuration stuff.
|
||||
* Check vmFindContiguousPages() to know more about this magic numbers. */
|
||||
#define REDIS_VM_MAX_NEAR_PAGES 65536
|
||||
#define REDIS_VM_MAX_RANDOM_JUMP 4096
|
||||
#define REDIS_VM_MAX_THREADS 32
|
||||
#define REDIS_THREAD_STACK_SIZE (1024*1024*4)
|
||||
/* The following is the *percentage* of completed I/O jobs to process when the
|
||||
* handelr is called. While Virtual Memory I/O operations are performed by
|
||||
* threads, this operations must be processed by the main thread when completed
|
||||
* in order to take effect. */
|
||||
#define REDIS_MAX_COMPLETED_JOBS_PROCESSED 1
|
||||
|
||||
/* Client flags */
|
||||
#define REDIS_SLAVE 1 /* This client is a slave server */
|
||||
@@ -187,7 +189,6 @@
|
||||
#define REDIS_VERBOSE 1
|
||||
#define REDIS_NOTICE 2
|
||||
#define REDIS_WARNING 3
|
||||
#define REDIS_LOG_RAW (1<<10) /* Modifier to log without timestamp */
|
||||
|
||||
/* Anti-warning macro... */
|
||||
#define REDIS_NOTUSED(V) ((void) V)
|
||||
@@ -222,12 +223,6 @@
|
||||
#define REDIS_MAXMEMORY_ALLKEYS_RANDOM 4
|
||||
#define REDIS_MAXMEMORY_NO_EVICTION 5
|
||||
|
||||
/* Diskstore background saving thread states */
|
||||
#define REDIS_BGSAVE_THREAD_UNACTIVE 0
|
||||
#define REDIS_BGSAVE_THREAD_ACTIVE 1
|
||||
#define REDIS_BGSAVE_THREAD_DONE_OK 2
|
||||
#define REDIS_BGSAVE_THREAD_DONE_ERR 3
|
||||
|
||||
/* We can print the stacktrace, so our assert is defined this way: */
|
||||
#define redisAssert(_e) ((_e)?(void)0 : (_redisAssert(#_e,__FILE__,__LINE__),_exit(1)))
|
||||
#define redisPanic(_e) _redisPanic(#_e,__FILE__,__LINE__),_exit(1)
|
||||
@@ -245,7 +240,7 @@ void _redisPanic(char *msg, char *file, int line);
|
||||
#define REDIS_LRU_CLOCK_RESOLUTION 10 /* LRU clock resolution in seconds */
|
||||
typedef struct redisObject {
|
||||
unsigned type:4;
|
||||
unsigned notused:2; /* Not used */
|
||||
unsigned storage:2; /* REDIS_VM_MEMORY or REDIS_VM_SWAPPING */
|
||||
unsigned encoding:4;
|
||||
unsigned lru:22; /* lru time (relative to server.lruclock) */
|
||||
int refcount;
|
||||
@@ -286,15 +281,14 @@ typedef struct vmPointer {
|
||||
_var.type = REDIS_STRING; \
|
||||
_var.encoding = REDIS_ENCODING_RAW; \
|
||||
_var.ptr = _ptr; \
|
||||
_var.storage = REDIS_VM_MEMORY; \
|
||||
} while(0);
|
||||
|
||||
typedef struct redisDb {
|
||||
dict *dict; /* The keyspace for this DB */
|
||||
dict *expires; /* Timeout of keys with a timeout set */
|
||||
dict *blocking_keys; /* Keys with clients waiting for data (BLPOP) */
|
||||
dict *io_keys; /* Keys with clients waiting for DS I/O */
|
||||
dict *io_negcache; /* Negative caching for disk store */
|
||||
dict *io_queued; /* Queued IO operations hash table */
|
||||
dict *io_keys; /* Keys with clients waiting for VM I/O */
|
||||
dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */
|
||||
int id;
|
||||
} redisDb;
|
||||
@@ -373,6 +367,191 @@ struct sharedObjectsStruct {
|
||||
*integers[REDIS_SHARED_INTEGERS];
|
||||
};
|
||||
|
||||
/* Global server state structure */
|
||||
struct redisServer {
|
||||
pthread_t mainthread;
|
||||
int port;
|
||||
char *bindaddr;
|
||||
char *unixsocket;
|
||||
int ipfd;
|
||||
int sofd;
|
||||
redisDb *db;
|
||||
long long dirty; /* changes to DB from the last save */
|
||||
long long dirty_before_bgsave; /* used to restore dirty on failed BGSAVE */
|
||||
list *clients;
|
||||
dict *commands; /* Command table hahs table */
|
||||
/* RDB / AOF loading information */
|
||||
int loading;
|
||||
off_t loading_total_bytes;
|
||||
off_t loading_loaded_bytes;
|
||||
time_t loading_start_time;
|
||||
/* Fast pointers to often looked up command */
|
||||
struct redisCommand *delCommand, *multiCommand;
|
||||
list *slaves, *monitors;
|
||||
char neterr[ANET_ERR_LEN];
|
||||
aeEventLoop *el;
|
||||
int cronloops; /* number of times the cron function run */
|
||||
time_t lastsave; /* Unix time of last save succeeede */
|
||||
/* Fields used only for stats */
|
||||
time_t stat_starttime; /* server start time */
|
||||
long long stat_numcommands; /* number of processed commands */
|
||||
long long stat_numconnections; /* number of connections received */
|
||||
long long stat_expiredkeys; /* number of expired keys */
|
||||
long long stat_evictedkeys; /* number of evicted keys (maxmemory) */
|
||||
long long stat_keyspace_hits; /* number of successful lookups of keys */
|
||||
long long stat_keyspace_misses; /* number of failed lookups of keys */
|
||||
size_t stat_peak_memory; /* max used memory record */
|
||||
long long stat_fork_time; /* time needed to perform latets fork() */
|
||||
/* Configuration */
|
||||
int verbosity;
|
||||
int maxidletime;
|
||||
int dbnum;
|
||||
int daemonize;
|
||||
int appendonly;
|
||||
int appendfsync;
|
||||
int no_appendfsync_on_rewrite;
|
||||
int shutdown_asap;
|
||||
time_t lastfsync;
|
||||
int appendfd;
|
||||
int appendseldb;
|
||||
char *pidfile;
|
||||
pid_t bgsavechildpid;
|
||||
pid_t bgrewritechildpid;
|
||||
sds bgrewritebuf; /* buffer taken by parent during oppend only rewrite */
|
||||
sds aofbuf; /* AOF buffer, written before entering the event loop */
|
||||
struct saveparam *saveparams;
|
||||
int saveparamslen;
|
||||
char *logfile;
|
||||
int syslog_enabled;
|
||||
char *syslog_ident;
|
||||
int syslog_facility;
|
||||
char *dbfilename;
|
||||
char *appendfilename;
|
||||
char *requirepass;
|
||||
int rdbcompression;
|
||||
int activerehashing;
|
||||
/* Replication related */
|
||||
int isslave;
|
||||
/* Slave specific fields */
|
||||
char *masterauth;
|
||||
char *masterhost;
|
||||
int masterport;
|
||||
redisClient *master; /* client that is master for this slave */
|
||||
int repl_syncio_timeout; /* timeout for synchronous I/O calls */
|
||||
int replstate; /* replication status if the instance is a slave */
|
||||
off_t repl_transfer_left; /* bytes left reading .rdb */
|
||||
int repl_transfer_s; /* slave -> master SYNC socket */
|
||||
int repl_transfer_fd; /* slave -> master SYNC temp file descriptor */
|
||||
char *repl_transfer_tmpfile; /* slave-> master SYNC temp file name */
|
||||
time_t repl_transfer_lastio; /* unix time of the latest read, for timeout */
|
||||
int repl_serve_stale_data; /* Serve stale data when link is down? */
|
||||
/* Limits */
|
||||
unsigned int maxclients;
|
||||
unsigned long long maxmemory;
|
||||
int maxmemory_policy;
|
||||
int maxmemory_samples;
|
||||
/* Blocked clients */
|
||||
unsigned int bpop_blocked_clients;
|
||||
unsigned int vm_blocked_clients;
|
||||
list *unblocked_clients;
|
||||
/* Sort parameters - qsort_r() is only available under BSD so we
|
||||
* have to take this state global, in order to pass it to sortCompare() */
|
||||
int sort_desc;
|
||||
int sort_alpha;
|
||||
int sort_bypattern;
|
||||
/* Virtual memory configuration */
|
||||
int vm_enabled;
|
||||
char *vm_swap_file;
|
||||
off_t vm_page_size;
|
||||
off_t vm_pages;
|
||||
unsigned long long vm_max_memory;
|
||||
/* Zip structure config */
|
||||
size_t hash_max_zipmap_entries;
|
||||
size_t hash_max_zipmap_value;
|
||||
size_t list_max_ziplist_entries;
|
||||
size_t list_max_ziplist_value;
|
||||
size_t set_max_intset_entries;
|
||||
size_t zset_max_ziplist_entries;
|
||||
size_t zset_max_ziplist_value;
|
||||
/* Virtual memory state */
|
||||
FILE *vm_fp;
|
||||
int vm_fd;
|
||||
off_t vm_next_page; /* Next probably empty page */
|
||||
off_t vm_near_pages; /* Number of pages allocated sequentially */
|
||||
unsigned char *vm_bitmap; /* Bitmap of free/used pages */
|
||||
time_t unixtime; /* Unix time sampled every second. */
|
||||
/* Virtual memory I/O threads stuff */
|
||||
/* An I/O thread process an element taken from the io_jobs queue and
|
||||
* put the result of the operation in the io_done list. While the
|
||||
* job is being processed, it's put on io_processing queue. */
|
||||
list *io_newjobs; /* List of VM I/O jobs yet to be processed */
|
||||
list *io_processing; /* List of VM I/O jobs being processed */
|
||||
list *io_processed; /* List of VM I/O jobs already processed */
|
||||
list *io_ready_clients; /* Clients ready to be unblocked. All keys loaded */
|
||||
pthread_mutex_t io_mutex; /* lock to access io_jobs/io_done/io_thread_job */
|
||||
pthread_mutex_t io_swapfile_mutex; /* So we can lseek + write */
|
||||
pthread_attr_t io_threads_attr; /* attributes for threads creation */
|
||||
int io_active_threads; /* Number of running I/O threads */
|
||||
int vm_max_threads; /* Max number of I/O threads running at the same time */
|
||||
/* Our main thread is blocked on the event loop, locking for sockets ready
|
||||
* to be read or written, so when a threaded I/O operation is ready to be
|
||||
* processed by the main thread, the I/O thread will use a unix pipe to
|
||||
* awake the main thread. The followings are the two pipe FDs. */
|
||||
int io_ready_pipe_read;
|
||||
int io_ready_pipe_write;
|
||||
/* Virtual memory stats */
|
||||
unsigned long long vm_stats_used_pages;
|
||||
unsigned long long vm_stats_swapped_objects;
|
||||
unsigned long long vm_stats_swapouts;
|
||||
unsigned long long vm_stats_swapins;
|
||||
/* Pubsub */
|
||||
dict *pubsub_channels; /* Map channels to list of subscribed clients */
|
||||
list *pubsub_patterns; /* A list of pubsub_patterns */
|
||||
/* Misc */
|
||||
unsigned lruclock:22; /* clock incrementing every minute, for LRU */
|
||||
unsigned lruclock_padding:10;
|
||||
};
|
||||
|
||||
typedef struct pubsubPattern {
|
||||
redisClient *client;
|
||||
robj *pattern;
|
||||
} pubsubPattern;
|
||||
|
||||
typedef void redisCommandProc(redisClient *c);
|
||||
typedef void redisVmPreloadProc(redisClient *c, struct redisCommand *cmd, int argc, robj **argv);
|
||||
struct redisCommand {
|
||||
char *name;
|
||||
redisCommandProc *proc;
|
||||
int arity;
|
||||
int flags;
|
||||
/* Use a function to determine which keys need to be loaded
|
||||
* in the background prior to executing this command. Takes precedence
|
||||
* over vm_firstkey and others, ignored when NULL */
|
||||
redisVmPreloadProc *vm_preload_proc;
|
||||
/* What keys should be loaded in background when calling this command? */
|
||||
int vm_firstkey; /* The first argument that's a key (0 = no keys) */
|
||||
int vm_lastkey; /* THe last argument that's a key */
|
||||
int vm_keystep; /* The step between first and last key */
|
||||
};
|
||||
|
||||
struct redisFunctionSym {
|
||||
char *name;
|
||||
unsigned long pointer;
|
||||
};
|
||||
|
||||
typedef struct _redisSortObject {
|
||||
robj *obj;
|
||||
union {
|
||||
double score;
|
||||
robj *cmpobj;
|
||||
} u;
|
||||
} redisSortObject;
|
||||
|
||||
typedef struct _redisSortOperation {
|
||||
int type;
|
||||
robj *pattern;
|
||||
} redisSortOperation;
|
||||
|
||||
/* ZSETs use a specialized version of Skiplists */
|
||||
typedef struct zskiplistNode {
|
||||
robj *obj;
|
||||
@@ -395,341 +574,25 @@ typedef struct zset {
|
||||
zskiplist *zsl;
|
||||
} zset;
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Redis cluster data structures
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
#define REDIS_CLUSTER_SLOTS 4096
|
||||
#define REDIS_CLUSTER_OK 0 /* Everything looks ok */
|
||||
#define REDIS_CLUSTER_FAIL 1 /* The cluster can't work */
|
||||
#define REDIS_CLUSTER_NEEDHELP 2 /* The cluster works, but needs some help */
|
||||
#define REDIS_CLUSTER_NAMELEN 40 /* sha1 hex length */
|
||||
#define REDIS_CLUSTER_PORT_INCR 10000 /* Cluster port = baseport + PORT_INCR */
|
||||
|
||||
struct clusterNode;
|
||||
|
||||
/* clusterLink encapsulates everything needed to talk with a remote node. */
|
||||
typedef struct clusterLink {
|
||||
int fd; /* TCP socket file descriptor */
|
||||
sds sndbuf; /* Packet send buffer */
|
||||
sds rcvbuf; /* Packet reception buffer */
|
||||
struct clusterNode *node; /* Node related to this link if any, or NULL */
|
||||
} clusterLink;
|
||||
|
||||
/* Node flags */
|
||||
#define REDIS_NODE_MASTER 1 /* The node is a master */
|
||||
#define REDIS_NODE_SLAVE 2 /* The node is a slave */
|
||||
#define REDIS_NODE_PFAIL 4 /* Failure? Need acknowledge */
|
||||
#define REDIS_NODE_FAIL 8 /* The node is believed to be malfunctioning */
|
||||
#define REDIS_NODE_MYSELF 16 /* This node is myself */
|
||||
#define REDIS_NODE_HANDSHAKE 32 /* We have still to exchange the first ping */
|
||||
#define REDIS_NODE_NOADDR 64 /* We don't know the address of this node */
|
||||
#define REDIS_NODE_MEET 128 /* Send a MEET message to this node */
|
||||
#define REDIS_NODE_NULL_NAME "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
|
||||
struct clusterNode {
|
||||
char name[REDIS_CLUSTER_NAMELEN]; /* Node name, hex string, sha1-size */
|
||||
int flags; /* REDIS_NODE_... */
|
||||
unsigned char slots[REDIS_CLUSTER_SLOTS/8]; /* slots handled by this node */
|
||||
int numslaves; /* Number of slave nodes, if this is a master */
|
||||
struct clusterNode **slaves; /* pointers to slave nodes */
|
||||
struct clusterNode *slaveof; /* pointer to the master node */
|
||||
time_t ping_sent; /* Unix time we sent latest ping */
|
||||
time_t pong_received; /* Unix time we received the pong */
|
||||
char *configdigest; /* Configuration digest of this node */
|
||||
time_t configdigest_ts; /* Configuration digest timestamp */
|
||||
char ip[16]; /* Latest known IP address of this node */
|
||||
int port; /* Latest known port of this node */
|
||||
clusterLink *link; /* TCP/IP link with this node */
|
||||
};
|
||||
typedef struct clusterNode clusterNode;
|
||||
|
||||
typedef struct {
|
||||
char *configfile;
|
||||
clusterNode *myself; /* This node */
|
||||
int state; /* REDIS_CLUSTER_OK, REDIS_CLUSTER_FAIL, ... */
|
||||
int node_timeout;
|
||||
dict *nodes; /* Hash table of name -> clusterNode structures */
|
||||
clusterNode *migrating_slots_to[REDIS_CLUSTER_SLOTS];
|
||||
clusterNode *importing_slots_from[REDIS_CLUSTER_SLOTS];
|
||||
clusterNode *slots[REDIS_CLUSTER_SLOTS];
|
||||
zskiplist *slots_to_keys;
|
||||
} clusterState;
|
||||
|
||||
/* Redis cluster messages header */
|
||||
|
||||
/* Note that the PING, PONG and MEET messages are actually the same exact
|
||||
* kind of packet. PONG is the reply to ping, in the extact format as a PING,
|
||||
* while MEET is a special PING that forces the receiver to add the sender
|
||||
* as a node (if it is not already in the list). */
|
||||
#define CLUSTERMSG_TYPE_PING 0 /* Ping */
|
||||
#define CLUSTERMSG_TYPE_PONG 1 /* Pong (reply to Ping) */
|
||||
#define CLUSTERMSG_TYPE_MEET 2 /* Meet "let's join" message */
|
||||
#define CLUSTERMSG_TYPE_FAIL 3 /* Mark node xxx as failing */
|
||||
|
||||
/* Initially we don't know our "name", but we'll find it once we connect
|
||||
* to the first node, using the getsockname() function. Then we'll use this
|
||||
* address for all the next messages. */
|
||||
typedef struct {
|
||||
char nodename[REDIS_CLUSTER_NAMELEN];
|
||||
uint32_t ping_sent;
|
||||
uint32_t pong_received;
|
||||
char ip[16]; /* IP address last time it was seen */
|
||||
uint16_t port; /* port last time it was seen */
|
||||
uint16_t flags;
|
||||
uint32_t notused; /* for 64 bit alignment */
|
||||
} clusterMsgDataGossip;
|
||||
|
||||
typedef struct {
|
||||
char nodename[REDIS_CLUSTER_NAMELEN];
|
||||
} clusterMsgDataFail;
|
||||
|
||||
union clusterMsgData {
|
||||
/* PING, MEET and PONG */
|
||||
struct {
|
||||
/* Array of N clusterMsgDataGossip structures */
|
||||
clusterMsgDataGossip gossip[1];
|
||||
} ping;
|
||||
/* FAIL */
|
||||
struct {
|
||||
clusterMsgDataFail about;
|
||||
} fail;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint32_t totlen; /* Total length of this message */
|
||||
uint16_t type; /* Message type */
|
||||
uint16_t count; /* Only used for some kind of messages. */
|
||||
char sender[REDIS_CLUSTER_NAMELEN]; /* Name of the sender node */
|
||||
unsigned char myslots[REDIS_CLUSTER_SLOTS/8];
|
||||
char slaveof[REDIS_CLUSTER_NAMELEN];
|
||||
char configdigest[32];
|
||||
uint16_t port; /* Sender TCP base port */
|
||||
unsigned char state; /* Cluster state from the POV of the sender */
|
||||
unsigned char notused[5]; /* Reserved for future use. For alignment. */
|
||||
union clusterMsgData data;
|
||||
} clusterMsg;
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Global server state
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
struct redisServer {
|
||||
/* General */
|
||||
pthread_t mainthread;
|
||||
redisDb *db;
|
||||
dict *commands; /* Command table hahs table */
|
||||
aeEventLoop *el;
|
||||
/* Networking */
|
||||
int port;
|
||||
char *bindaddr;
|
||||
char *unixsocket;
|
||||
int ipfd;
|
||||
int sofd;
|
||||
int cfd;
|
||||
list *clients;
|
||||
list *slaves, *monitors;
|
||||
char neterr[ANET_ERR_LEN];
|
||||
/* RDB / AOF loading information */
|
||||
int loading;
|
||||
off_t loading_total_bytes;
|
||||
off_t loading_loaded_bytes;
|
||||
time_t loading_start_time;
|
||||
/* Fast pointers to often looked up command */
|
||||
struct redisCommand *delCommand, *multiCommand;
|
||||
int cronloops; /* number of times the cron function run */
|
||||
time_t lastsave; /* Unix time of last save succeeede */
|
||||
/* Fields used only for stats */
|
||||
time_t stat_starttime; /* server start time */
|
||||
long long stat_numcommands; /* number of processed commands */
|
||||
long long stat_numconnections; /* number of connections received */
|
||||
long long stat_expiredkeys; /* number of expired keys */
|
||||
long long stat_evictedkeys; /* number of evicted keys (maxmemory) */
|
||||
long long stat_keyspace_hits; /* number of successful lookups of keys */
|
||||
long long stat_keyspace_misses; /* number of failed lookups of keys */
|
||||
size_t stat_peak_memory; /* max used memory record */
|
||||
long long stat_fork_time; /* time needed to perform latets fork() */
|
||||
/* Configuration */
|
||||
int verbosity;
|
||||
int maxidletime;
|
||||
int dbnum;
|
||||
int daemonize;
|
||||
int appendonly;
|
||||
int appendfsync;
|
||||
int no_appendfsync_on_rewrite;
|
||||
int auto_aofrewrite_perc; /* Rewrite AOF if % growth is > M and... */
|
||||
off_t auto_aofrewrite_min_size; /* the AOF file is at least N bytes. */
|
||||
off_t auto_aofrewrite_base_size;/* AOF size on latest startup or rewrite. */
|
||||
off_t appendonly_current_size; /* AOF current size. */
|
||||
int aofrewrite_scheduled; /* Rewrite once BGSAVE terminates. */
|
||||
int shutdown_asap;
|
||||
int activerehashing;
|
||||
char *requirepass;
|
||||
/* Persistence */
|
||||
long long dirty; /* changes to DB from the last save */
|
||||
long long dirty_before_bgsave; /* used to restore dirty on failed BGSAVE */
|
||||
time_t lastfsync;
|
||||
int appendfd;
|
||||
int appendseldb;
|
||||
char *pidfile;
|
||||
pid_t bgsavechildpid;
|
||||
pid_t bgrewritechildpid;
|
||||
int bgsavethread_state;
|
||||
pthread_mutex_t bgsavethread_mutex;
|
||||
pthread_t bgsavethread;
|
||||
sds bgrewritebuf; /* buffer taken by parent during oppend only rewrite */
|
||||
sds aofbuf; /* AOF buffer, written before entering the event loop */
|
||||
struct saveparam *saveparams;
|
||||
int saveparamslen;
|
||||
char *dbfilename;
|
||||
int rdbcompression;
|
||||
char *appendfilename;
|
||||
/* Logging */
|
||||
char *logfile;
|
||||
int syslog_enabled;
|
||||
char *syslog_ident;
|
||||
int syslog_facility;
|
||||
/* Replication related */
|
||||
int isslave;
|
||||
/* Slave specific fields */
|
||||
char *masterauth;
|
||||
char *masterhost;
|
||||
int masterport;
|
||||
redisClient *master; /* client that is master for this slave */
|
||||
int repl_syncio_timeout; /* timeout for synchronous I/O calls */
|
||||
int replstate; /* replication status if the instance is a slave */
|
||||
off_t repl_transfer_left; /* bytes left reading .rdb */
|
||||
int repl_transfer_s; /* slave -> master SYNC socket */
|
||||
int repl_transfer_fd; /* slave -> master SYNC temp file descriptor */
|
||||
char *repl_transfer_tmpfile; /* slave-> master SYNC temp file name */
|
||||
time_t repl_transfer_lastio; /* unix time of the latest read, for timeout */
|
||||
int repl_serve_stale_data; /* Serve stale data when link is down? */
|
||||
time_t repl_down_since; /* unix time at which link with master went down */
|
||||
/* Limits */
|
||||
unsigned int maxclients;
|
||||
unsigned long long maxmemory;
|
||||
int maxmemory_policy;
|
||||
int maxmemory_samples;
|
||||
/* Blocked clients */
|
||||
unsigned int bpop_blocked_clients;
|
||||
unsigned int cache_blocked_clients;
|
||||
list *unblocked_clients; /* list of clients to unblock before next loop */
|
||||
list *cache_io_queue; /* IO operations queue */
|
||||
int cache_flush_delay; /* seconds to wait before flushing keys */
|
||||
/* Sort parameters - qsort_r() is only available under BSD so we
|
||||
* have to take this state global, in order to pass it to sortCompare() */
|
||||
int sort_desc;
|
||||
int sort_alpha;
|
||||
int sort_bypattern;
|
||||
/* Virtual memory configuration */
|
||||
int ds_enabled; /* backend disk in redis.conf */
|
||||
char *ds_path; /* location of the disk store on disk */
|
||||
unsigned long long cache_max_memory;
|
||||
/* Zip structure config */
|
||||
size_t hash_max_zipmap_entries;
|
||||
size_t hash_max_zipmap_value;
|
||||
size_t list_max_ziplist_entries;
|
||||
size_t list_max_ziplist_value;
|
||||
size_t set_max_intset_entries;
|
||||
size_t zset_max_ziplist_entries;
|
||||
size_t zset_max_ziplist_value;
|
||||
time_t unixtime; /* Unix time sampled every second. */
|
||||
/* Virtual memory I/O threads stuff */
|
||||
/* An I/O thread process an element taken from the io_jobs queue and
|
||||
* put the result of the operation in the io_done list. While the
|
||||
* job is being processed, it's put on io_processing queue. */
|
||||
list *io_newjobs; /* List of VM I/O jobs yet to be processed */
|
||||
list *io_processing; /* List of VM I/O jobs being processed */
|
||||
list *io_processed; /* List of VM I/O jobs already processed */
|
||||
list *io_ready_clients; /* Clients ready to be unblocked. All keys loaded */
|
||||
pthread_mutex_t io_mutex; /* lock to access io_jobs/io_done/io_thread_job */
|
||||
pthread_cond_t io_condvar; /* I/O threads conditional variable */
|
||||
pthread_attr_t io_threads_attr; /* attributes for threads creation */
|
||||
int io_active_threads; /* Number of running I/O threads */
|
||||
int vm_max_threads; /* Max number of I/O threads running at the same time */
|
||||
/* Our main thread is blocked on the event loop, locking for sockets ready
|
||||
* to be read or written, so when a threaded I/O operation is ready to be
|
||||
* processed by the main thread, the I/O thread will use a unix pipe to
|
||||
* awake the main thread. The followings are the two pipe FDs. */
|
||||
int io_ready_pipe_read;
|
||||
int io_ready_pipe_write;
|
||||
/* Virtual memory stats */
|
||||
unsigned long long vm_stats_used_pages;
|
||||
unsigned long long vm_stats_swapped_objects;
|
||||
unsigned long long vm_stats_swapouts;
|
||||
unsigned long long vm_stats_swapins;
|
||||
/* Pubsub */
|
||||
dict *pubsub_channels; /* Map channels to list of subscribed clients */
|
||||
list *pubsub_patterns; /* A list of pubsub_patterns */
|
||||
/* Misc */
|
||||
unsigned lruclock:22; /* clock incrementing every minute, for LRU */
|
||||
unsigned lruclock_padding:10;
|
||||
/* Cluster */
|
||||
int cluster_enabled;
|
||||
clusterState cluster;
|
||||
};
|
||||
|
||||
typedef struct pubsubPattern {
|
||||
redisClient *client;
|
||||
robj *pattern;
|
||||
} pubsubPattern;
|
||||
|
||||
typedef void redisCommandProc(redisClient *c);
|
||||
typedef int *redisGetKeysProc(struct redisCommand *cmd, robj **argv, int argc, int *numkeys, int flags);
|
||||
struct redisCommand {
|
||||
char *name;
|
||||
redisCommandProc *proc;
|
||||
int arity;
|
||||
int flags;
|
||||
/* Use a function to determine keys arguments in a command line.
|
||||
* Used both for diskstore preloading and Redis Cluster. */
|
||||
redisGetKeysProc *getkeys_proc;
|
||||
/* What keys should be loaded in background when calling this command? */
|
||||
int firstkey; /* The first argument that's a key (0 = no keys) */
|
||||
int lastkey; /* THe last argument that's a key */
|
||||
int keystep; /* The step between first and last key */
|
||||
long long microseconds, calls;
|
||||
};
|
||||
|
||||
struct redisFunctionSym {
|
||||
char *name;
|
||||
unsigned long pointer;
|
||||
};
|
||||
|
||||
typedef struct _redisSortObject {
|
||||
robj *obj;
|
||||
union {
|
||||
double score;
|
||||
robj *cmpobj;
|
||||
} u;
|
||||
} redisSortObject;
|
||||
|
||||
typedef struct _redisSortOperation {
|
||||
int type;
|
||||
robj *pattern;
|
||||
} redisSortOperation;
|
||||
|
||||
/* DIsk store threaded I/O request message */
|
||||
#define REDIS_IOJOB_LOAD 0
|
||||
#define REDIS_IOJOB_SAVE 1
|
||||
|
||||
/* VM threaded I/O request message */
|
||||
#define REDIS_IOJOB_LOAD 0 /* Load from disk to memory */
|
||||
#define REDIS_IOJOB_PREPARE_SWAP 1 /* Compute needed pages */
|
||||
#define REDIS_IOJOB_DO_SWAP 2 /* Swap from memory to disk */
|
||||
typedef struct iojob {
|
||||
int type; /* Request type, REDIS_IOJOB_* */
|
||||
redisDb *db;/* Redis database */
|
||||
robj *key; /* This I/O request is about this key */
|
||||
robj *val; /* the value to swap for REDIS_IOJOB_SAVE, otherwise this
|
||||
* field is populated by the I/O thread for REDIS_IOJOB_LOAD. */
|
||||
time_t expire; /* Expire time for this key on REDIS_IOJOB_LOAD */
|
||||
robj *key; /* This I/O request is about swapping this key */
|
||||
robj *id; /* Unique identifier of this job:
|
||||
this is the object to swap for REDIS_IOREQ_*_SWAP, or the
|
||||
vmpointer objct for REDIS_IOREQ_LOAD. */
|
||||
robj *val; /* the value to swap for REDIS_IOREQ_*_SWAP, otherwise this
|
||||
* field is populated by the I/O thread for REDIS_IOREQ_LOAD. */
|
||||
off_t page; /* Swap page where to read/write the object */
|
||||
off_t pages; /* Swap pages needed to save object. PREPARE_SWAP return val */
|
||||
int canceled; /* True if this command was canceled by blocking side of VM */
|
||||
pthread_t thread; /* ID of the thread processing this entry */
|
||||
} iojob;
|
||||
|
||||
/* IO operations scheduled -- check dscache.c for more info */
|
||||
typedef struct ioop {
|
||||
int type;
|
||||
redisDb *db;
|
||||
robj *key;
|
||||
time_t ctime; /* This is the creation time of the entry. */
|
||||
} ioop;
|
||||
|
||||
/* Structure to hold list iteration abstraction. */
|
||||
typedef struct {
|
||||
robj *subject;
|
||||
@@ -779,7 +642,6 @@ extern struct redisServer server;
|
||||
extern struct sharedObjectsStruct shared;
|
||||
extern dictType setDictType;
|
||||
extern dictType zsetDictType;
|
||||
extern dictType clusterNodesDictType;
|
||||
extern double R_Zero, R_PosInf, R_NegInf, R_Nan;
|
||||
dictType hashDictType;
|
||||
|
||||
@@ -787,9 +649,6 @@ dictType hashDictType;
|
||||
* Functions prototypes
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
/* Utils */
|
||||
long long ustime(void);
|
||||
|
||||
/* networking.c -- Networking and Client related operations */
|
||||
redisClient *createClient(int fd);
|
||||
void closeTimedoutClients(void);
|
||||
@@ -897,7 +756,6 @@ int fwriteBulkString(FILE *fp, char *s, unsigned long len);
|
||||
int fwriteBulkDouble(FILE *fp, double d);
|
||||
int fwriteBulkLongLong(FILE *fp, long long l);
|
||||
int fwriteBulkObject(FILE *fp, robj *obj);
|
||||
int fwriteBulkCount(FILE *fp, char prefix, int count);
|
||||
|
||||
/* Replication */
|
||||
void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc);
|
||||
@@ -919,13 +777,8 @@ int rdbSaveObject(FILE *fp, robj *o);
|
||||
off_t rdbSavedObjectLen(robj *o);
|
||||
off_t rdbSavedObjectPages(robj *o);
|
||||
robj *rdbLoadObject(int type, FILE *fp);
|
||||
void backgroundSaveDoneHandler(int exitcode, int bysignal);
|
||||
int rdbSaveKeyValuePair(FILE *fp, robj *key, robj *val, time_t expireitme, time_t now);
|
||||
int rdbLoadType(FILE *fp);
|
||||
time_t rdbLoadTime(FILE *fp);
|
||||
robj *rdbLoadStringObject(FILE *fp);
|
||||
int rdbSaveType(FILE *fp, unsigned char type);
|
||||
int rdbSaveLen(FILE *fp, uint32_t len);
|
||||
void backgroundSaveDoneHandler(int statloc);
|
||||
int getObjectSaveType(robj *o);
|
||||
|
||||
/* AOF persistence */
|
||||
void flushAppendOnlyFile(void);
|
||||
@@ -935,22 +788,13 @@ int rewriteAppendOnlyFileBackground(void);
|
||||
int loadAppendOnlyFile(char *filename);
|
||||
void stopAppendOnly(void);
|
||||
int startAppendOnly(void);
|
||||
void backgroundRewriteDoneHandler(int exitcode, int bysignal);
|
||||
void backgroundRewriteDoneHandler(int statloc);
|
||||
|
||||
/* Sorted sets data type */
|
||||
|
||||
/* Struct to hold a inclusive/exclusive range spec. */
|
||||
typedef struct {
|
||||
double min, max;
|
||||
int minex, maxex; /* are min or max exclusive? */
|
||||
} zrangespec;
|
||||
|
||||
zskiplist *zslCreate(void);
|
||||
void zslFree(zskiplist *zsl);
|
||||
zskiplistNode *zslInsert(zskiplist *zsl, double score, robj *obj);
|
||||
unsigned char *zzlInsert(unsigned char *zl, robj *ele, double score);
|
||||
int zslDelete(zskiplist *zsl, double score, robj *obj);
|
||||
zskiplistNode *zslFirstInRange(zskiplist *zsl, zrangespec range);
|
||||
double zzlGetScore(unsigned char *sptr);
|
||||
void zzlNext(unsigned char *zl, unsigned char **eptr, unsigned char **sptr);
|
||||
void zzlPrev(unsigned char *zl, unsigned char **eptr, unsigned char **sptr);
|
||||
@@ -971,41 +815,33 @@ void updateDictResizePolicy(void);
|
||||
int htNeedsResize(dict *dict);
|
||||
void oom(const char *msg);
|
||||
void populateCommandTable(void);
|
||||
void resetCommandTableStats(void);
|
||||
|
||||
/* Disk store */
|
||||
int dsOpen(void);
|
||||
int dsClose(void);
|
||||
int dsSet(redisDb *db, robj *key, robj *val, time_t expire);
|
||||
robj *dsGet(redisDb *db, robj *key, time_t *expire);
|
||||
int dsDel(redisDb *db, robj *key);
|
||||
int dsExists(redisDb *db, robj *key);
|
||||
void dsFlushDb(int dbid);
|
||||
int dsRdbSaveBackground(char *filename);
|
||||
int dsRdbSave(char *filename);
|
||||
|
||||
/* Disk Store Cache */
|
||||
void dsInit(void);
|
||||
/* Virtual Memory */
|
||||
void vmInit(void);
|
||||
void vmMarkPagesFree(off_t page, off_t count);
|
||||
robj *vmLoadObject(robj *o);
|
||||
robj *vmPreviewObject(robj *o);
|
||||
int vmSwapOneObjectBlocking(void);
|
||||
int vmSwapOneObjectThreaded(void);
|
||||
int vmCanSwapOut(void);
|
||||
void vmThreadedIOCompletedJob(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
void vmCancelThreadedIOJob(robj *o);
|
||||
void lockThreadedIO(void);
|
||||
void unlockThreadedIO(void);
|
||||
int vmSwapObjectThreaded(robj *key, robj *val, redisDb *db);
|
||||
void freeIOJob(iojob *j);
|
||||
void queueIOJob(iojob *j);
|
||||
int vmWriteObjectOnSwap(robj *o, off_t page);
|
||||
robj *vmReadObjectFromSwap(off_t page, int type);
|
||||
void waitEmptyIOJobsQueue(void);
|
||||
void processAllPendingIOJobs(void);
|
||||
void vmReopenSwapFile(void);
|
||||
int vmFreePage(off_t page);
|
||||
void zunionInterBlockClientOnSwappedKeys(redisClient *c, struct redisCommand *cmd, int argc, robj **argv);
|
||||
void execBlockClientOnSwappedKeys(redisClient *c, struct redisCommand *cmd, int argc, robj **argv);
|
||||
int blockClientOnSwappedKeys(redisClient *c, struct redisCommand *cmd);
|
||||
int dontWaitForSwappedKey(redisClient *c, robj *key);
|
||||
void handleClientsBlockedOnSwappedKey(redisDb *db, robj *key);
|
||||
int cacheFreeOneEntry(void);
|
||||
void cacheScheduleIOAddFlag(redisDb *db, robj *key, long flag);
|
||||
void cacheScheduleIODelFlag(redisDb *db, robj *key, long flag);
|
||||
int cacheScheduleIOGetFlags(redisDb *db, robj *key);
|
||||
void cacheScheduleIO(redisDb *db, robj *key, int type);
|
||||
void cacheCron(void);
|
||||
int cacheKeyMayExist(redisDb *db, robj *key);
|
||||
void cacheSetKeyMayExist(redisDb *db, robj *key);
|
||||
void cacheSetKeyDoesNotExist(redisDb *db, robj *key);
|
||||
void cacheForcePointInTime(void);
|
||||
vmpointer *vmSwapObjectBlocking(robj *val);
|
||||
|
||||
/* Set data type */
|
||||
robj *setTypeCreate(robj *value);
|
||||
@@ -1059,35 +895,13 @@ robj *lookupKeyRead(redisDb *db, robj *key);
|
||||
robj *lookupKeyWrite(redisDb *db, robj *key);
|
||||
robj *lookupKeyReadOrReply(redisClient *c, robj *key, robj *reply);
|
||||
robj *lookupKeyWriteOrReply(redisClient *c, robj *key, robj *reply);
|
||||
void dbAdd(redisDb *db, robj *key, robj *val);
|
||||
void dbOverwrite(redisDb *db, robj *key, robj *val);
|
||||
void setKey(redisDb *db, robj *key, robj *val);
|
||||
int dbAdd(redisDb *db, robj *key, robj *val);
|
||||
int dbReplace(redisDb *db, robj *key, robj *val);
|
||||
int dbExists(redisDb *db, robj *key);
|
||||
robj *dbRandomKey(redisDb *db);
|
||||
int dbDelete(redisDb *db, robj *key);
|
||||
long long emptyDb();
|
||||
int selectDb(redisClient *c, int id);
|
||||
void signalModifiedKey(redisDb *db, robj *key);
|
||||
void signalFlushedDb(int dbid);
|
||||
unsigned int GetKeysInSlot(unsigned int hashslot, robj **keys, unsigned int count);
|
||||
|
||||
/* API to get key arguments from commands */
|
||||
#define REDIS_GETKEYS_ALL 0
|
||||
#define REDIS_GETKEYS_PRELOAD 1
|
||||
int *getKeysFromCommand(struct redisCommand *cmd, robj **argv, int argc, int *numkeys, int flags);
|
||||
void getKeysFreeResult(int *result);
|
||||
int *noPreloadGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags);
|
||||
int *renameGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags);
|
||||
int *zunionInterGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags);
|
||||
|
||||
/* Cluster */
|
||||
void clusterInit(void);
|
||||
unsigned short crc16(const char *buf, int len);
|
||||
unsigned int keyHashSlot(char *key, int keylen);
|
||||
clusterNode *createClusterNode(char *nodename, int flags);
|
||||
int clusterAddNode(clusterNode *node);
|
||||
void clusterCron(void);
|
||||
clusterNode *getNodeByQuery(redisClient *c, struct redisCommand *cmd, robj **argv, int argc, int *hashslot, int *ask);
|
||||
|
||||
/* Git SHA1 */
|
||||
char *redisGitSHA1(void);
|
||||
@@ -1211,10 +1025,6 @@ void punsubscribeCommand(redisClient *c);
|
||||
void publishCommand(redisClient *c);
|
||||
void watchCommand(redisClient *c);
|
||||
void unwatchCommand(redisClient *c);
|
||||
void clusterCommand(redisClient *c);
|
||||
void restoreCommand(redisClient *c);
|
||||
void migrateCommand(redisClient *c);
|
||||
void dumpCommand(redisClient *c);
|
||||
void objectCommand(redisClient *c);
|
||||
void clientCommand(redisClient *c);
|
||||
|
||||
|
||||
+3
-5
@@ -358,8 +358,6 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
server.master->authenticated = 1;
|
||||
server.replstate = REDIS_REPL_CONNECTED;
|
||||
redisLog(REDIS_NOTICE, "MASTER <-> SLAVE sync: Finished with success");
|
||||
/* Rewrite the AOF file now that the dataset changed. */
|
||||
if (server.appendonly) rewriteAppendOnlyFileBackground();
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -376,11 +374,10 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
REDIS_NOTUSED(privdata);
|
||||
REDIS_NOTUSED(mask);
|
||||
|
||||
redisLog(REDIS_NOTICE,"Non blocking connect for SYNC fired the event.");
|
||||
/* This event should only be triggered once since it is used to have a
|
||||
* non-blocking connect(2) to the master. It has been triggered when this
|
||||
* function is called, so we can delete it. */
|
||||
aeDeleteFileEvent(server.el,fd,AE_READABLE|AE_WRITABLE);
|
||||
aeDeleteFileEvent(server.el,fd,AE_WRITABLE);
|
||||
|
||||
/* AUTH with the master if required. */
|
||||
if(server.masterauth) {
|
||||
@@ -456,7 +453,7 @@ int connectWithMaster(void) {
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
if (aeCreateFileEvent(server.el,fd,AE_READABLE|AE_WRITABLE,syncWithMaster,NULL) ==
|
||||
if (aeCreateFileEvent(server.el,fd,AE_WRITABLE,syncWithMaster,NULL) ==
|
||||
AE_ERR)
|
||||
{
|
||||
close(fd);
|
||||
@@ -522,6 +519,7 @@ void replicationCron(void) {
|
||||
redisLog(REDIS_NOTICE,"Connecting to MASTER...");
|
||||
if (connectWithMaster() == REDIS_OK) {
|
||||
redisLog(REDIS_NOTICE,"MASTER <-> SLAVE sync started");
|
||||
if (server.appendonly) rewriteAppendOnlyFileBackground();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,11 +51,7 @@ static void sdsOomAbort(void) {
|
||||
sds sdsnewlen(const void *init, size_t initlen) {
|
||||
struct sdshdr *sh;
|
||||
|
||||
if (init) {
|
||||
sh = zmalloc(sizeof(struct sdshdr)+initlen+1);
|
||||
} else {
|
||||
sh = zcalloc(sizeof(struct sdshdr)+initlen+1);
|
||||
}
|
||||
sh = zmalloc(sizeof(struct sdshdr)+initlen+1);
|
||||
#ifdef SDS_ABORT_ON_OOM
|
||||
if (sh == NULL) sdsOomAbort();
|
||||
#else
|
||||
@@ -63,8 +59,10 @@ sds sdsnewlen(const void *init, size_t initlen) {
|
||||
#endif
|
||||
sh->len = initlen;
|
||||
sh->free = 0;
|
||||
if (initlen && init)
|
||||
memcpy(sh->buf, init, initlen);
|
||||
if (initlen) {
|
||||
if (init) memcpy(sh->buf, init, initlen);
|
||||
else memset(sh->buf,0,initlen);
|
||||
}
|
||||
sh->buf[initlen] = '\0';
|
||||
return (char*)sh->buf;
|
||||
}
|
||||
@@ -363,7 +361,6 @@ cleanup:
|
||||
int i;
|
||||
for (i = 0; i < elements; i++) sdsfree(tokens[i]);
|
||||
zfree(tokens);
|
||||
*count = 0;
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
@@ -546,13 +543,6 @@ err:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void sdssplitargs_free(sds *argv, int argc) {
|
||||
int j;
|
||||
|
||||
for (j = 0 ;j < argc; j++) sdsfree(argv[j]);
|
||||
zfree(argv);
|
||||
}
|
||||
|
||||
#ifdef SDS_TEST_MAIN
|
||||
#include <stdio.h>
|
||||
#include "testhelp.h"
|
||||
|
||||
@@ -84,6 +84,5 @@ void sdstoupper(sds s);
|
||||
sds sdsfromlonglong(long long value);
|
||||
sds sdscatrepr(sds s, char *p, size_t len);
|
||||
sds *sdssplitargs(char *line, int *argc);
|
||||
void sdssplitargs_free(sds *argv, int argc);
|
||||
|
||||
#endif
|
||||
|
||||
+49
-1
@@ -28,7 +28,55 @@ A million repetitions of "a"
|
||||
#include "solarisfixes.h"
|
||||
#endif
|
||||
#include "sha1.h"
|
||||
#include "config.h"
|
||||
|
||||
#ifndef BYTE_ORDER
|
||||
#if (BSD >= 199103)
|
||||
# include <machine/endian.h>
|
||||
#else
|
||||
#if defined(linux) || defined(__linux__)
|
||||
# include <endian.h>
|
||||
#else
|
||||
#define LITTLE_ENDIAN 1234 /* least-significant byte first (vax, pc) */
|
||||
#define BIG_ENDIAN 4321 /* most-significant byte first (IBM, net) */
|
||||
#define PDP_ENDIAN 3412 /* LSB first in word, MSW first in long (pdp)*/
|
||||
|
||||
#if defined(vax) || defined(ns32000) || defined(sun386) || defined(__i386__) || \
|
||||
defined(MIPSEL) || defined(_MIPSEL) || defined(BIT_ZERO_ON_RIGHT) || \
|
||||
defined(__alpha__) || defined(__alpha)
|
||||
#define BYTE_ORDER LITTLE_ENDIAN
|
||||
#endif
|
||||
|
||||
#if defined(sel) || defined(pyr) || defined(mc68000) || defined(sparc) || \
|
||||
defined(is68k) || defined(tahoe) || defined(ibm032) || defined(ibm370) || \
|
||||
defined(MIPSEB) || defined(_MIPSEB) || defined(_IBMR2) || defined(DGUX) ||\
|
||||
defined(apollo) || defined(__convex__) || defined(_CRAY) || \
|
||||
defined(__hppa) || defined(__hp9000) || \
|
||||
defined(__hp9000s300) || defined(__hp9000s700) || \
|
||||
defined (BIT_ZERO_ON_LEFT) || defined(m68k) || defined(__sparc)
|
||||
#define BYTE_ORDER BIG_ENDIAN
|
||||
#endif
|
||||
#endif /* linux */
|
||||
#endif /* BSD */
|
||||
#endif /* BYTE_ORDER */
|
||||
|
||||
#if defined(__BYTE_ORDER) && !defined(BYTE_ORDER)
|
||||
#if (__BYTE_ORDER == __LITTLE_ENDIAN)
|
||||
#define BYTE_ORDER LITTLE_ENDIAN
|
||||
#else
|
||||
#define BYTE_ORDER BIG_ENDIAN
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if !defined(BYTE_ORDER) || \
|
||||
(BYTE_ORDER != BIG_ENDIAN && BYTE_ORDER != LITTLE_ENDIAN && \
|
||||
BYTE_ORDER != PDP_ENDIAN)
|
||||
/* you must determine what the correct bit order is for
|
||||
* your compiler - the next line is an intentional error
|
||||
* which will force your compiles to bomb until you fix
|
||||
* the above macros.
|
||||
*/
|
||||
#error "Undefined or invalid BYTE_ORDER"
|
||||
#endif
|
||||
|
||||
#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
|
||||
|
||||
|
||||
+2
-2
@@ -366,12 +366,12 @@ void sortCommand(redisClient *c) {
|
||||
}
|
||||
}
|
||||
}
|
||||
setKey(c->db,storekey,sobj);
|
||||
decrRefCount(sobj);
|
||||
dbReplace(c->db,storekey,sobj);
|
||||
/* Note: we add 1 because the DB is dirty anyway since even if the
|
||||
* SORT result is empty a new key is set and maybe the old content
|
||||
* replaced. */
|
||||
server.dirty += 1+outputlen;
|
||||
touchWatchedKey(c->db,storekey);
|
||||
addReplyLongLong(c,outputlen);
|
||||
}
|
||||
|
||||
|
||||
@@ -107,7 +107,6 @@ int syncReadLine(int fd, char *ptr, ssize_t size, int timeout) {
|
||||
int fwriteBulkString(FILE *fp, char *s, unsigned long len) {
|
||||
char cbuf[128];
|
||||
int clen;
|
||||
|
||||
cbuf[0] = '$';
|
||||
clen = 1+ll2string(cbuf+1,sizeof(cbuf)-1,len);
|
||||
cbuf[clen++] = '\r';
|
||||
@@ -118,19 +117,6 @@ int fwriteBulkString(FILE *fp, char *s, unsigned long len) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Write a multi bulk count in the form "*<count>\r\n" */
|
||||
int fwriteBulkCount(FILE *fp, char prefix, int count) {
|
||||
char cbuf[128];
|
||||
int clen;
|
||||
|
||||
cbuf[0] = prefix;
|
||||
clen = 1+ll2string(cbuf+1,sizeof(cbuf)-1,count);
|
||||
cbuf[clen++] = '\r';
|
||||
cbuf[clen++] = '\n';
|
||||
if (fwrite(cbuf,clen,1,fp) == 0) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Write a double value in bulk format $<count>\r\n<payload>\r\n */
|
||||
int fwriteBulkDouble(FILE *fp, double d) {
|
||||
char buf[128], dbuf[128];
|
||||
|
||||
+5
-5
@@ -279,7 +279,7 @@ void hsetCommand(redisClient *c) {
|
||||
hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]);
|
||||
update = hashTypeSet(o,c->argv[2],c->argv[3]);
|
||||
addReply(c, update ? shared.czero : shared.cone);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
}
|
||||
|
||||
@@ -294,7 +294,7 @@ void hsetnxCommand(redisClient *c) {
|
||||
hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]);
|
||||
hashTypeSet(o,c->argv[2],c->argv[3]);
|
||||
addReply(c, shared.cone);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
}
|
||||
}
|
||||
@@ -315,7 +315,7 @@ void hmsetCommand(redisClient *c) {
|
||||
hashTypeSet(o,c->argv[i],c->argv[i+1]);
|
||||
}
|
||||
addReply(c, shared.ok);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
}
|
||||
|
||||
@@ -342,7 +342,7 @@ void hincrbyCommand(redisClient *c) {
|
||||
hashTypeSet(o,c->argv[2],new);
|
||||
decrRefCount(new);
|
||||
addReplyLongLong(c,value);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
}
|
||||
|
||||
@@ -408,7 +408,7 @@ void hdelCommand(redisClient *c) {
|
||||
}
|
||||
}
|
||||
if (deleted) {
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
server.dirty += deleted;
|
||||
}
|
||||
addReplyLongLong(c,deleted);
|
||||
|
||||
+10
-11
@@ -286,7 +286,7 @@ void pushGenericCommand(redisClient *c, int where) {
|
||||
pushed++;
|
||||
}
|
||||
addReplyLongLong(c,addlen + (lobj ? listTypeLength(lobj) : 0));
|
||||
if (pushed) signalModifiedKey(c->db,c->argv[1]);
|
||||
if (pushed) touchWatchedKey(c->db,c->argv[1]);
|
||||
server.dirty += pushed;
|
||||
}
|
||||
|
||||
@@ -335,7 +335,7 @@ void pushxGenericCommand(redisClient *c, robj *refval, robj *val, int where) {
|
||||
if (subject->encoding == REDIS_ENCODING_ZIPLIST &&
|
||||
ziplistLen(subject->ptr) > server.list_max_ziplist_entries)
|
||||
listTypeConvert(subject,REDIS_ENCODING_LINKEDLIST);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
} else {
|
||||
/* Notify client of a failed insert */
|
||||
@@ -344,7 +344,7 @@ void pushxGenericCommand(redisClient *c, robj *refval, robj *val, int where) {
|
||||
}
|
||||
} else {
|
||||
listTypePush(subject,val,where);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
}
|
||||
|
||||
@@ -432,7 +432,7 @@ void lsetCommand(redisClient *c) {
|
||||
o->ptr = ziplistInsert(o->ptr,p,value->ptr,sdslen(value->ptr));
|
||||
decrRefCount(value);
|
||||
addReply(c,shared.ok);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
}
|
||||
} else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
|
||||
@@ -444,7 +444,7 @@ void lsetCommand(redisClient *c) {
|
||||
listNodeValue(ln) = value;
|
||||
incrRefCount(value);
|
||||
addReply(c,shared.ok);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
}
|
||||
} else {
|
||||
@@ -463,7 +463,7 @@ void popGenericCommand(redisClient *c, int where) {
|
||||
addReplyBulk(c,value);
|
||||
decrRefCount(value);
|
||||
if (listTypeLength(o) == 0) dbDelete(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
}
|
||||
}
|
||||
@@ -578,7 +578,7 @@ void ltrimCommand(redisClient *c) {
|
||||
redisPanic("Unknown list encoding");
|
||||
}
|
||||
if (listTypeLength(o) == 0) dbDelete(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
addReply(c,shared.ok);
|
||||
}
|
||||
@@ -621,7 +621,7 @@ void lremCommand(redisClient *c) {
|
||||
|
||||
if (listTypeLength(subject) == 0) dbDelete(c->db,c->argv[1]);
|
||||
addReplyLongLong(c,removed);
|
||||
if (removed) signalModifiedKey(c->db,c->argv[1]);
|
||||
if (removed) touchWatchedKey(c->db,c->argv[1]);
|
||||
}
|
||||
|
||||
/* This is the semantic of this command:
|
||||
@@ -649,7 +649,7 @@ void rpoplpushHandlePush(redisClient *origclient, redisClient *c, robj *dstkey,
|
||||
dstobj = createZiplistObject();
|
||||
dbAdd(c->db,dstkey,dstobj);
|
||||
} else {
|
||||
signalModifiedKey(c->db,dstkey);
|
||||
touchWatchedKey(c->db,dstkey);
|
||||
}
|
||||
listTypePush(dstobj,value,REDIS_HEAD);
|
||||
/* If we are pushing as a result of LPUSH against a key
|
||||
@@ -698,7 +698,7 @@ void rpoplpushCommand(redisClient *c) {
|
||||
|
||||
/* Delete the source list when it is empty */
|
||||
if (listTypeLength(sobj) == 0) dbDelete(c->db,touchedkey);
|
||||
signalModifiedKey(c->db,touchedkey);
|
||||
touchWatchedKey(c->db,touchedkey);
|
||||
decrRefCount(touchedkey);
|
||||
server.dirty++;
|
||||
}
|
||||
@@ -848,7 +848,6 @@ int handleClientsWaitingListPush(redisClient *c, robj *key, robj *ele) {
|
||||
/* Protect receiver->bpop.target, that will be freed by
|
||||
* the next unblockClientWaitingData() call. */
|
||||
if (dstkey) incrRefCount(dstkey);
|
||||
|
||||
/* This should remove the first element of the "clients" list. */
|
||||
unblockClientWaitingData(receiver);
|
||||
|
||||
|
||||
+8
-8
@@ -235,7 +235,7 @@ void saddCommand(redisClient *c) {
|
||||
c->argv[j] = tryObjectEncoding(c->argv[j]);
|
||||
if (setTypeAdd(set,c->argv[j])) added++;
|
||||
}
|
||||
if (added) signalModifiedKey(c->db,c->argv[1]);
|
||||
if (added) touchWatchedKey(c->db,c->argv[1]);
|
||||
server.dirty += added;
|
||||
addReplyLongLong(c,added);
|
||||
}
|
||||
@@ -257,7 +257,7 @@ void sremCommand(redisClient *c) {
|
||||
}
|
||||
}
|
||||
if (deleted) {
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
server.dirty += deleted;
|
||||
}
|
||||
addReplyLongLong(c,deleted);
|
||||
@@ -294,8 +294,8 @@ void smoveCommand(redisClient *c) {
|
||||
|
||||
/* Remove the src set from the database when empty */
|
||||
if (setTypeSize(srcset) == 0) dbDelete(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[2]);
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
touchWatchedKey(c->db,c->argv[2]);
|
||||
server.dirty++;
|
||||
|
||||
/* Create the destination set when it doesn't exist */
|
||||
@@ -356,7 +356,7 @@ void spopCommand(redisClient *c) {
|
||||
|
||||
addReplyBulk(c,ele);
|
||||
if (setTypeSize(set) == 0) dbDelete(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
}
|
||||
|
||||
@@ -397,7 +397,7 @@ void sinterGenericCommand(redisClient *c, robj **setkeys, unsigned long setnum,
|
||||
zfree(sets);
|
||||
if (dstkey) {
|
||||
if (dbDelete(c->db,dstkey)) {
|
||||
signalModifiedKey(c->db,dstkey);
|
||||
touchWatchedKey(c->db,dstkey);
|
||||
server.dirty++;
|
||||
}
|
||||
addReply(c,shared.czero);
|
||||
@@ -502,7 +502,7 @@ void sinterGenericCommand(redisClient *c, robj **setkeys, unsigned long setnum,
|
||||
decrRefCount(dstset);
|
||||
addReply(c,shared.czero);
|
||||
}
|
||||
signalModifiedKey(c->db,dstkey);
|
||||
touchWatchedKey(c->db,dstkey);
|
||||
server.dirty++;
|
||||
} else {
|
||||
setDeferredMultiBulkLength(c,replylen,cardinality);
|
||||
@@ -594,7 +594,7 @@ void sunionDiffGenericCommand(redisClient *c, robj **setkeys, int setnum, robj *
|
||||
decrRefCount(dstset);
|
||||
addReply(c,shared.czero);
|
||||
}
|
||||
signalModifiedKey(c->db,dstkey);
|
||||
touchWatchedKey(c->db,dstkey);
|
||||
server.dirty++;
|
||||
}
|
||||
zfree(sets);
|
||||
|
||||
+37
-24
@@ -13,6 +13,7 @@ static int checkStringLength(redisClient *c, long long size) {
|
||||
}
|
||||
|
||||
void setGenericCommand(redisClient *c, int nx, robj *key, robj *val, robj *expire) {
|
||||
int retval;
|
||||
long seconds = 0; /* initialized to avoid an harmness warning */
|
||||
|
||||
if (expire) {
|
||||
@@ -24,12 +25,21 @@ void setGenericCommand(redisClient *c, int nx, robj *key, robj *val, robj *expir
|
||||
}
|
||||
}
|
||||
|
||||
if (lookupKeyWrite(c->db,key) != NULL && nx) {
|
||||
addReply(c,shared.czero);
|
||||
return;
|
||||
retval = dbAdd(c->db,key,val);
|
||||
if (retval == REDIS_ERR) {
|
||||
if (!nx) {
|
||||
dbReplace(c->db,key,val);
|
||||
incrRefCount(val);
|
||||
} else {
|
||||
addReply(c,shared.czero);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
incrRefCount(val);
|
||||
}
|
||||
setKey(c->db,key,val);
|
||||
touchWatchedKey(c->db,key);
|
||||
server.dirty++;
|
||||
removeExpire(c->db,key);
|
||||
if (expire) setExpire(c->db,key,time(NULL)+seconds);
|
||||
addReply(c, nx ? shared.cone : shared.ok);
|
||||
}
|
||||
@@ -71,8 +81,11 @@ void getCommand(redisClient *c) {
|
||||
void getsetCommand(redisClient *c) {
|
||||
if (getGenericCommand(c) == REDIS_ERR) return;
|
||||
c->argv[2] = tryObjectEncoding(c->argv[2]);
|
||||
setKey(c->db,c->argv[1],c->argv[2]);
|
||||
dbReplace(c->db,c->argv[1],c->argv[2]);
|
||||
incrRefCount(c->argv[2]);
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
removeExpire(c->db,c->argv[1]);
|
||||
}
|
||||
|
||||
static int getBitOffsetFromArgument(redisClient *c, robj *o, size_t *offset) {
|
||||
@@ -125,7 +138,7 @@ void setbitCommand(redisClient *c) {
|
||||
robj *decoded = getDecodedObject(o);
|
||||
o = createStringObject(decoded->ptr, sdslen(decoded->ptr));
|
||||
decrRefCount(decoded);
|
||||
dbOverwrite(c->db,c->argv[1],o);
|
||||
dbReplace(c->db,c->argv[1],o);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,7 +155,7 @@ void setbitCommand(redisClient *c) {
|
||||
byteval &= ~(1 << bit);
|
||||
byteval |= ((on & 0x1) << bit);
|
||||
((char*)o->ptr)[byte] = byteval;
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
addReply(c, bitval ? shared.cone : shared.czero);
|
||||
}
|
||||
@@ -223,14 +236,14 @@ void setrangeCommand(redisClient *c) {
|
||||
robj *decoded = getDecodedObject(o);
|
||||
o = createStringObject(decoded->ptr, sdslen(decoded->ptr));
|
||||
decrRefCount(decoded);
|
||||
dbOverwrite(c->db,c->argv[1],o);
|
||||
dbReplace(c->db,c->argv[1],o);
|
||||
}
|
||||
}
|
||||
|
||||
if (sdslen(value) > 0) {
|
||||
o->ptr = sdsgrowzero(o->ptr,offset+sdslen(value));
|
||||
memcpy((char*)o->ptr+offset,value,sdslen(value));
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
}
|
||||
addReplyLongLong(c,sdslen(o->ptr));
|
||||
@@ -306,15 +319,18 @@ void msetGenericCommand(redisClient *c, int nx) {
|
||||
busykeys++;
|
||||
}
|
||||
}
|
||||
if (busykeys) {
|
||||
addReply(c, shared.czero);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (busykeys) {
|
||||
addReply(c, shared.czero);
|
||||
return;
|
||||
}
|
||||
|
||||
for (j = 1; j < c->argc; j += 2) {
|
||||
c->argv[j+1] = tryObjectEncoding(c->argv[j+1]);
|
||||
setKey(c->db,c->argv[j],c->argv[j+1]);
|
||||
dbReplace(c->db,c->argv[j],c->argv[j+1]);
|
||||
incrRefCount(c->argv[j+1]);
|
||||
removeExpire(c->db,c->argv[j]);
|
||||
touchWatchedKey(c->db,c->argv[j]);
|
||||
}
|
||||
server.dirty += (c->argc-1)/2;
|
||||
addReply(c, nx ? shared.cone : shared.ok);
|
||||
@@ -330,7 +346,7 @@ void msetnxCommand(redisClient *c) {
|
||||
|
||||
void incrDecrCommand(redisClient *c, long long incr) {
|
||||
long long value, oldvalue;
|
||||
robj *o, *new;
|
||||
robj *o;
|
||||
|
||||
o = lookupKeyWrite(c->db,c->argv[1]);
|
||||
if (o != NULL && checkType(c,o,REDIS_STRING)) return;
|
||||
@@ -342,15 +358,12 @@ void incrDecrCommand(redisClient *c, long long incr) {
|
||||
addReplyError(c,"increment or decrement would overflow");
|
||||
return;
|
||||
}
|
||||
new = createStringObjectFromLongLong(value);
|
||||
if (o)
|
||||
dbOverwrite(c->db,c->argv[1],new);
|
||||
else
|
||||
dbAdd(c->db,c->argv[1],new);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
o = createStringObjectFromLongLong(value);
|
||||
dbReplace(c->db,c->argv[1],o);
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
addReply(c,shared.colon);
|
||||
addReply(c,new);
|
||||
addReply(c,o);
|
||||
addReply(c,shared.crlf);
|
||||
}
|
||||
|
||||
@@ -403,14 +416,14 @@ void appendCommand(redisClient *c) {
|
||||
robj *decoded = getDecodedObject(o);
|
||||
o = createStringObject(decoded->ptr, sdslen(decoded->ptr));
|
||||
decrRefCount(decoded);
|
||||
dbOverwrite(c->db,c->argv[1],o);
|
||||
dbReplace(c->db,c->argv[1],o);
|
||||
}
|
||||
|
||||
/* Append the value */
|
||||
o->ptr = sdscatlen(o->ptr,append->ptr,sdslen(append->ptr));
|
||||
totlen = sdslen(o->ptr);
|
||||
}
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
addReplyLongLong(c,totlen);
|
||||
}
|
||||
|
||||
+16
-10
@@ -174,6 +174,12 @@ int zslDelete(zskiplist *zsl, double score, robj *obj) {
|
||||
return 0; /* not found */
|
||||
}
|
||||
|
||||
/* Struct to hold a inclusive/exclusive range spec. */
|
||||
typedef struct {
|
||||
double min, max;
|
||||
int minex, maxex; /* are min or max exclusive? */
|
||||
} zrangespec;
|
||||
|
||||
static int zslValueGteMin(double value, zrangespec *spec) {
|
||||
return spec->minex ? (value > spec->min) : (value >= spec->min);
|
||||
}
|
||||
@@ -876,7 +882,7 @@ void zaddGenericCommand(redisClient *c, int incr) {
|
||||
zobj->ptr = zzlDelete(zobj->ptr,eptr);
|
||||
zobj->ptr = zzlInsert(zobj->ptr,ele,score);
|
||||
|
||||
signalModifiedKey(c->db,key);
|
||||
touchWatchedKey(c->db,key);
|
||||
server.dirty++;
|
||||
}
|
||||
} else {
|
||||
@@ -888,7 +894,7 @@ void zaddGenericCommand(redisClient *c, int incr) {
|
||||
if (sdslen(ele->ptr) > server.zset_max_ziplist_value)
|
||||
zsetConvert(zobj,REDIS_ENCODING_SKIPLIST);
|
||||
|
||||
signalModifiedKey(c->db,key);
|
||||
touchWatchedKey(c->db,key);
|
||||
server.dirty++;
|
||||
if (!incr) added++;
|
||||
}
|
||||
@@ -923,7 +929,7 @@ void zaddGenericCommand(redisClient *c, int incr) {
|
||||
incrRefCount(curobj); /* Re-inserted in skiplist. */
|
||||
dictGetEntryVal(de) = &znode->score; /* Update score ptr. */
|
||||
|
||||
signalModifiedKey(c->db,key);
|
||||
touchWatchedKey(c->db,key);
|
||||
server.dirty++;
|
||||
}
|
||||
} else {
|
||||
@@ -932,7 +938,7 @@ void zaddGenericCommand(redisClient *c, int incr) {
|
||||
redisAssert(dictAdd(zs->dict,ele,&znode->score) == DICT_OK);
|
||||
incrRefCount(ele); /* Added to dictionary. */
|
||||
|
||||
signalModifiedKey(c->db,key);
|
||||
touchWatchedKey(c->db,key);
|
||||
server.dirty++;
|
||||
if (!incr) added++;
|
||||
}
|
||||
@@ -1004,7 +1010,7 @@ void zremCommand(redisClient *c) {
|
||||
}
|
||||
|
||||
if (deleted) {
|
||||
signalModifiedKey(c->db,key);
|
||||
touchWatchedKey(c->db,key);
|
||||
server.dirty += deleted;
|
||||
}
|
||||
addReplyLongLong(c,deleted);
|
||||
@@ -1037,7 +1043,7 @@ void zremrangebyscoreCommand(redisClient *c) {
|
||||
redisPanic("Unknown sorted set encoding");
|
||||
}
|
||||
|
||||
if (deleted) signalModifiedKey(c->db,key);
|
||||
if (deleted) touchWatchedKey(c->db,key);
|
||||
server.dirty += deleted;
|
||||
addReplyLongLong(c,deleted);
|
||||
}
|
||||
@@ -1085,7 +1091,7 @@ void zremrangebyrankCommand(redisClient *c) {
|
||||
redisPanic("Unknown sorted set encoding");
|
||||
}
|
||||
|
||||
if (deleted) signalModifiedKey(c->db,key);
|
||||
if (deleted) touchWatchedKey(c->db,key);
|
||||
server.dirty += deleted;
|
||||
addReplyLongLong(c,deleted);
|
||||
}
|
||||
@@ -1530,7 +1536,7 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
|
||||
|
||||
dstobj = createZsetObject();
|
||||
dstzset = dstobj->ptr;
|
||||
memset(&zval, 0, sizeof(zval));
|
||||
memset(&zval,0,sizeof(zval));
|
||||
|
||||
if (op == REDIS_OP_INTER) {
|
||||
/* Skip everything if the smallest input is empty. */
|
||||
@@ -1617,7 +1623,7 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
|
||||
zuiClearIterator(&src[i]);
|
||||
|
||||
if (dbDelete(c->db,dstkey)) {
|
||||
signalModifiedKey(c->db,dstkey);
|
||||
touchWatchedKey(c->db,dstkey);
|
||||
touched = 1;
|
||||
server.dirty++;
|
||||
}
|
||||
@@ -1629,7 +1635,7 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
|
||||
|
||||
dbAdd(c->db,dstkey,dstobj);
|
||||
addReplyLongLong(c,zsetLength(dstobj));
|
||||
if (!touched) signalModifiedKey(c->db,dstkey);
|
||||
if (!touched) touchWatchedKey(c->db,dstkey);
|
||||
server.dirty++;
|
||||
} else {
|
||||
decrRefCount(dstobj);
|
||||
|
||||
+12
@@ -5,6 +5,7 @@
|
||||
#include <ctype.h>
|
||||
#include <limits.h>
|
||||
#include <math.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include "util.h"
|
||||
|
||||
@@ -327,6 +328,17 @@ int d2string(char *buf, size_t len, double value) {
|
||||
return len;
|
||||
}
|
||||
|
||||
/* Return the UNIX time in microseconds */
|
||||
long long ustime(void) {
|
||||
struct timeval tv;
|
||||
long long ust;
|
||||
|
||||
gettimeofday(&tv, NULL);
|
||||
ust = ((long long)tv.tv_sec)*1000000;
|
||||
ust += tv.tv_usec;
|
||||
return ust;
|
||||
}
|
||||
|
||||
#ifdef UTIL_TEST_MAIN
|
||||
#include <assert.h>
|
||||
|
||||
|
||||
@@ -8,5 +8,6 @@ int ll2string(char *s, size_t len, long long value);
|
||||
int string2ll(char *s, size_t slen, long long *value);
|
||||
int string2l(char *s, size_t slen, long *value);
|
||||
int d2string(char *buf, size_t len, double value);
|
||||
long long ustime(void);
|
||||
|
||||
#endif
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
#define REDIS_VERSION "2.9.0"
|
||||
#define REDIS_VERSION "2.3.5"
|
||||
|
||||
+72
-134
@@ -18,9 +18,9 @@ daemonize no
|
||||
|
||||
# When running daemonized, Redis writes a pid file in /var/run/redis.pid by
|
||||
# default. You can specify a custom pid file location here.
|
||||
pidfile /var/run/redis.pid
|
||||
pidfile redis.pid
|
||||
|
||||
# Accept connections on the specified port, default is 6379.
|
||||
# Accept connections on the specified port, default is 6379
|
||||
port 6379
|
||||
|
||||
# If you want you can bind a single interface, if the bind option is not
|
||||
@@ -28,12 +28,6 @@ port 6379
|
||||
#
|
||||
# bind 127.0.0.1
|
||||
|
||||
# Specify the path for the unix socket that will be used to listen for
|
||||
# incoming connections. There is no default, so Redis will not listen
|
||||
# on a unix socket when not specified.
|
||||
#
|
||||
# unixsocket /tmp/redis.sock
|
||||
|
||||
# Close the connection after a client is idle for N seconds (0 to disable)
|
||||
timeout 300
|
||||
|
||||
@@ -50,16 +44,6 @@ loglevel verbose
|
||||
# output for logging but daemonize, logs will be sent to /dev/null
|
||||
logfile stdout
|
||||
|
||||
# To enable logging to the system logger, just set 'syslog-enabled' to yes,
|
||||
# and optionally update the other syslog parameters to suit your needs.
|
||||
# syslog-enabled no
|
||||
|
||||
# Specify the syslog identity.
|
||||
# syslog-ident redis
|
||||
|
||||
# Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7.
|
||||
# syslog-facility local0
|
||||
|
||||
# Set the number of databases. The default database is DB 0, you can select
|
||||
# a different one on a per-connection basis using SELECT <dbid> where
|
||||
# dbid is a number between 0 and 'databases'-1
|
||||
@@ -102,7 +86,7 @@ dbfilename dump.rdb
|
||||
# Also the Append Only File will be created inside this directory.
|
||||
#
|
||||
# Note that you must specify a directory here, not a file name.
|
||||
dir ./
|
||||
dir ./test/tmp
|
||||
|
||||
################################# REPLICATION #################################
|
||||
|
||||
@@ -120,19 +104,6 @@ dir ./
|
||||
#
|
||||
# masterauth <master-password>
|
||||
|
||||
# When a slave lost the connection with the master, or when the replication
|
||||
# is still in progress, the slave can act in two different ways:
|
||||
#
|
||||
# 1) if slave-serve-stale-data is set to 'yes' (the default) the slave will
|
||||
# still reply to client requests, possibly with out of data data, or the
|
||||
# data set may just be empty if this is the first synchronization.
|
||||
#
|
||||
# 2) if slave-serve-stale data is set to 'no' the slave will reply with
|
||||
# an error "SYNC with master in progress" to all the kind of commands
|
||||
# but to INFO and SLAVEOF.
|
||||
#
|
||||
slave-serve-stale-data yes
|
||||
|
||||
################################## SECURITY ###################################
|
||||
|
||||
# Require clients to issue AUTH <PASSWORD> before processing any other
|
||||
@@ -148,22 +119,6 @@ slave-serve-stale-data yes
|
||||
#
|
||||
# requirepass foobared
|
||||
|
||||
# Command renaming.
|
||||
#
|
||||
# It is possilbe to change the name of dangerous commands in a shared
|
||||
# environment. For instance the CONFIG command may be renamed into something
|
||||
# of hard to guess so that it will be still available for internal-use
|
||||
# tools but not available for general clients.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52
|
||||
#
|
||||
# It is also possilbe to completely kill a command renaming it into
|
||||
# an empty string:
|
||||
#
|
||||
# rename-command CONFIG ""
|
||||
|
||||
################################### LIMITS ####################################
|
||||
|
||||
# Set the max number of connected clients at the same time. By default there
|
||||
@@ -193,37 +148,6 @@ slave-serve-stale-data yes
|
||||
#
|
||||
# maxmemory <bytes>
|
||||
|
||||
# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
|
||||
# is reached? You can select among five behavior:
|
||||
#
|
||||
# volatile-lru -> remove the key with an expire set using an LRU algorithm
|
||||
# allkeys-lru -> remove any key accordingly to the LRU algorithm
|
||||
# volatile-random -> remove a random key with an expire set
|
||||
# allkeys->random -> remove a random key, any key
|
||||
# volatile-ttl -> remove the key with the nearest expire time (minor TTL)
|
||||
# noeviction -> don't expire at all, just return an error on write operations
|
||||
#
|
||||
# Note: with all the kind of policies, Redis will return an error on write
|
||||
# operations, when there are not suitable keys for eviction.
|
||||
#
|
||||
# At the date of writing this commands are: set setnx setex append
|
||||
# incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd
|
||||
# sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby
|
||||
# zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby
|
||||
# getset mset msetnx exec sort
|
||||
#
|
||||
# The default is:
|
||||
#
|
||||
# maxmemory-policy volatile-lru
|
||||
|
||||
# LRU and minimal TTL algorithms are not precise algorithms but approximated
|
||||
# algorithms (in order to save memory), so you can select as well the sample
|
||||
# size to check. For instance for default Redis will check three keys and
|
||||
# pick the one that was used less recently, you can change the sample size
|
||||
# using the following configuration directive.
|
||||
#
|
||||
# maxmemory-samples 3
|
||||
|
||||
############################## APPEND ONLY MODE ###############################
|
||||
|
||||
# By default Redis asynchronously dumps the dataset on disk. If you can live
|
||||
@@ -271,54 +195,81 @@ appendonly no
|
||||
appendfsync everysec
|
||||
# appendfsync no
|
||||
|
||||
# When the AOF fsync policy is set to always or everysec, and a background
|
||||
# saving process (a background save or AOF log background rewriting) is
|
||||
# performing a lot of I/O against the disk, in some Linux configurations
|
||||
# Redis may block too long on the fsync() call. Note that there is no fix for
|
||||
# this currently, as even performing fsync in a different thread will block
|
||||
# our synchronous write(2) call.
|
||||
#
|
||||
# In order to mitigate this problem it's possible to use the following option
|
||||
# that will prevent fsync() from being called in the main process while a
|
||||
# BGSAVE or BGREWRITEAOF is in progress.
|
||||
#
|
||||
# This means that while another child is saving the durability of Redis is
|
||||
# the same as "appendfsync none", that in pratical terms means that it is
|
||||
# possible to lost up to 30 seconds of log in the worst scenario (with the
|
||||
# default Linux settings).
|
||||
#
|
||||
# If you have latency problems turn this to "yes". Otherwise leave it as
|
||||
# "no" that is the safest pick from the point of view of durability.
|
||||
no-appendfsync-on-rewrite no
|
||||
################################ VIRTUAL MEMORY ###############################
|
||||
|
||||
#################################### DISK STORE ###############################
|
||||
# Virtual Memory allows Redis to work with datasets bigger than the actual
|
||||
# amount of RAM needed to hold the whole dataset in memory.
|
||||
# In order to do so very used keys are taken in memory while the other keys
|
||||
# are swapped into a swap file, similarly to what operating systems do
|
||||
# with memory pages.
|
||||
#
|
||||
# To enable VM just set 'vm-enabled' to yes, and set the following three
|
||||
# VM parameters accordingly to your needs.
|
||||
|
||||
# When disk store is active Redis works as an on-disk database, where memory
|
||||
# is only used as a object cache.
|
||||
#
|
||||
# This mode is good for datasets that are bigger than memory, and in general
|
||||
# when you want to trade speed for:
|
||||
#
|
||||
# - less memory used
|
||||
# - immediate server restart
|
||||
# - per key durability, without need for backgrond savig
|
||||
#
|
||||
# On the other hand, with disk store enabled MULTI/EXEC are no longer
|
||||
# transactional from the point of view of the persistence on disk, that is,
|
||||
# Redis transactions will still guarantee that commands are either processed
|
||||
# all or nothing, but there is no guarantee that all the keys are flushed
|
||||
# on disk in an atomic way.
|
||||
#
|
||||
# Of course with disk store enabled Redis is not as fast as it is when
|
||||
# working with just the memory back end.
|
||||
vm-enabled no
|
||||
# vm-enabled yes
|
||||
|
||||
diskstore-enabled no
|
||||
diskstore-path redis.ds
|
||||
cache-max-memory 0
|
||||
cache-flush-delay 0
|
||||
# This is the path of the Redis swap file. As you can guess, swap files
|
||||
# can't be shared by different Redis instances, so make sure to use a swap
|
||||
# file for every redis process you are running. Redis will complain if the
|
||||
# swap file is already in use.
|
||||
#
|
||||
# The best kind of storage for the Redis swap file (that's accessed at random)
|
||||
# is a Solid State Disk (SSD).
|
||||
#
|
||||
# *** WARNING *** if you are using a shared hosting the default of putting
|
||||
# the swap file under /tmp is not secure. Create a dir with access granted
|
||||
# only to Redis user and configure Redis to create the swap file there.
|
||||
vm-swap-file redis.swap
|
||||
|
||||
# vm-max-memory configures the VM to use at max the specified amount of
|
||||
# RAM. Everything that deos not fit will be swapped on disk *if* possible, that
|
||||
# is, if there is still enough contiguous space in the swap file.
|
||||
#
|
||||
# With vm-max-memory 0 the system will swap everything it can. Not a good
|
||||
# default, just specify the max amount of RAM you can in bytes, but it's
|
||||
# better to leave some margin. For instance specify an amount of RAM
|
||||
# that's more or less between 60 and 80% of your free RAM.
|
||||
vm-max-memory 0
|
||||
|
||||
# Redis swap files is split into pages. An object can be saved using multiple
|
||||
# contiguous pages, but pages can't be shared between different objects.
|
||||
# So if your page is too big, small objects swapped out on disk will waste
|
||||
# a lot of space. If you page is too small, there is less space in the swap
|
||||
# file (assuming you configured the same number of total swap file pages).
|
||||
#
|
||||
# If you use a lot of small objects, use a page size of 64 or 32 bytes.
|
||||
# If you use a lot of big objects, use a bigger page size.
|
||||
# If unsure, use the default :)
|
||||
vm-page-size 32
|
||||
|
||||
# Number of total memory pages in the swap file.
|
||||
# Given that the page table (a bitmap of free/used pages) is taken in memory,
|
||||
# every 8 pages on disk will consume 1 byte of RAM.
|
||||
#
|
||||
# The total swap size is vm-page-size * vm-pages
|
||||
#
|
||||
# 32M swap should be enough for testing.
|
||||
vm-pages 1048576
|
||||
|
||||
# Max number of VM I/O threads running at the same time.
|
||||
# This threads are used to read/write data from/to swap file, since they
|
||||
# also encode and decode objects from disk to memory or the reverse, a bigger
|
||||
# number of threads can help with big objects even if they can't help with
|
||||
# I/O itself as the physical device may not be able to couple with many
|
||||
# reads/writes operations at the same time.
|
||||
#
|
||||
# The special value of 0 turn off threaded I/O and enables the blocking
|
||||
# Virtual Memory implementation.
|
||||
vm-max-threads 4
|
||||
|
||||
############################### ADVANCED CONFIG ###############################
|
||||
|
||||
# Glue small output buffers together in order to send small replies in a
|
||||
# single TCP packet. Uses a bit more CPU but most of the times it is a win
|
||||
# in terms of number of queries per second. Use 'yes' if unsure.
|
||||
glueoutputbuf yes
|
||||
|
||||
# Hashes are encoded in a special way (much more memory efficient) when they
|
||||
# have at max a given numer of elements, and the biggest element does not
|
||||
# exceed a given threshold. You can configure this limits with the following
|
||||
@@ -326,19 +277,6 @@ cache-flush-delay 0
|
||||
hash-max-zipmap-entries 64
|
||||
hash-max-zipmap-value 512
|
||||
|
||||
# Similarly to hashes, small lists are also encoded in a special way in order
|
||||
# to save a lot of space. The special representation is only used when
|
||||
# you are under the following limits:
|
||||
list-max-ziplist-entries 512
|
||||
list-max-ziplist-value 64
|
||||
|
||||
# Sets have a special encoding in just one case: when a set is composed
|
||||
# of just strings that happens to be integers in radix 10 in the range
|
||||
# of 64 bit signed integers.
|
||||
# The following configuration setting sets the limit in the size of the
|
||||
# set in order to use this special memory saving encoding.
|
||||
set-max-intset-entries 512
|
||||
|
||||
# Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in
|
||||
# order to help rehashing the main Redis hash table (the one mapping top-level
|
||||
# keys to values). The hash table implementation redis uses (see dict.c)
|
||||
|
||||
@@ -214,7 +214,7 @@ proc start_server {options {code undefined}} {
|
||||
|
||||
# find out the pid
|
||||
while {![info exists pid]} {
|
||||
regexp {\[(\d+)\]} [exec cat $stdout] _ pid
|
||||
regexp {^\[(\d+)\]} [exec head -n1 $stdout] _ pid
|
||||
after 100
|
||||
}
|
||||
|
||||
|
||||
@@ -4,13 +4,7 @@ file mkdir $::tmproot
|
||||
|
||||
# returns a dirname unique to this process to write to
|
||||
proc tmpdir {basename} {
|
||||
if {$::diskstore} {
|
||||
# For diskstore we want to use the same dir again and again
|
||||
# otherwise everything is too slow.
|
||||
set dir [file join $::tmproot $basename.diskstore]
|
||||
} else {
|
||||
set dir [file join $::tmproot $basename.[pid].[incr ::tmpcounter]]
|
||||
}
|
||||
set dir [file join $::tmproot $basename.[pid].[incr ::tmpcounter]]
|
||||
file mkdir $dir
|
||||
set _ $dir
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ set ::allowtags {}
|
||||
set ::external 0; # If "1" this means, we are running against external instance
|
||||
set ::file ""; # If set, runs only the tests in this comma separated list
|
||||
set ::curfile ""; # Hold the filename of the current suite
|
||||
set ::diskstore 0; # Don't touch this by hand. The test itself will toggle it.
|
||||
|
||||
proc execute_tests name {
|
||||
set path "tests/$name.tcl"
|
||||
@@ -104,7 +103,6 @@ proc s {args} {
|
||||
}
|
||||
|
||||
proc cleanup {} {
|
||||
puts "Cleanup: warning may take some time..."
|
||||
catch {exec rm -rf {*}[glob tests/tmp/redis.conf.*]}
|
||||
catch {exec rm -rf {*}[glob tests/tmp/server.*]}
|
||||
}
|
||||
@@ -135,12 +133,9 @@ proc execute_everything {} {
|
||||
# execute_tests "integration/redis-cli"
|
||||
execute_tests "unit/pubsub"
|
||||
|
||||
return; # No diskstore tests for now...
|
||||
# run tests with diskstore enabled
|
||||
puts "\nRunning diskstore tests... this is slow, press Ctrl+C if not interested.."
|
||||
set ::diskstore 1
|
||||
lappend ::denytags nodiskstore
|
||||
set ::global_overrides {diskstore-enabled yes}
|
||||
# run tests with VM enabled
|
||||
if 0 {
|
||||
set ::global_overrides {vm-enabled yes}
|
||||
execute_tests "unit/protocol"
|
||||
execute_tests "unit/basic"
|
||||
execute_tests "unit/type/list"
|
||||
@@ -151,6 +146,7 @@ proc execute_everything {} {
|
||||
execute_tests "unit/expire"
|
||||
execute_tests "unit/other"
|
||||
execute_tests "unit/cas"
|
||||
}
|
||||
}
|
||||
|
||||
proc main {} {
|
||||
|
||||
+11
-38
@@ -138,48 +138,21 @@ start_server {tags {"basic"}} {
|
||||
r decrby novar 17179869185
|
||||
} {-1}
|
||||
|
||||
test "SETNX target key missing" {
|
||||
r del novar
|
||||
assert_equal 1 [r setnx novar foobared]
|
||||
assert_equal "foobared" [r get novar]
|
||||
}
|
||||
test {SETNX target key missing} {
|
||||
r setnx novar2 foobared
|
||||
r get novar2
|
||||
} {foobared}
|
||||
|
||||
test "SETNX target key exists" {
|
||||
r set novar foobared
|
||||
assert_equal 0 [r setnx novar blabla]
|
||||
assert_equal "foobared" [r get novar]
|
||||
}
|
||||
test {SETNX target key exists} {
|
||||
r setnx novar2 blabla
|
||||
r get novar2
|
||||
} {foobared}
|
||||
|
||||
test "SETNX against not-expired volatile key" {
|
||||
test {SETNX against volatile key} {
|
||||
r set x 10
|
||||
r expire x 10000
|
||||
assert_equal 0 [r setnx x 20]
|
||||
assert_equal 10 [r get x]
|
||||
}
|
||||
|
||||
test "SETNX against expired volatile key" {
|
||||
# Make it very unlikely for the key this test uses to be expired by the
|
||||
# active expiry cycle. This is tightly coupled to the implementation of
|
||||
# active expiry and dbAdd() but currently the only way to test that
|
||||
# SETNX expires a key when it should have been.
|
||||
for {set x 0} {$x < 9999} {incr x} {
|
||||
r setex key-$x 3600 value
|
||||
}
|
||||
|
||||
# This will be one of 10000 expiring keys. A cycle is executed every
|
||||
# 100ms, sampling 10 keys for being expired or not. This key will be
|
||||
# expired for at most 1s when we wait 2s, resulting in a total sample
|
||||
# of 100 keys. The probability of the success of this test being a
|
||||
# false positive is therefore approx. 1%.
|
||||
r set x 10
|
||||
r expire x 1
|
||||
|
||||
# Wait for the key to expire
|
||||
after 2000
|
||||
|
||||
assert_equal 1 [r setnx x 20]
|
||||
assert_equal 20 [r get x]
|
||||
}
|
||||
list [r setnx x 20] [r get x]
|
||||
} {0 10}
|
||||
|
||||
test {EXISTS} {
|
||||
set res {}
|
||||
|
||||
+32
-41
@@ -12,7 +12,7 @@ start_server {tags {"other"}} {
|
||||
r save
|
||||
} {OK}
|
||||
|
||||
tags {slow nodiskstore} {
|
||||
tags {"slow"} {
|
||||
foreach fuzztype {binary alpha compr} {
|
||||
test "FUZZ stresser with data model $fuzztype" {
|
||||
set err 0
|
||||
@@ -46,7 +46,7 @@ start_server {tags {"other"}} {
|
||||
set _ $err
|
||||
} {*invalid*}
|
||||
|
||||
tags {consistency nodiskstore} {
|
||||
tags {consistency} {
|
||||
if {![catch {package require sha1}]} {
|
||||
test {Check consistency of different data types after a reload} {
|
||||
r flushdb
|
||||
@@ -102,54 +102,45 @@ start_server {tags {"other"}} {
|
||||
r flushdb
|
||||
r set x 10
|
||||
r expire x 1000
|
||||
if {$::diskstore} {
|
||||
r debug flushcache
|
||||
} else {
|
||||
r save
|
||||
r debug reload
|
||||
}
|
||||
r save
|
||||
r debug reload
|
||||
set ttl [r ttl x]
|
||||
set e1 [expr {$ttl > 900 && $ttl <= 1000}]
|
||||
if {!$::diskstore} {
|
||||
r bgrewriteaof
|
||||
waitForBgrewriteaof r
|
||||
r debug loadaof
|
||||
}
|
||||
r bgrewriteaof
|
||||
waitForBgrewriteaof r
|
||||
set ttl [r ttl x]
|
||||
set e2 [expr {$ttl > 900 && $ttl <= 1000}]
|
||||
list $e1 $e2
|
||||
} {1 1}
|
||||
|
||||
tags {protocol nodiskstore} {
|
||||
test {PIPELINING stresser (also a regression for the old epoll bug)} {
|
||||
set fd2 [socket $::host $::port]
|
||||
fconfigure $fd2 -encoding binary -translation binary
|
||||
puts -nonewline $fd2 "SELECT 9\r\n"
|
||||
flush $fd2
|
||||
gets $fd2
|
||||
test {PIPELINING stresser (also a regression for the old epoll bug)} {
|
||||
set fd2 [socket $::host $::port]
|
||||
fconfigure $fd2 -encoding binary -translation binary
|
||||
puts -nonewline $fd2 "SELECT 9\r\n"
|
||||
flush $fd2
|
||||
gets $fd2
|
||||
|
||||
for {set i 0} {$i < 100000} {incr i} {
|
||||
set q {}
|
||||
set val "0000${i}0000"
|
||||
append q "SET key:$i $val\r\n"
|
||||
puts -nonewline $fd2 $q
|
||||
set q {}
|
||||
append q "GET key:$i\r\n"
|
||||
puts -nonewline $fd2 $q
|
||||
}
|
||||
flush $fd2
|
||||
for {set i 0} {$i < 100000} {incr i} {
|
||||
set q {}
|
||||
set val "0000${i}0000"
|
||||
append q "SET key:$i $val\r\n"
|
||||
puts -nonewline $fd2 $q
|
||||
set q {}
|
||||
append q "GET key:$i\r\n"
|
||||
puts -nonewline $fd2 $q
|
||||
}
|
||||
flush $fd2
|
||||
|
||||
for {set i 0} {$i < 100000} {incr i} {
|
||||
gets $fd2 line
|
||||
gets $fd2 count
|
||||
set count [string range $count 1 end]
|
||||
set val [read $fd2 $count]
|
||||
read $fd2 2
|
||||
}
|
||||
close $fd2
|
||||
set _ 1
|
||||
} {1}
|
||||
}
|
||||
for {set i 0} {$i < 100000} {incr i} {
|
||||
gets $fd2 line
|
||||
gets $fd2 count
|
||||
set count [string range $count 1 end]
|
||||
set val [read $fd2 $count]
|
||||
read $fd2 2
|
||||
}
|
||||
close $fd2
|
||||
set _ 1
|
||||
} {1}
|
||||
|
||||
test {MUTLI / EXEC basics} {
|
||||
r del mylist
|
||||
|
||||
@@ -59,13 +59,6 @@ start_server {
|
||||
assert_encoding hashtable myset
|
||||
}
|
||||
|
||||
test {Variadic SADD} {
|
||||
r del myset
|
||||
assert_equal 3 [r sadd myset a b c]
|
||||
assert_equal 2 [r sadd myset A a b c B]
|
||||
assert_equal [lsort {A a b c B}] [lsort [r smembers myset]]
|
||||
}
|
||||
|
||||
test "Set encoding after DEBUG RELOAD" {
|
||||
r del myintset myhashset mylargeintset
|
||||
for {set i 0} {$i < 100} {incr i} { r sadd myintset $i }
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
# Build a symbol table for static symbols of redis.c
|
||||
# Useful to get stack traces on segfault without a debugger. See redis.c
|
||||
# for more information.
|
||||
#
|
||||
# Copyright(C) 2009 Salvatore Sanfilippo, under the BSD license.
|
||||
|
||||
set fd [open redis.c]
|
||||
set symlist {}
|
||||
while {[gets $fd line] != -1} {
|
||||
if {[regexp {^static +[A-z0-9]+[ *]+([A-z0-9]*)\(} $line - sym]} {
|
||||
lappend symlist $sym
|
||||
}
|
||||
}
|
||||
set symlist [lsort -unique $symlist]
|
||||
puts "static struct redisFunctionSym symsTable\[\] = {"
|
||||
foreach sym $symlist {
|
||||
puts "{\"$sym\",(unsigned long)$sym},"
|
||||
}
|
||||
puts "{NULL,0}"
|
||||
puts "};"
|
||||
|
||||
close $fd
|
||||
@@ -1,14 +0,0 @@
|
||||
#!/bin/sh
|
||||
if [ $# != "1" ]
|
||||
then
|
||||
echo "Usage: ./mkrelease.sh <git-ref>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TAG=$1
|
||||
TARNAME="redis-${TAG}.tar"
|
||||
echo "Generating /tmp/${TARNAME}"
|
||||
git archive $TAG --prefix redis-${TAG}/ > /tmp/$TARNAME || exit 1
|
||||
echo "Gizipping the archive"
|
||||
rm -f /tmp/$TARNAME.gz
|
||||
gzip -9 /tmp/$TARNAME
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ "$1" = "" ]
|
||||
then
|
||||
echo "Usage: mktarball.sh <git tag, branch or commit>"
|
||||
echo "Example: mktarball.sh 2.2-rc4"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PREFIX="redis-${1}/"
|
||||
TARBALL="/tmp/redis-${1}.tar.gz"
|
||||
git archive --format=tar --prefix=$PREFIX $1 | gzip -c > $TARBALL
|
||||
echo "File created: $TARBALL"
|
||||
@@ -1,78 +0,0 @@
|
||||
# redis-copy.rb - Copyright (C) 2009-2010 Salvatore Sanfilippo
|
||||
# BSD license, See the COPYING file for more information.
|
||||
#
|
||||
# Copy the whole dataset from one Redis instance to another one
|
||||
#
|
||||
# WARNING: currently hashes and sorted sets are not supported! This
|
||||
# program should be updated.
|
||||
|
||||
require 'rubygems'
|
||||
require 'redis'
|
||||
require 'digest/sha1'
|
||||
|
||||
def redisCopy(opts={})
|
||||
sha1=""
|
||||
src = Redis.new(:host => opts[:srchost], :port => opts[:srcport])
|
||||
dst = Redis.new(:host => opts[:dsthost], :port => opts[:dstport])
|
||||
puts "Loading key names..."
|
||||
keys = src.keys('*')
|
||||
puts "Copying #{keys.length} keys..."
|
||||
c = 0
|
||||
keys.each{|k|
|
||||
vtype = src.type?(k)
|
||||
ttl = src.ttl(k).to_i if vtype != "none"
|
||||
|
||||
if vtype == "string"
|
||||
dst[k] = src[k]
|
||||
elsif vtype == "list"
|
||||
list = src.lrange(k,0,-1)
|
||||
if list.length == 0
|
||||
# Empty list special case
|
||||
dst.lpush(k,"")
|
||||
dst.lpop(k)
|
||||
else
|
||||
list.each{|ele|
|
||||
dst.rpush(k,ele)
|
||||
}
|
||||
end
|
||||
elsif vtype == "set"
|
||||
set = src.smembers(k)
|
||||
if set.length == 0
|
||||
# Empty set special case
|
||||
dst.sadd(k,"")
|
||||
dst.srem(k,"")
|
||||
else
|
||||
set.each{|ele|
|
||||
dst.sadd(k,ele)
|
||||
}
|
||||
end
|
||||
elsif vtype == "none"
|
||||
puts "WARNING: key '#{k}' was removed in the meanwhile."
|
||||
end
|
||||
|
||||
# Handle keys with an expire time set
|
||||
if ttl != -1 and vtype != "none"
|
||||
dst.expire(k,ttl)
|
||||
end
|
||||
|
||||
c = c+1
|
||||
if (c % 1000) == 0
|
||||
puts "#{c}/#{keys.length} completed"
|
||||
end
|
||||
}
|
||||
puts "DONE!"
|
||||
end
|
||||
|
||||
if ARGV.length != 4
|
||||
puts "Usage: redis-copy.rb <srchost> <srcport> <dsthost> <dstport>"
|
||||
exit 1
|
||||
end
|
||||
puts "WARNING: it's up to you to FLUSHDB the destination host before to continue, press any key when ready."
|
||||
STDIN.gets
|
||||
srchost = ARGV[0]
|
||||
srcport = ARGV[1]
|
||||
dsthost = ARGV[2]
|
||||
dstport = ARGV[3]
|
||||
puts "Copying #{srchost}:#{srcport} into #{dsthost}:#{dstport}"
|
||||
redisCopy(:srchost => srchost, :srcport => srcport.to_i,
|
||||
:dsthost => dsthost, :dstport => dstport.to_i)
|
||||
@@ -1,52 +0,0 @@
|
||||
# redis-sha1.rb - Copyright (C) 2009 Salvatore Sanfilippo
|
||||
# BSD license, See the COPYING file for more information.
|
||||
#
|
||||
# Performs the SHA1 sum of the whole datset.
|
||||
# This is useful to spot bugs in persistence related code and to make sure
|
||||
# Slaves and Masters are in SYNC.
|
||||
#
|
||||
# If you hack this code make sure to sort keys and set elements as this are
|
||||
# unsorted elements. Otherwise the sum may differ with equal dataset.
|
||||
|
||||
require 'rubygems'
|
||||
require 'redis'
|
||||
require 'digest/sha1'
|
||||
|
||||
def redisSha1(opts={})
|
||||
sha1=""
|
||||
r = Redis.new(opts)
|
||||
r.keys('*').sort.each{|k|
|
||||
vtype = r.type?(k)
|
||||
if vtype == "string"
|
||||
len = 1
|
||||
sha1 = Digest::SHA1.hexdigest(sha1+k)
|
||||
sha1 = Digest::SHA1.hexdigest(sha1+r.get(k))
|
||||
elsif vtype == "list"
|
||||
len = r.llen(k)
|
||||
if len != 0
|
||||
sha1 = Digest::SHA1.hexdigest(sha1+k)
|
||||
sha1 = Digest::SHA1.hexdigest(sha1+r.list_range(k,0,-1).join("\x01"))
|
||||
end
|
||||
elsif vtype == "set"
|
||||
len = r.scard(k)
|
||||
if len != 0
|
||||
sha1 = Digest::SHA1.hexdigest(sha1+k)
|
||||
sha1 = Digest::SHA1.hexdigest(sha1+r.set_members(k).to_a.sort.join("\x02"))
|
||||
end
|
||||
elsif vtype == "zset"
|
||||
len = r.zcard(k)
|
||||
if len != 0
|
||||
sha1 = Digest::SHA1.hexdigest(sha1+k)
|
||||
sha1 = Digest::SHA1.hexdigest(sha1+r.zrange(k,0,-1).join("\x01"))
|
||||
end
|
||||
end
|
||||
# puts "#{k} => #{sha1}" if len != 0
|
||||
}
|
||||
sha1
|
||||
end
|
||||
|
||||
host = ARGV[0] || "127.0.0.1"
|
||||
port = ARGV[1] || "6379"
|
||||
db = ARGV[2] || "0"
|
||||
puts "Performing SHA1 of Redis server #{host} #{port} DB: #{db}"
|
||||
p "Dataset SHA1: #{redisSha1(:host => host, :port => port.to_i, :db => db)}"
|
||||
+7
-14
@@ -1,11 +1,7 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Simple Redis init.d script conceived to work on Linux systems
|
||||
# as it does use of the /proc filesystem.
|
||||
|
||||
REDISPORT=6379
|
||||
EXEC=/usr/local/bin/redis-server
|
||||
CLIEXEC=/usr/local/bin/redis-cli
|
||||
|
||||
PIDFILE=/var/run/redis_${REDISPORT}.pid
|
||||
CONF="/etc/redis/${REDISPORT}.conf"
|
||||
@@ -14,21 +10,21 @@ case "$1" in
|
||||
start)
|
||||
if [ -f $PIDFILE ]
|
||||
then
|
||||
echo "$PIDFILE exists, process is already running or crashed"
|
||||
echo -n "$PIDFILE exists, process is already running or crashed\n"
|
||||
else
|
||||
echo "Starting Redis server..."
|
||||
echo -n "Starting Redis server...\n"
|
||||
$EXEC $CONF
|
||||
fi
|
||||
;;
|
||||
stop)
|
||||
if [ ! -f $PIDFILE ]
|
||||
then
|
||||
echo "$PIDFILE does not exist, process is not running"
|
||||
echo -n "$PIDFILE does not exist, process is not running\n"
|
||||
else
|
||||
PID=$(cat $PIDFILE)
|
||||
echo "Stopping ..."
|
||||
$CLIEXEC -p $REDISPORT shutdown
|
||||
while [ -x /proc/${PID} ]
|
||||
PID=$(cat $PIDFILE)
|
||||
echo -n "Stopping ...\n"
|
||||
echo -n "SHUTDOWN\r\n" | nc localhost $REDISPORT &
|
||||
while [ -x /proc/${PIDFILE} ]
|
||||
do
|
||||
echo "Waiting for Redis to shutdown ..."
|
||||
sleep 1
|
||||
@@ -36,7 +32,4 @@ case "$1" in
|
||||
echo "Redis stopped"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "Please use start or stop as first argument"
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
# This script is from http://poormansprofiler.org/
|
||||
|
||||
#!/bin/bash
|
||||
nsamples=1
|
||||
sleeptime=0
|
||||
pid=$(pidof redis-server)
|
||||
|
||||
for x in $(seq 1 $nsamples)
|
||||
do
|
||||
gdb -ex "set pagination 0" -ex "thread apply all bt" -batch -p $pid
|
||||
sleep $sleeptime
|
||||
done | \
|
||||
awk '
|
||||
BEGIN { s = ""; }
|
||||
/Thread/ { print s; s = ""; }
|
||||
/^\#/ { if (s != "" ) { s = s "," $4} else { s = $4 } }
|
||||
END { print s }' | \
|
||||
sort | uniq -c | sort -r -n -k 1,1
|
||||
Reference in New Issue
Block a user