Skip to content

Commit 674be38

Browse files
committedApr 16, 2014
Add redis database backend
1 parent db60ae0 commit 674be38

File tree

7 files changed

+322
-1
lines changed

7 files changed

+322
-1
lines changed
 

‎src/CMakeLists.txt

+26
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,25 @@ if(ENABLE_LEVELDB)
291291
endif(LEVELDB_LIBRARY AND LEVELDB_INCLUDE_DIR)
292292
endif(ENABLE_LEVELDB)
293293

294+
set(USE_REDIS 0)
295+
296+
OPTION(ENABLE_REDIS "Enable redis backend" 1)
297+
298+
if(ENABLE_REDIS)
299+
find_library(REDIS_LIBRARY hiredis)
300+
find_path(REDIS_INCLUDE_DIR hiredis.h PATH_SUFFIXES hiredis)
301+
message(STATUS "redis library: ${REDIS_LIBRARY}")
302+
message(STATUS "redis headers: ${REDIS_INCLUDE_DIR}")
303+
if(REDIS_LIBRARY AND REDIS_INCLUDE_DIR)
304+
set(USE_REDIS 1)
305+
message(STATUS "redis backend enabled")
306+
include_directories(${REDIS_INCLUDE_DIR})
307+
else(REDIS_LIBRARY AND REDIS_INCLUDE_DIR)
308+
set(USE_REDIS 0)
309+
message(STATUS "redis not found!")
310+
endif(REDIS_LIBRARY AND REDIS_INCLUDE_DIR)
311+
endif(ENABLE_REDIS)
312+
294313
configure_file(
295314
"${PROJECT_SOURCE_DIR}/cmake_config.h.in"
296315
"${PROJECT_BINARY_DIR}/cmake_config.h"
@@ -368,6 +387,7 @@ set(common_SRCS
368387
database-dummy.cpp
369388
database-leveldb.cpp
370389
database-sqlite3.cpp
390+
database-redis.cpp
371391
player.cpp
372392
test.cpp
373393
sha1.cpp
@@ -533,6 +553,9 @@ if(BUILD_CLIENT)
533553
if (USE_LEVELDB)
534554
target_link_libraries(${PROJECT_NAME} ${LEVELDB_LIBRARY})
535555
endif(USE_LEVELDB)
556+
if (USE_REDIS)
557+
target_link_libraries(${PROJECT_NAME} ${REDIS_LIBRARY})
558+
endif(USE_REDIS)
536559
endif(BUILD_CLIENT)
537560

538561
if(BUILD_SERVER)
@@ -550,6 +573,9 @@ if(BUILD_SERVER)
550573
if (USE_LEVELDB)
551574
target_link_libraries(${PROJECT_NAME}server ${LEVELDB_LIBRARY})
552575
endif(USE_LEVELDB)
576+
if (USE_REDIS)
577+
target_link_libraries(${PROJECT_NAME}server ${REDIS_LIBRARY})
578+
endif(USE_REDIS)
553579
if(USE_CURL)
554580
target_link_libraries(
555581
${PROJECT_NAME}server

‎src/cmake_config.h.in

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#define CMAKE_STATIC_SHAREDIR "@SHAREDIR@"
1515
#define CMAKE_USE_LEVELDB @USE_LEVELDB@
1616
#define CMAKE_USE_LUAJIT @USE_LUAJIT@
17+
#define CMAKE_USE_REDIS @USE_REDIS@
1718
#define CMAKE_VERSION_MAJOR @VERSION_MAJOR@
1819
#define CMAKE_VERSION_MINOR @VERSION_MINOR@
1920
#define CMAKE_VERSION_PATCH @VERSION_PATCH@

‎src/config.h

+6
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@
2828
#define USE_LUAJIT 0
2929
#endif
3030

31+
#ifndef USE_REDIS
32+
#define USE_REDIS 0
33+
#endif
34+
3135
#ifdef USE_CMAKE_CONFIG_H
3236
#include "cmake_config.h"
3337
#undef PROJECT_NAME
@@ -48,6 +52,8 @@
4852
#define USE_LEVELDB CMAKE_USE_LEVELDB
4953
#undef USE_LUAJIT
5054
#define USE_LUAJIT CMAKE_USE_LUAJIT
55+
#undef USE_REDIS
56+
#define USE_REDIS CMAKE_USE_REDIS
5157
#undef VERSION_MAJOR
5258
#define VERSION_MAJOR CMAKE_VERSION_MAJOR
5359
#undef VERSION_MINOR

‎src/database-redis.cpp

+224
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
/*
2+
Minetest
3+
Copyright (C) 2014 celeron55, Perttu Ahola <celeron55@gmail.com>
4+
5+
This program is free software; you can redistribute it and/or modify
6+
it under the terms of the GNU Lesser General Public License as published by
7+
the Free Software Foundation; either version 2.1 of the License, or
8+
(at your option) any later version.
9+
10+
This program is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
GNU Lesser General Public License for more details.
14+
15+
You should have received a copy of the GNU Lesser General Public License along
16+
with this program; if not, write to the Free Software Foundation, Inc.,
17+
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*/
19+
20+
#include "config.h"
21+
22+
#if USE_REDIS
23+
/*
24+
Redis databases
25+
*/
26+
27+
28+
#include "database-redis.h"
29+
#include <hiredis.h>
30+
31+
#include "map.h"
32+
#include "mapsector.h"
33+
#include "mapblock.h"
34+
#include "serialization.h"
35+
#include "main.h"
36+
#include "settings.h"
37+
#include "log.h"
38+
39+
Database_Redis::Database_Redis(ServerMap *map, std::string savedir)
40+
{
41+
Settings conf;
42+
conf.readConfigFile((std::string(savedir) + DIR_DELIM + "world.mt").c_str());
43+
std::string tmp;
44+
try {
45+
tmp = conf.get("redis_address");
46+
hash = conf.get("redis_hash");
47+
} catch(SettingNotFoundException e) {
48+
throw SettingNotFoundException("Set redis_address and redis_hash in world.mt to use the redis backend");
49+
}
50+
const char *addr = tmp.c_str();
51+
int port = conf.exists("redis_port") ? conf.getU16("redis_port") : 6379;
52+
ctx = redisConnect(addr, port);
53+
if(!ctx)
54+
throw FileNotGoodException("Cannot allocate redis context");
55+
else if(ctx->err) {
56+
std::string err = std::string("Connection error: ") + ctx->errstr;
57+
redisFree(ctx);
58+
throw FileNotGoodException(err);
59+
}
60+
srvmap = map;
61+
}
62+
63+
int Database_Redis::Initialized(void)
64+
{
65+
return 1;
66+
}
67+
68+
void Database_Redis::beginSave() {
69+
redisReply *reply;
70+
reply = (redisReply*) redisCommand(ctx, "MULTI");
71+
if(!reply)
72+
throw FileNotGoodException(std::string("redis command 'MULTI' failed: ") + ctx->errstr);
73+
freeReplyObject(reply);
74+
}
75+
76+
void Database_Redis::endSave() {
77+
redisReply *reply;
78+
reply = (redisReply*) redisCommand(ctx, "EXEC");
79+
if(!reply)
80+
throw FileNotGoodException(std::string("redis command 'EXEC' failed: ") + ctx->errstr);
81+
freeReplyObject(reply);
82+
}
83+
84+
void Database_Redis::saveBlock(MapBlock *block)
85+
{
86+
DSTACK(__FUNCTION_NAME);
87+
/*
88+
Dummy blocks are not written
89+
*/
90+
if(block->isDummy())
91+
{
92+
return;
93+
}
94+
95+
// Format used for writing
96+
u8 version = SER_FMT_VER_HIGHEST_WRITE;
97+
// Get destination
98+
v3s16 p3d = block->getPos();
99+
100+
/*
101+
[0] u8 serialization version
102+
[1] data
103+
*/
104+
105+
std::ostringstream o(std::ios_base::binary);
106+
o.write((char*)&version, 1);
107+
// Write basic data
108+
block->serialize(o, version, true);
109+
// Write block to database
110+
std::string tmp1 = o.str();
111+
std::string tmp2 = i64tos(getBlockAsInteger(p3d));
112+
113+
redisReply *reply;
114+
reply = (redisReply*) redisCommand(ctx, "HSET %s %s %b", hash.c_str(), tmp2.c_str(), tmp1.c_str(), tmp1.size());
115+
if(!reply)
116+
throw FileNotGoodException(std::string("redis command 'HSET %s %s %b' failed: ") + ctx->errstr);
117+
if(reply->type == REDIS_REPLY_ERROR)
118+
throw FileNotGoodException("Failed to store block in Database");
119+
120+
// We just wrote it to the disk so clear modified flag
121+
block->resetModified();
122+
}
123+
124+
MapBlock* Database_Redis::loadBlock(v3s16 blockpos)
125+
{
126+
v2s16 p2d(blockpos.X, blockpos.Z);
127+
128+
std::string tmp = i64tos(getBlockAsInteger(blockpos));
129+
redisReply *reply;
130+
reply = (redisReply*) redisCommand(ctx, "HGET %s %s", hash.c_str(), tmp.c_str());
131+
if(!reply)
132+
throw FileNotGoodException(std::string("redis command 'HGET %s %s' failed: ") + ctx->errstr);
133+
134+
if (reply->type == REDIS_REPLY_STRING && reply->len == 0) {
135+
freeReplyObject(reply);
136+
errorstream << "Blank block data in database (reply->len == 0) ("
137+
<< blockpos.X << "," << blockpos.Y << "," << blockpos.Z << ")" << std::endl;
138+
139+
if (g_settings->getBool("ignore_world_load_errors")) {
140+
errorstream << "Ignoring block load error. Duck and cover! "
141+
<< "(ignore_world_load_errors)" << std::endl;
142+
} else {
143+
throw SerializationError("Blank block data in database");
144+
}
145+
return NULL;
146+
}
147+
148+
if (reply->type == REDIS_REPLY_STRING) {
149+
/*
150+
Make sure sector is loaded
151+
*/
152+
MapSector *sector = srvmap->createSector(p2d);
153+
154+
try {
155+
std::istringstream is(std::string(reply->str, reply->len), std::ios_base::binary);
156+
freeReplyObject(reply); // std::string copies the memory so we can already do this here
157+
u8 version = SER_FMT_VER_INVALID;
158+
is.read((char *)&version, 1);
159+
160+
if (is.fail())
161+
throw SerializationError("ServerMap::loadBlock(): Failed"
162+
" to read MapBlock version");
163+
164+
MapBlock *block = NULL;
165+
bool created_new = false;
166+
block = sector->getBlockNoCreateNoEx(blockpos.Y);
167+
if (block == NULL)
168+
{
169+
block = sector->createBlankBlockNoInsert(blockpos.Y);
170+
created_new = true;
171+
}
172+
173+
// Read basic data
174+
block->deSerialize(is, version, true);
175+
176+
// If it's a new block, insert it to the map
177+
if (created_new)
178+
sector->insertBlock(block);
179+
180+
// We just loaded it from, so it's up-to-date.
181+
block->resetModified();
182+
}
183+
catch (SerializationError &e)
184+
{
185+
errorstream << "Invalid block data in database"
186+
<< " (" << blockpos.X << "," << blockpos.Y << "," << blockpos.Z
187+
<< ") (SerializationError): " << e.what() << std::endl;
188+
// TODO: Block should be marked as invalid in memory so that it is
189+
// not touched but the game can run
190+
191+
if (g_settings->getBool("ignore_world_load_errors")) {
192+
errorstream << "Ignoring block load error. Duck and cover! "
193+
<< "(ignore_world_load_errors)" << std::endl;
194+
} else {
195+
throw SerializationError("Invalid block data in database");
196+
}
197+
}
198+
199+
return srvmap->getBlockNoCreateNoEx(blockpos); // should not be using this here
200+
}
201+
return NULL;
202+
}
203+
204+
void Database_Redis::listAllLoadableBlocks(std::list<v3s16> &dst)
205+
{
206+
redisReply *reply;
207+
reply = (redisReply*) redisCommand(ctx, "HKEYS %s", hash.c_str());
208+
if(!reply)
209+
throw FileNotGoodException(std::string("redis command 'HKEYS %s' failed: ") + ctx->errstr);
210+
if(reply->type != REDIS_REPLY_ARRAY)
211+
throw FileNotGoodException("Failed to get keys from database");
212+
for(size_t i = 0; i < reply->elements; i++)
213+
{
214+
assert(reply->element[i]->type == REDIS_REPLY_STRING);
215+
dst.push_back(getIntegerAsBlock(stoi64(reply->element[i]->str)));
216+
}
217+
freeReplyObject(reply);
218+
}
219+
220+
Database_Redis::~Database_Redis()
221+
{
222+
redisFree(ctx);
223+
}
224+
#endif

‎src/database-redis.h

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
Minetest
3+
Copyright (C) 2014 celeron55, Perttu Ahola <celeron55@gmail.com>
4+
5+
This program is free software; you can redistribute it and/or modify
6+
it under the terms of the GNU Lesser General Public License as published by
7+
the Free Software Foundation; either version 2.1 of the License, or
8+
(at your option) any later version.
9+
10+
This program is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
GNU Lesser General Public License for more details.
14+
15+
You should have received a copy of the GNU Lesser General Public License along
16+
with this program; if not, write to the Free Software Foundation, Inc.,
17+
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*/
19+
20+
#ifndef DATABASE_REDIS_HEADER
21+
#define DATABASE_REDIS_HEADER
22+
23+
#include "config.h"
24+
25+
#if USE_REDIS
26+
27+
#include "database.h"
28+
#include <hiredis.h>
29+
#include <string>
30+
31+
class ServerMap;
32+
33+
class Database_Redis : public Database
34+
{
35+
public:
36+
Database_Redis(ServerMap *map, std::string savedir);
37+
virtual void beginSave();
38+
virtual void endSave();
39+
virtual void saveBlock(MapBlock *block);
40+
virtual MapBlock* loadBlock(v3s16 blockpos);
41+
virtual void listAllLoadableBlocks(std::list<v3s16> &dst);
42+
virtual int Initialized(void);
43+
~Database_Redis();
44+
private:
45+
ServerMap *srvmap;
46+
redisContext *ctx;
47+
std::string hash;
48+
};
49+
#endif
50+
#endif

‎src/main.cpp

+8-1
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
8484
#ifdef USE_LEVELDB
8585
#include "database-leveldb.h"
8686
#endif
87+
#if USE_REDIS
88+
#include "database-redis.h"
89+
#endif
8790

8891
/*
8992
Settings.
@@ -1242,7 +1245,7 @@ int main(int argc, char *argv[])
12421245
}
12431246
if (!world_mt.exists("backend")) {
12441247
errorstream << "Please specify your current backend in world.mt file:"
1245-
<< std::endl << " backend = {sqlite3|leveldb|dummy}" << std::endl;
1248+
<< std::endl << " backend = {sqlite3|leveldb|redis|dummy}" << std::endl;
12461249
return 1;
12471250
}
12481251
std::string backend = world_mt.get("backend");
@@ -1257,6 +1260,10 @@ int main(int argc, char *argv[])
12571260
else if (migrate_to == "leveldb")
12581261
new_db = new Database_LevelDB(&(ServerMap&)server.getMap(), world_path);
12591262
#endif
1263+
#if USE_REDIS
1264+
else if (migrate_to == "redis")
1265+
new_db = new Database_Redis(&(ServerMap&)server.getMap(), world_path);
1266+
#endif
12601267
else {
12611268
errorstream << "Migration to " << migrate_to << " is not supported" << std::endl;
12621269
return 1;

2 commit comments

Comments
 (2)

JPRuehmann commented on Apr 18, 2014

@JPRuehmann

Hello
Two Questions,
First shouldn´t be the settings in the minetest.conf instead of the world.mt?
At least the redis_address.
two could you tell me where or how to obtain this ominious redis_hash?
Thanks,
JPR

sfan5 commented on Apr 18, 2014

@sfan5
CollaboratorAuthor
Please sign in to comment.