Linux下使用hiredis库实现优先级队列

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/stayneckwind2/article/details/79429977

一、背景需求

    需求主要为:

    1、优先级分为高、中、低(优先级队列);

    2、支持多生产者、多消费者模型(解决竞争);

    3、期间进程退出,队列内容不会丢失(持久化);

    选型方面考虑了第三方工具,先调研了一下Redis,发现其中LIST数据结构非常适用上诉需求;

二、相关知识

2.1 List结构

     Redis List是Redis最重要的数据结构之一,内部实现是一个双向链表,链表上的每个节点都包含一个字符串。

    主要功能有:    

    1、从链表两端推入(push)、弹出(pop)元素;

    2、根据偏移量对链表进行修剪(trim);

    3、读取单个或者多个元素;

    4、根据值查找或者移出元素;

2.2 List结构相关命令(help @list)

  BLPOP key [key ...] timeout
  summary: Remove and get the first element in a list, or block until one is available

  BRPOP key [key ...] timeout
  summary: Remove and get the last element in a list, or block until one is available

  BRPOPLPUSH source destination timeout
  summary: Pop a value from a list, push it to another list and return it; or block until one is available

  LINDEX key index
  summary: Get an element from a list by its index

  LINSERT key BEFORE|AFTER pivot value
  summary: Insert an element before or after another element in a list

  LLEN key 
  summary: Get the length of a list

  LPOP key 
  summary: Remove and get the first element in a list

  LPUSH key value [value ...]
  summary: Prepend one or multiple values to a list

  LPUSHX key value
  summary: Prepend a value to a list, only if the list exists

  LRANGE key start stop
  summary: Get a range of elements from a list

  LREM key count value
  summary: Remove elements from a list

  LSET key index value
  summary: Set the value of an element in a list by its index

  LTRIM key start stop
  summary: Trim a list to the specified range

  RPOP key 
  summary: Remove and get the last element in a list

  RPOPLPUSH source destination
  summary: Remove the last element in a list, prepend it to another list and return it

  RPUSH key value [value ...]
  summary: Append one or multiple values to a list

  RPUSHX key value
  summary: Append a value to a list, only if the list exists

2.3 基础C语言接口

Redis源码包中包含了C语言接口库Hiredis(deps/hiredis),接口的快速入门如下:

1、连接Redis实例

redisContext *redisConnect(const char *ip, int port);
The function `redisConnect` is used to create a so-called `redisContext`. 
The context is where Hiredis holds state for a connection.
The `redisContext` struct has an integer `err` field that is non-zero when the connection is in an error state.
The field `errstr` will contain a string with a description of the error.

2、发送命令

void *redisCommand(redisContext *c, const char *format, ...);
There are several ways to issue commands to Redis. The first that will be introduced is `redisCommand`
    reply = redisCommand(context, "SET foo bar");
The specifier `%s` interpolates a string in the command, and uses `strlen` to determine the length of the string:
    reply = redisCommand(context, "SET foo %s", value);
When you need to pass binary safe strings in a command, the `%b` specifier can be used.
Together with a pointer to the string, it requires a `size_t` length argument of the string:
    reply = redisCommand(context, "SET foo %b", value, (size_t) valuelen);

3、解析回复

The standard replies that `redisCommand` are of the type `redisReply`. 
The `type` field in the `redisReply` should be used to test what kind of reply was received:
1) REDIS_REPLY_STATUS
    The command replied with a status reply. The status string can be accessed using `reply->str`.
    The length of this string can be accessed using `reply->len`.
2) REDIS_REPLY_ERROR
    The command replied with an error. The error string can be accessed identical to `REDIS_REPLY_STATUS`.
3) REDIS_REPLY_INTEGER
    The command replied with an integer. The integer value can be accessed using the `reply->integer` field of type `long long`.
4) REDIS_REPLY_NIL
    The command replied with a **nil** object. There is no data to access.
5) REDIS_REPLY_STRING
    A bulk (string) reply. The value of the reply can be accessed using `reply->str`. 
    The length of this string can be accessed using `reply->len`.
6) REDIS_REPLY_ARRAY
    A multi bulk reply. The number of elements in the multi bulk reply is stored in `reply->elements`.
    Every element in the multi bulk reply is a `redisReply` object as well and can be accessed via `reply->element[..index..]`.
    Redis may reply with nested arrays but this is fully supported.

4、释放、断开实例连接

Replies should be freed using the `freeReplyObject()` function.
To disconnect and free the context the following function can be used: void redisFree(redisContext *c); 

三、编程实例

该实例使用LPUSH进行入列操作、BRPOP进行出列操作,由于BRPOP支持超时阻塞,所以可以避免轮询带来的延迟

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>

#include <hiredis.h>

#define FAILURE EXIT_FAILURE
#define SUCCESS EXIT_SUCCESS

#define FREE_REDIS(c) if (c) { \
        redisFree(c); \
        c = NULL; \
}

#define FREE_REPLY(r) if (r) { \
        freeReplyObject(r); \
        r = NULL; \
}

static void __do_handle(redisReply *reply)
{
        if (reply->elements == 2) {
                printf("DEQUEUE: %s %s\n",
                           reply->element[0]->str, reply->element[1]->str);
                usleep(3000); //TODO something
        }
        else {
                printf("Unknow\n");
        }
}

int test_enqueue(redisContext *c, int times)
{
        int ix = 0;
        const char key[3][10] = {"hig", "mid", "low"};
        redisReply *reply = NULL;

        for (ix = 0; ix < times; ix++) {
                reply = redisCommand(c, "LPUSH %s element-%05d", key[random()%3], ix);
                FREE_REPLY(reply);
        }

        return SUCCESS;
}

int test_dequeue(redisContext *c)
{
        int ret = FAILURE;
        redisReply *reply = NULL;

        while (1) {
                /* Dequeue timeout 10s */
                reply = redisCommand(c, "BRPOP hig mid low 10");
                switch (reply->type) {
                case REDIS_REPLY_NIL:
                        printf("empty timeout\n");
                        continue;

                case REDIS_REPLY_ARRAY:
                        __do_handle(reply);
                        continue;

                default:
                        printf("Unknow type: %d\n", reply->type);
                        ret = FAILURE;
                        goto _E1;
                }
                FREE_REPLY(reply);
        }

        ret = SUCCESS;
_E1:
        FREE_REPLY(reply);
        return ret;
}

int main(int argc, char *argv[])
{
        int ret = FAILURE;

        struct timeval timeout = { 1, 500000 }; // 1.5 seconds
        const char *hostname = "127.0.0.1";
        int port = 6379;

        redisContext *c = NULL;
        c = redisConnectWithTimeout(hostname, port, timeout);
        if (c == NULL || c->err) {
                if (c) {
                        printf("Connection error: %s\n", c->errstr);
                }
                else {
                        printf("Connection error: can't allocate redis context\n");
                }
                ret = FAILURE;
                goto _E1;
        }

        printf("Connect success\n");

        if (argv[1]) {
                assert(SUCCESS == test_enqueue(c, atoi(argv[1])));
        }
        else {
                assert(SUCCESS == test_dequeue(c));
        }

        printf("Done\n");
        ret = SUCCESS;
_E1:
        /* Disconnects and frees the context */
        FREE_REDIS(c);

        printf("Result:\t\t\t\t[%s]\n", ret ? "Failure" : "Success");
        exit(ret ? EXIT_FAILURE : EXIT_SUCCESS);
}

四、执行结果

使用单生产者、双消费者进行测试:

producter顺序推送 element-00001 到 element-00019,随机优先级入列

customer-1 信息如下:


customer-2 信息如下:


可见第一批信息 element-00001、element-00002刚入列就分别被 customer-1、customer-2抢到,

然后在usleep(3000)期间,producter将20个elements全部入列,下一轮由两个customer进行优先级排序瓜分;


参考文章:

[1] https://github.com/antirez/redis/blob/unstable/deps/hiredis/README.md

[2] https://github.com/antirez/redis/blob/unstable/README.md


猜你喜欢

转载自blog.csdn.net/stayneckwind2/article/details/79429977