2
0
mirror of https://github.com/boostorg/redis.git synced 2026-01-19 04:42:09 +00:00
Files
redis/README.md
2021-01-09 18:56:19 +01:00

5.5 KiB

Aedis

Aedis is a low level redis client designed for scalability and performance while providing an easy and intuitive interface. Some of the supported features are

  • TLS, RESP3 and STL containers.
  • Pipelines (essential for performance).
  • Coroutines, futures and callbacks.

At the moment the biggest missing part is the Attribute data type as its specification seems to be incomplete and I found no way to test them.

Tutorial

Sending a command to a redis server is done as follows

   resp::request req;
   req.hello();
   req.set("Password", {"12345"});
   req.get("Password");
   req.quit();

   co_await resp::async_write(socket, req);

where socket is a tcp socket. Reading on the other hand looks like the following

   resp::response_set<int> res;
   co_await resp::async_read(socket, buffer, res);

The response type above depends on which command is being expected. This library also offers the synchronous functions to parse RESP, in this tutorial however we will focus in async code.

A complete example can be seem bellow

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

      resp::request p;
      p.hello();
      p.rpush("list", {1, 2, 3});
      p.lrange("list");
      p.quit();

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

      std::string buffer;
      resp::response_flat_map<std::string> hello;
      co_await resp::async_read(socket, buffer, hello);

      resp::response_number<int> rpush;
      co_await resp::async_read(socket, buffer, rpush);
      std::cout << rpush.result << std::endl;

      resp::response_list<int> lrange;
      co_await resp::async_read(socket, buffer, lrange);
      print(lrange.result);

      resp::response_simple_string quit;
      co_await resp::async_read(socket, buffer, quit);
      std::cout << quit.result << std::endl;

      resp::response_ignore eof;
      co_await resp::async_read(socket, buffer, eof);

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

The important things to notice above are

  • After connecting RESP3 requires the hello command to be sent.
  • Many commands are sent in the same request, the so called pipeline.
  • Keep reading from the socket until it is closed by the redis server as requested by quit.
  • The response is parsed directly in a buffer that is suitable to the application.

Response buffer

To communicate efficiently with redis it is necessary to understand the possible response types. RESP3 specifies the following data types simple string, Simple error, number, double, bool, big number, null, blob error, verbatim string, blob string, streamed string part. These data types may come in different aggregate types array, push, set, map, attribute. Aedis provides appropriate response types for each of them.

Events

The request type used above keeps a std::queue of commands in the order they are expected to arrive. In addition to that you can specify your own events

enum class myevents
{ ignore
, interesting1
, interesting2
};

and pass it as argument to the request as follows

net::awaitable<void> example()
{
   try {
      resp::request<myevents> req;
      req.rpush("list", {1, 2, 3});
      req.lrange("list", 0, -1, myevents::interesting1);
      req.sadd("set", std::set<int>{3, 4, 5});
      req.smembers("set", myevents::interesting2);
      req.quit();

      auto ex = co_await this_coro::executor;
      tcp::resolver resv(ex);
      tcp_socket socket {ex};
      co_await net::async_connect(socket, resv.resolve("127.0.0.1", "6379"));
      co_await resp::async_write(socket, req);

      std::string buffer;
      for (;;) {
	 switch (req.events.front().second) {
	    case myevents::interesting1:
	    {
	       resp::response_list<int> res;
	       co_await resp::async_read(socket, buffer, res);
	       print(res.result);
	    } break;
	    case myevents::interesting2:
	    {
	       resp::response_set<int> res;
	       co_await resp::async_read(socket, buffer, res);
	       print(res.result);
	    } break;
	    default:
	    {
	       resp::response_ignore res;
	       co_await resp::async_read(socket, buffer, res);
	    }
	 }
	 req.events.pop();
      }
   } catch (std::exception const& e) {
      std::cerr << e.what() << std::endl;
   }
}

Reconnecting and Sentinel support

In production we usually need a way to reconnect to the redis server after a disconnect, some of the reasons are

  1. The server has crashed and has been restarted by systemd.
  2. All connection have been killed by the admin.
  3. A failover operation has started.

It is easy to implement such a mechnism in scalable way using coroutines, for example

net::awaitable<void> example1()
{
   auto ex = co_await this_coro::executor;
   for (;;) {
      try {
	 resp::request req;
	 req.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, req);

	 std::string buffer;
	 for (;;) {
	    resp::response_ignore res;
	    co_await resp::async_read(socket, buffer, res);
	 }
      } catch (std::exception const& e) {
	 std::cerr << "Trying to reconnect ..." << std::endl;
	 stimer timer(ex, std::chrono::seconds{2});
	 co_await timer.async_wait();
      }
   }
}

More sophisticated reconnect strategies using sentinel are also easy to implement using coroutines.