Commit ebf341ee9e58fd57cdc32f521e20b052b3b732da

Authored by Hayk Martirosyan
1 parent e07c208e

Write tutorial for core functionality

Explain core commands, pubsub, and reply types.
Showing 2 changed files with 195 additions and 27 deletions
README.md
@@ -40,6 +40,198 @@ Local Redis server, TCP connection: @@ -40,6 +40,198 @@ Local Redis server, TCP connection:
40 Results are comparable to that of an average laptop. On a high-end machine, 40 Results are comparable to that of an average laptop. On a high-end machine,
41 `speed_test_async_multi` usually tops 1,000,000 commands/s. 41 `speed_test_async_multi` usually tops 1,000,000 commands/s.
42 42
  43 +## Tutorial
  44 +This section introduces the main features of redox. Look in the `examples/` for more inspiration.
  45 +
  46 +#### Hello world
  47 +Here is the simplest possible redox program:
  48 +
  49 + #include <iostream>
  50 + #include <redox.hpp>
  51 +
  52 + using namespace std;
  53 + using namespace redox;
  54 +
  55 + int main(int argc, char* argv[]) {
  56 +
  57 + Redox rdx = {"localhost", 6379};
  58 + if(!rdx.connect()) return 1;
  59 +
  60 + rdx.set("hello", "world!");
  61 + cout << "Hello, " << rdx.get("hello") << endl;
  62 +
  63 + rdx.disconnect();
  64 + return 0;
  65 + }
  66 +
  67 +Compile and run:
  68 +
  69 + $ g++ hello.cpp -o hello -std=c++11 -lredox -lev -lhiredis
  70 + $ ./hello
  71 + Hello, world!
  72 +
  73 +This example is synchronous, in the sense that the commands don't return until
  74 +a reply is received from the server.
  75 +
  76 +#### Asynchronous commands
  77 +In a high-performance application, we don't want to wait for a reply, but instead
  78 +do other work.
  79 +The `command` method accepts a Redis command and a callback to be invoked when a reply is received.
  80 +
  81 + rdx.command<string>("GET hello", [](Command<string>& c) {
  82 + if(c.ok()) {
  83 + cout << "Hello, async " << c.reply() << endl;
  84 + } else {
  85 + cerr << "Command has error code " << c.status() << endl;
  86 + }
  87 + });
  88 +
  89 +This statement tells redox to run the command `GET hello`. The `<string>` template
  90 +parameter means that we want the reply to be put into a string and that we expect
  91 +the server to respond with something that can be put into a string. The full list
  92 +of reply types is listed in this document and covers convenient access to anything
  93 +returned from the Redis protocol.
  94 +
  95 +The second argument is a callback function that accepts a reference to a Command object
  96 +of the requested reply type. The Command object contains the reply and any error
  97 +information. If `c.ok()` is true, the expected reply is accessed from
  98 +`c.reply()` (a string in this case). If `c.ok()` is false, then the error
  99 +code is given by `c.status()`, which can report an error or nil reply, a reply of
  100 +the wrong type, a send error, etc. The callback is guaranteed to be invoked
  101 +exactly once, and the memory for the Command object is freed automatically once
  102 +the callback returns.
  103 +
  104 +Here is a simple example of running `GET hello` asynchronously ten times:
  105 +
  106 + Redox rdx; // Localhost by default
  107 + if(!rdx.connect()) return 1; // Block until connected
  108 +
  109 + auto got_reply = [](Command<string>& c) {
  110 + if(!c.ok()) return;
  111 + cout << c.cmd() << ": " << c.reply() << endl;
  112 + };
  113 +
  114 + for(int i = 0; i < 10; i++) rdx.command<string>("GET hello", got_reply);
  115 +
  116 + // Do useful work
  117 + this_thread::sleep_for(chrono::milliseconds(10));
  118 +
  119 + rdx.disconnect(); // Block until disconnected
  120 +
  121 +The `.command()` method returns immediately, so this program doesn't wait for a reply
  122 +from the server - it just pauses for ten milliseconds and then shuts down. If we want to
  123 +shut down after we get all replies, we could do something like this:
  124 +
  125 + Redox rdx;
  126 + if(!rdx.connect()) return 1;
  127 +
  128 + int total = 10; // Number of commands to run
  129 + atomic_int count(0); // Number of replies expected
  130 + auto got_reply = [&](Command<string>& c) {
  131 + count++;
  132 + if(c.ok()) cout << c.cmd() << " #" << count << ": " << c.reply() << endl;
  133 + if(count == total) rdx.stop(); // Signal to shut down
  134 + };
  135 +
  136 + for(int i = 0; i < total; i++) rdx.command<string>("GET hello", got_reply);
  137 +
  138 + // Do useful work
  139 +
  140 + rdx.wait(); // Block until shut down complete
  141 +
  142 +This example tracks of how how many replies are received and signals the Redox
  143 +instance to stop once they all process. We use an `std::atomic_int` to be safe
  144 +because the callback is invoked from a separate thread. The `stop()` method
  145 +signals Redox to shut down its event loop and disconnect from Redis. The `wait()`
  146 +method blocks until `stop()` has been called and everything is brought down.
  147 +The `disconnect()` method used earlier is just a call to `stop()` and then a
  148 +call to `wait()`.
  149 +
  150 +#### Synchronous commands
  151 +Redox implements synchronous commands by running asynchronous commands and waiting
  152 +on them with condition variables. That way, we can reap the benefits of pipelining
  153 +between synchronous commands in different threads. The `commandSync` method provides
  154 +a similar API to `command`, but instead of a callback returns a Command object when
  155 +a reply is received.
  156 +
  157 + Command<string>& c = rdx.commandSync<string>("GET hello");
  158 + if(c.ok()) cout << c.cmd() << ": " << c.reply() << endl;
  159 + c.free();
  160 +
  161 +When using synchronous commands, the user is responsible for freeing the memory of
  162 +the Command object by calling `c.free()`. The `c.cmd()` method just returns the
  163 +command string (`GET hello` in this case).
  164 +
  165 +#### Looping and delayed commands
  166 +We often want to run commands on regular invervals. Redox provides the `commandLoop`
  167 +method to accomplish this. It is easier to use and more efficient than running individual
  168 +commands in a loop, because it only creates a single Command object.
  169 +`commandLoop` takes a command string, a callback, and an interval (in seconds)
  170 +to repeat the command. It then runs the command on the given interval until the user
  171 +calls `c.free()`.
  172 +
  173 + Command<string>& cmd = rdx.commandLoop<string>("GET hello", [](Command<string>& c) {
  174 + if(c.ok()) cout << c.cmd() << ": " << c.reply() << endl;
  175 + }, 0.1);
  176 +
  177 + this_thread::sleep_for(chrono::seconds(1));
  178 + cmd.free();
  179 + rdx.disconnect();
  180 +
  181 +Finally, `commandDelayed` runs a command after a specified delay (in seconds). It does
  182 +not return a command object, because the memory is automatically freed after the callback
  183 +is invoked.
  184 +
  185 + rdx.commandDelayed<string>("GET hello", [](Command<string>& c) {
  186 + if(c.ok()) cout << c.cmd() << ": " << c.reply() << endl;
  187 + }, 1);
  188 + this_thread::sleep_for(chrono::seconds(2));
  189 +
  190 +#### Convenience methods
  191 +The four methods `command`, `commandSync`, `commandLoop`, and `commandDelayed` form
  192 +the core of Redox's functionality. There are convenience methods provided that are
  193 +simple wrappers over the core methods. Some examples of those are `.get()`, `.set()`,
  194 +`.del()`, and `.publish()`. These methods are nice because they return simple values,
  195 +and there are no Command objects or template parameters. However, they make strong
  196 +assumptions about how to deal with errors (ignore or throw exceptions), and since
  197 +their implementations are a few lines of code it is often easier to create custom
  198 +convenience methods for your application.
  199 +
  200 +#### Publisher / Subscriber
  201 +Redox provides an API for the pub/sub functionality of Redis. Publishing is done just like
  202 +any other command using a Redox instance. There is a separate Subscriber class that
  203 +receives messages and provides subscribe/unsubscribe and psubscribe/punsubscribe methods.
  204 +
  205 + Redox rdx; Subscriber sub;
  206 + if(!rdx.connect() || !sub.connect()) return 1;
  207 +
  208 + sub.subscribe("hello", [](const string& topic, const string& msg) {
  209 + cout << topic << ": " << msg << endl;
  210 + });
  211 +
  212 + for(int i = 0; i < 10; i++) {
  213 + rdx.publish("hello", "this is a pubsub message");
  214 + this_thread::sleep_for(chrono::milliseconds(500));
  215 + }
  216 +
  217 + sub.disconnect(); rdx.disconnect();
  218 +
  219 +## Reply types
  220 +These the available template parameters in redox and the Redis
  221 +[return types](http://redis.io/topics/protocol) they can hold.
  222 +If a given command returns an incompatible type you will get
  223 +a `WRONG_TYPE` or `NIL_REPLY` status.
  224 +
  225 + * `<redisReply*>`: All reply types, returns the hiredis struct directly
  226 + * `<char*>`: Simple Strings, Bulk Strings
  227 + * `<std::string>`: Simple Strings, Bulk Strings
  228 + * `<long long int>`: Integers
  229 + * `<int>`: Integers (careful about overflow, `long long int` recommended)
  230 + * `<std::nullptr_t>`: Null Bulk Strings, any other receiving a nil reply will get a NIL_REPLY status
  231 + * `<std::vector<std::string>>`: Arrays of Simple Strings or Bulk Strings (in received order)
  232 + * `<std::set<std::string>>`: Arrays of Simple Strings or Bulk Strings (in sorted order)
  233 + * `<std::unordered_set<std::string>>`: Arrays of Simple Strings or Bulk Strings (in no order)
  234 +
43 ## Installation 235 ## Installation
44 Instructions provided are for Ubuntu, but all components are platform-independent. 236 Instructions provided are for Ubuntu, but all components are platform-independent.
45 237
@@ -72,27 +264,3 @@ then: @@ -72,27 +264,3 @@ then:
72 cmake -Dtests=ON .. 264 cmake -Dtests=ON ..
73 make test_redox 265 make test_redox
74 ./test_redox 266 ./test_redox
75 -  
76 -## Tutorial  
77 -Here is a hello world program for redox:  
78 -  
79 - #include <iostream>  
80 - #include "redox.hpp"  
81 -  
82 - int main(int argc, char* argv[]) {  
83 -  
84 - redox::Redox rdx = {"localhost", 6379};  
85 - if(!rdx.connect()) return 1;  
86 -  
87 - rdx.set("hello", "world!");  
88 - std::cout << "Hello, " << rdx.get("hello") << std::endl;  
89 -  
90 - rdx.disconnect();  
91 - return 0;  
92 - }  
93 -  
94 -Compile and run:  
95 -  
96 - $ g++ hello.cpp -o hello -std=c++11 -lredox -lev -lhiredis  
97 - $ ./hello  
98 - Hello, world!  
examples/basic.cpp
@@ -3,15 +3,16 @@ @@ -3,15 +3,16 @@
3 */ 3 */
4 4
5 #include <iostream> 5 #include <iostream>
6 -#include "redox.hpp" 6 +#include <redox.hpp>
7 7
8 using namespace std; 8 using namespace std;
9 using redox::Redox; 9 using redox::Redox;
10 using redox::Command; 10 using redox::Command;
  11 +using redox::Subscriber;
11 12
12 int main(int argc, char* argv[]) { 13 int main(int argc, char* argv[]) {
13 14
14 - Redox rdx = {"localhost", 6379, nullptr, cout, redox::log::Info}; // Initialize Redox 15 + Redox rdx = {"localhost", 6379}; // Initialize Redox
15 16
16 if(!rdx.connect()) return 1; // Start the event loop 17 if(!rdx.connect()) return 1; // Start the event loop
17 18
@@ -22,6 +23,5 @@ int main(int argc, char* argv[]) { @@ -22,6 +23,5 @@ int main(int argc, char* argv[]) {
22 23
23 cout << "key = \"occupation\", value = \"" << rdx.get("occupation") << "\"" << endl; 24 cout << "key = \"occupation\", value = \"" << rdx.get("occupation") << "\"" << endl;
24 25
25 - rdx.disconnect();  
26 return 0; 26 return 0;
27 } 27 }