使用C++操作Redis5.0新特性Stream实现发布订阅

关于Redis5.0新出的这个stream数据结构的用法与介绍,可以看我上一篇关于Python操作Redis的client的介绍。

借助这个发送订阅平台,我重构了公司的摇号短信发送的平台。通过合作方提供格式化的摇号中签信息,一次抓取之后便可以实现对于中签者发布摇号成功消息,对于未中签者发布摇号失败通知。涉及公司隐私,这里并不展开讲摇号订阅发送的代码逻辑,只分享使用的Redis client。

同样的,写了一个C++的client,功能要更健全些。github上也有一些client,但我下载了stars数最多的两个项目,发现都不是很适用我的项目,一个client不支持接受一个map的消息,一条消息里只能包含一个key和一个value,不支持嵌套消息的读取,局限性很大,因为摇号的信息必须包含地域,中奖编码等,也不适用于我的工程项目。

这个client包括了:发布一个map的消息,创建消费组,使用消费组消费消息,ACK和删除一条消息,获取嵌套消息并存储到用户指定的map里等订阅发布需要实现的功能,对于一些需要尝试多次的操作,支持传入重试次数。

GitHub链接在这里

//put a meassage map into specific channel
int RedisTool::Publish(const std::string channel,std::map field_string_map)
{
	if (!pRedisContext) return -1;
	std::string field_string;
	std::string command = "XADD";
	std::string format = "*";

	int command_length = field_string_map.size();
	const char **commandList = new const char*[3+2*command_length];
	commandList[0] = command.c_str();
	commandList[1] = channel.c_str();
	commandList[2] = format.c_str();

	size_t *lengthList = new size_t[3+2*command_length];
	lengthList[0] = command.length();
	lengthList[1] = channel.length();
	lengthList[2] = format.length();

	int index = 3;
	for(std::map::const_iterator iter = field_string_map.begin(); iter != field_string_map.end(); ++iter)
	{
		commandList[index] = iter->first.c_str();
		lengthList[index] = iter->first.length();
		index++;
		commandList[index] = iter->second.c_str();
		lengthList[index] = iter->second.length();
		index++;
	}
	redisReply *pRedisReply = (redisReply*)redisCommandArgv(pRedisContext, index, (const char**)commandList, (const size_t*)lengthList); 
	fprintf(stderr, "RedisTool:: publish:%s: type:%d, str:%s\n", channel.c_str(), pRedisReply->type, pRedisReply->str);
    if (pRedisReply == NULL)
        return -1;
	if (pRedisReply->type == REDIS_REPLY_NIL)
    {
        freeReplyObject(pRedisReply);
        return -1;
    }
    else
    {
        freeReplyObject(pRedisReply);
        return 0;
    }	
}

//read channel messages
int RedisTool::XREAD(const std::string channel, int timeout)
{
	if (!pRedisContext) return -1;
	redisReply *pRedisReply = (redisReply*)redisCommand(pRedisContext, "XREAD BLOCK %d STREAMS %s $",timeout,channel.c_str()); 
    if (pRedisReply == NULL)
        return -1;
	if (pRedisReply->type != REDIS_REPLY_INTEGER)
    {
        freeReplyObject(pRedisReply);
        return -1;
    }
    else if (pRedisReply->integer > 0)
    {
        freeReplyObject(pRedisReply);
        return 1;
    }
    else
    {
        freeReplyObject(pRedisReply);
        return 0;
    }
}

//create a group able to consume messages of a channel
int RedisTool::XgroupCreate(std::string channel, std::string group)
{
	int ret = -1;
	if (!pRedisContext) return -1;
	redisReply *pRedisReply = (redisReply*)redisCommand(pRedisContext, "XGROUP CREATE %s %s 0",channel.c_str(),group.c_str()); 
    if (pRedisReply == NULL)
        return -1;
	else if (strcmp(pRedisReply->str, "OK") != 0)
	{
		fprintf(stderr, "RedisTool::Create Group %s of %s ERROR!\n",group.c_str(),channel.c_str());
		freeReplyObject(pRedisReply);
		return -1;
	}
	else
	{
		freeReplyObject(pRedisReply);
		ret = 1;
	}
    return ret;
}

//using consuming group read messages of a channel and put key&value into MsgMap
int RedisTool::XgroupRead(std::string channel, std::string group, std::string consumer, std::string &delivered_id, std::map &MsgMap, int number)
{
	int ret = -1;
	if (!pRedisContext)
		return -1;
	redisReply *pRedisReply = (redisReply*)redisCommand(pRedisContext, "XREADGROUP GROUP %s %s count %d STREAMS %s >",group.c_str(),consumer.c_str(),number,channel.c_str()); 
	fprintf(stderr, "RedisAdapter::Init XREADGROUP GROUP %s %s count %d STREAMS %s %s!\n",group.c_str(),consumer.c_str(),number,channel.c_str(), delivered_id.c_str());
    if (pRedisReply == NULL)
	{
		fprintf(stderr, "RedisTool::Init ERROR:%s!\n",pRedisContext->errstr);
		return -1;
	}
	if (pRedisReply->type == REDIS_REPLY_NIL)
	{
		fprintf(stderr, "RedisTool::Group %s get messages of %s null,keep waiting!\n",group.c_str(),channel.c_str());
		freeReplyObject(pRedisReply);
        return 0;
	}
	else if (pRedisReply->type != REDIS_REPLY_ARRAY)
    {
		fprintf(stderr, "RedisTool::Group %s get messages of %s ERROR,type:%d,reply :%s!\n",group.c_str(),channel.c_str(),pRedisReply->type,pRedisReply->str);
        freeReplyObject(pRedisReply);
        return -1;
    }
	else
	{
		redisReply* msg_struct_0 = pRedisReply->element[0]->element[1];
		fprintf(stderr, "RedisAdapter::Group %s get messages of %s,type:%d.\n",group.c_str(),channel.c_str(),pRedisReply->type);
		std::string deliverkey = "delivered_id";
		delivered_id = msg_struct_0->element[0]->element[0]->str;
		MsgMap[deliverkey] = delivered_id;
		redisReply* msg_struct_1 = msg_struct_0->element[0]->element[1];
		for (int i = 0; i < msg_struct_1->elements; i += 2)
        {
			std::string key = msg_struct_1->element[i]->str;
			std::string value =  msg_struct_1->element[i+1]->str;
			MsgMap[key] = value;
			fprintf(stderr, "RedisTool::Group %s get messages of %s,key:%s,value :%s!\n",group.c_str(),channel.c_str(),key.c_str(),value.c_str());
		}
		ret = 1;
	}
    freeReplyObject(pRedisReply);
    return ret;
}

int RedisTool::XACK(std::string channel, std::string group, std::string delivered_id)
{
	int ret = -1;
	if (!pRedisContext) return -1;
	redisReply *pRedisReply = (redisReply*)redisCommand(pRedisContext, "XACK %s %s %s",channel.c_str(),group.c_str(),delivered_id.c_str()); 
    if (pRedisReply == NULL)
        return -1;
	if (pRedisReply->type == REDIS_REPLY_INTEGER)
		ret = pRedisReply->integer;
	else if (pRedisReply->type == REDIS_REPLY_ERROR  && strcmp(pRedisReply->str, "ERR Unknown or disabled command 'XACK'") == 0)
		fprintf(stderr, "RedisTool::Group %s ack message %s of %s error, because this message has already been acked!\n",group.c_str(),delivered_id.c_str(),channel.c_str());
	else
		fprintf(stderr, "RedisTool::Group %s ack message %s of %s error, type : %d, str:%s!\n",group.c_str(),delivered_id.c_str(),channel.c_str(),pRedisReply->type,pRedisReply->str);
	freeReplyObject(pRedisReply);
    return ret;
}

int RedisTool::NextMsgId(const std::string channel, std::string start, std::string end, int count=1)
{
	int ret = -1;
	if (!pRedisContext) return -1;
	redisReply *pRedisReply = (redisReply*)redisCommand(pRedisContext, "XRANGE %s %s %s COUNT %d",channel.c_str(),start.c_str(),end.c_str(),count); 
    if (pRedisReply == NULL)
        return -1;
	if (pRedisReply->type == REDIS_REPLY_NIL)
	{
		ret = 0;
	}
	else if(pRedisReply->type == REDIS_REPLY_ARRAY)
	{
		ret = atoi(pRedisReply->element[0]->str);
	}
	freeReplyObject(pRedisReply);
    return ret;
}

//delete a msg by msgid
int RedisTool::XDEL(std::string channel, std::string delivered_id)
{
	int ret = -1;
	if (!pRedisContext) return -1;
	redisReply *pRedisReply = (redisReply*)redisCommand(pRedisContext, "XDEL %s %s",channel.c_str(),delivered_id.c_str()); 
    if (pRedisReply == NULL)
        return -1;
	else if (pRedisReply->type == REDIS_REPLY_INTEGER)
		ret = pRedisReply->integer;
	return ret;
}

//consume msg with retry
int RedisTool::ConsumeMsg(std::string channel, std::string group, std::string consumer, std::string &delivered_id, std::map &MsgMap, int number, int reconn)
{
	int ret = -1;
	int retry = 0;
	while (ret < 0) {
		if (!GetConnected())
		{
			Connect();
		}
		ret = XgroupRead(channel,group,consumer,delivered_id, MsgMap, number);
		if (ret < 0)
		{
			SetConnected(false);
			Close();
		}
		retry++;
		if (retry > reconn)
			break;
	}
	return ret;
}

int RedisTool::DeleteMsg(std::string channel, std::string delivered_id,int reconn)
{
	int ret = -1;
	int retry = 0;
	while (ret < 0) {
		if (!GetConnected())
		{
			Connect();
		}
		ret = XDEL(channel,delivered_id);
		if (ret < 0)
		{
			SetConnected(false);
			Close();
		}
		retry++;
		if (retry > reconn)
			break;
	}
	return ret;
}

int RedisTool::ACK(std::string channel, std::string group, std::string delivered_id,int reconn)
{
	int ret = -1;
	int retry = 0;
	while (ret < 0) {
		if (!GetConnected())
		{
			Connect();
		}
		ret = XACK(channel,group,delivered_id);
		if (ret < 0)
		{
			SetConnected(false);
			Close();
		}
		retry++;
		if (retry > reconn)
			break;
	}
	return ret;
}

 

你可能感兴趣的:(搭建一个简单的推送平台)