2
0
mirror of https://github.com/boostorg/redis.git synced 2026-02-09 11:22:22 +00:00
Marcelo Zimbres 254215f7d4 Changes:
- Fixes some warning
- Adds version header.
- More improvements in the response type.
- Renames response_vector to response_array.
- Example on how to load all hashes key for a givent pattern from redis.
2020-12-23 20:05:08 +01:00
2020-11-28 21:24:11 +01:00
2020-12-21 16:51:14 +01:00
2020-12-23 20:05:08 +01:00
2020-12-23 20:05:08 +01:00
2020-12-23 20:05:08 +01:00
2020-12-12 18:08:08 +01:00
2020-09-19 10:55:46 +02:00
2019-11-19 22:19:03 +01:00
2020-12-23 20:05:08 +01:00
2020-12-22 19:25:46 +01:00

Aedis

Aedis is a low level redis client designed for scalability while providing and to provide an easy and intuitive interface.

Tutorial

We begin with sync events below and jump to async code thereafter.

Sync

Synchronous code are clear by themselves

int main()
{
   try {
      resp::pipeline p;
      p.set("Password", {"12345"});
      p.quit();

      io_context ioc {1};
      tcp::resolver resv(ioc);
      tcp::socket socket {ioc};
      net::connect(socket, resv.resolve("127.0.0.1", "6379"));
      net::write(socket, buffer(p.payload));

      std::string buffer;
      for (;;) {
	 resp::response_string res;
	 resp::read(socket, buffer, res);
	 std::cout << res.result << std::endl;
      }
   } catch (std::exception const& e) {
      std::cerr << e.what() << std::endl;
   }
}

We keep reading from the socket until it is closed as a result of the quit command. The commands are sent in a pipeline to redis, which greatly improves performance. Notice also we parse the result in the buffer resp::reponse_string. This is overly simplistic for most apps. Typically we will want to parse each response in an appropriate data structure.

Response buffer

Aedis comes with general purpose response buffers that are suitable to parse directly in C++ built-in data types and containers or on your own. For example

int main()
{
   try {
      ... // Like before
      std::string buffer;

      resp::response_int<int> list_size; // Parses into an int
      resp::read(socket, buffer, list_size);
      std::cout << list_size.result << std::endl;

      resp::response_list<int> list; // Parses into a std::list<int>
      resp::read(socket, buffer, list);
      print(list.result);

      resp::response_string ok; // Parses into a std::string
      resp::read(socket, buffer, ok);
      std::cout << ok.result << std::endl;

      resp::response noop;
      resp::read(socket, buffer, noop);

   } catch (std::exception const& e) {
      std::cerr << e.what() << std::endl;
   }
}

Other response types are available also. Structure the code like in the example above is usually not feasible as the commands send are determined dynamically, for that case we have events.

Events

Most times, that events that are added to the pipeline are decided dynamically and we are not interested in the response for some of them. To deal with this we support events. To use them, define an enum class like the one below

enum class myevents
{ ignore
, list
, set
};

Pass it as argument to the pipeline class as below and use the appropriate enum field as the last argument to every command in the pipeline you want to use the result

int main()
{
   try {
      resp::pipeline<myevents> p;
      p.rpush("list", {1, 2, 3});
      p.lrange("list", 0, -1, myevents::list);
      p.sadd("set", std::set<int>{3, 4, 5});
      p.smembers("set", myevents::set);
      p.quit();

      io_context ioc {1};
      tcp::resolver resv(ioc);
      tcp::socket socket {ioc};
      net::connect(socket, resv.resolve("127.0.0.1", "6379"));
      net::write(socket, buffer(p.payload));

      std::string buffer;
      for (;;) {
	 switch (p.events.front()) {
	 case myevents::list:
	 {
	    resp::response_list<int> res;
	    resp::read(socket, buffer, res);
	    print(res.result);
	 } break;
	 case myevents::set:
	 {
	    resp::response_set<int> res;
	    resp::read(socket, buffer, res);
	    print(res.result);
	 } break;
	 default:
	 {
	    resp::response res;
	    resp::read(socket, buffer, res);
	 }
	 }
	 p.events.pop();
      }
   } catch (std::exception const& e) {
      std::cerr << e.what() << std::endl;
   }
}

Async

The sync examples above are good as introduction and can be also useful in production. However, sync code doesn't scale well, this is specially problematic on backends. Fortunately in C++20 it became trivial to convert sync into asyn code. The example below shows the async version of our first sync example.

net::awaitable<void> example1()
{
   auto ex = co_await this_coro::executor;

   tcp::resolver resv(ex);
   auto const r = resv.resolve("127.0.0.1", "6379");

   tcp_socket socket {ex};
   co_await async_connect(socket, r);

   resp::pipeline p;
   p.set("Password", {"12345"});
   p.quit();

   co_await async_write(socket, buffer(p.payload));

   std::string buffer;
   for (;;) {
      resp::response_string res;
      co_await resp::async_read(socket, buffer, res);
      std::cout << res.result << std::endl;
   }
}

Basically, we only have to replace read with async_read and use the co_await keyword.

Reconnecting and Sentinel support

On some occasions we will want to reconnet to redis server after a disconnect. This can happen for a couple of reasons, for example, the redis-server may crash and be restarted by systemd.

Simple reconnet

It is trivial to implement a reconnect using a coroutine

net::awaitable<void> example1()
{
   auto ex = co_await this_coro::executor;
   for (;;) {
      try {
	 resp::pipeline p;
	 p.set("Password", {"12345"});
	 p.quit();

	 tcp::resolver resv(ex);
	 auto const r = resv.resolve("127.0.0.1", "6379");
	 tcp_socket socket {ex};
	 co_await async_connect(socket, r);
	 co_await async_write(socket, buffer(p.payload));

	 std::string buffer;
	 for (;;) {
	    resp::response_string res;
	    co_await resp::async_read(socket, buffer, res);
	    std::cout << res.result << std::endl;
	 }
      } catch (std::exception const& e) {
	 std::cerr << "Error: " << e.what() << std::endl;
	 stimer timer(ex);
	 timer.expires_after(std::chrono::seconds{2});
	 co_await timer.async_wait();
      }
   }
}

For many usecases this is often enough. A more sophisticated reconnect strategy however is to use a redis-sentinel.

Sentinel

Description
Mirrored via gitea-mirror
Readme 6.7 MiB
Languages
C++ 96%
Python 1.4%
CMake 1.2%
Java 0.3%
Rust 0.3%
Other 0.7%