利用redis制作消息队列
redis在游戏服务器中的使用初探(一) 环境搭建redis在游戏服务器中的使用初探(二) 客户端开源库选择redis在游戏服务器中的使用初探(三) 信息存储redis在游戏服务器中的使用初探(四) redis应用
在学习分布式对象存储的期间,有这么一个需求
"多个接口服务(本文当作客户端Clinet)需要以固定间隔向所有的多个服务器发送心跳,保证服务器确认客户端状态。
服务器在接收到文件读取请求时候,会广播询问所有数据服务器(本文也当作服务器)存储的数据情况"
以上一对多 的询问,是需要消息队列来进行通讯的
但是其实 redis也可以作为轻量级的消息队列来完成这个需求。
结构图
服务器开启一个线程进行redis订阅模式,当有人在指定频道发布消息时,所有订阅该频道的节点都可以接收到消息。
但是订阅操作如果我们不想采取固定时间间隔去获取频道是否有消息这么LOW的方案,其实是需要做成异步模式的。
而windows下 hredis异步模式是需要libevent支持的。 两者都是linux下运行良好的开源库,在windows下却是问题多多。
经过多次尝试,我决定放弃使用这两个开源库而选择cpp-redis。(linux下使用hredis和libevent ,有时间会试试)
流程如下:
一个服务节点 需要开启一个线程 进行客户端消息队列的订阅,每当收到消息就会调用收到消息的回调函数
而最初开启的服务节点的运行线程会定时的在服务器消息队列发布询问数据存储的信息。
客户端节点则相反 开启一个线程 定时向客户端消息队列发布心跳信息。
最初开启的客户端节点进行服务器消息队列的订阅,若收到服务器的数据存储询问,则进行本身是否存储该数据的判断
由于资源有限,最开始我们开启了5个线程 来模拟 2个服务器和 3个客户端
代码如下
#include <iostream>
#include <Winsock2.h>
#include <thread>
#include <mutex> #include "cpp_redis/cpp_redis"
#include "tacopie/tacopie" using namespace std; const int serverThreadNum = ;
const int clientThreadNum = ;
const int heartBeatTime = ;
const int ServerQueryTime = ;
const std::string clientChanName = "ClientChan";
const std::string serverChanName = "ServerChan";
std::mutex g_mutex; class WinsockGuard {
public:
WinsockGuard() {
WORD version = MAKEWORD(, );
if (WSAStartup(version, &data) != ) {
std::cerr << "WSAStartup() failure!" << std::endl;
return;
}
} ~WinsockGuard() {
WSACleanup();
}
private:
WSADATA data;
}; bool SubcribCommFunc(int threadNum,bool isServer) {
cpp_redis::subscriber sub; try {
sub.connect("127.0.0.1", , [](const std::string& host, std::size_t port, cpp_redis::subscriber::connect_state status) {
if (status == cpp_redis::subscriber::connect_state::dropped) {
{std::lock_guard<std::mutex> l(g_mutex); std::cout << "client disconnected from " << host << ":" << port << std::endl; }
//should_exit.notify_all();
}
}); }
catch (std::exception& e) {
{std::lock_guard<std::mutex> l(g_mutex); std::cerr << "in " << __FUNCTION__ << ".err = " << e.what() << std::endl; }
return false;
}
std::string chanName;
if (isServer) {chanName = clientChanName;}
else {chanName = serverChanName;} sub.subscribe(chanName.c_str(), [threadNum, isServer](const std::string& chan, const std::string& msg) {
string s;
if (isServer)s = "server ";
else s = "client ";
s += to_string(threadNum);s += " recv ";
{std::lock_guard<std::mutex> l(g_mutex); std::cout << s.c_str() << chan << ": " << msg << std::endl; }
//todo Check heatbeat or response
});
sub.commit(); while () {
std::this_thread::sleep_for(std::chrono::seconds());
} return true;
} bool RecvClientInfo(int i) {
return SubcribCommFunc(i,true);
} bool PublishCommFunc(int threadNum, bool isServer, string publishStr) {
cpp_redis::client client;
try {
client.connect("127.0.0.1", , [threadNum, isServer,&publishStr](const std::string& host, std::size_t port, cpp_redis::client::connect_state status) {
if (status == cpp_redis::client::connect_state::dropped) {
{std::lock_guard<std::mutex> l(g_mutex); std::cout << "disconnected from " << host << ":" << port << std::endl; }
}
});
while () {
std::string chanName;
if (isServer) {chanName = serverChanName;}
else { chanName = clientChanName;} client.publish(chanName.c_str(), publishStr.c_str());
client.commit(); int PubliLoopTime = ;
if (isServer) {PubliLoopTime = ServerQueryTime;}
else {PubliLoopTime = heartBeatTime;} std::this_thread::sleep_for(std::chrono::seconds(PubliLoopTime));
}
}
catch (std::exception& e) {
{std::lock_guard<std::mutex> l(g_mutex); std::cerr << "in " << __FUNCTION__ << ".err = " << e.what() << std::endl; }
return false;
} return true;
} void QueryWhoSaveDataLoop(int i) {
string s = "Server thread ";s += to_string(i);s += " query Who save data? ";
PublishCommFunc(i, true, s);
return;
} void ServerFunc(int i) {
{std::lock_guard<std::mutex> l(g_mutex);std::cout << "Enter ServerFunc threadNo = " << i << std::endl;}
//开启一个订阅客户端消息队列的线程 接受客户端的心跳包
thread t = thread(RecvClientInfo, i);
t.detach(); //开启一个定时检测心跳超时的客户端 todo //本线程不定时随机 发送一个询问各个客户端是否保存有数据
QueryWhoSaveDataLoop(i); std::this_thread::sleep_for(std::chrono::seconds());
} void SendHeatBeatOnTime(int threadNum, int sendTime) {
string s = "client thread ";s += to_string(threadNum);s += " send heartbeat";
PublishCommFunc(threadNum, false, s);
} void ClientFunc(int i) {
{std::lock_guard<std::mutex> l(g_mutex);std::cout << "Enter ClientFunc threadNo = " << i << std::endl;} //开启一个线程 定时发送心跳包
int s = heartBeatTime;
std::thread t = thread(SendHeatBeatOnTime, i, s);
t.detach(); SubcribCommFunc(i, false);
} void Start() {
thread serverThread[serverThreadNum];
thread clientThread[clientThreadNum]; for (int i = ; i < serverThreadNum; i++) {
serverThread[i] = thread(ServerFunc, i);
}
for (int i = ; i < clientThreadNum; i++) {
clientThread[i] = thread(ClientFunc, i);
}
//==================================================
for (int i = ; i < serverThreadNum; i++) {
serverThread[i].join();
}
for (int i = ; i < clientThreadNum; i++) {
clientThread[i].join();
}
} int main()
{
WinsockGuard g;
Start();
std::cout << "Finish!\n";
}
开启redis 运行代码如图
番外: 补上我在ubuntu下进行的libevent + hiredis的异步测试
首先是安装源头更新 更新 gcc g++ make 等工具
sudo apt-get update
sudo apt-get install g++ gcc
安装 redis server
sudo apt-get install redis-server
现在可以通过下面的命令查看到该进程:
ps -ef|grep redis
然后安装 hiredis 和 libevent
sudo apt-get install libhiredis-dev
sudo apt-get install libevent-dev
安装完成验证下是否正确安装
编写libevent 示例代码
#include <event.h>
#include <stdio.h> struct event ev;
struct timeval tv; void time_cb(int fd, short event, void *argc)
{
printf( "timer wakeup\n");
event_add(&ev, &tv);
} int main()
{
struct event_base *base = event_init(); tv.tv_sec = ;
tv.tv_usec = ;
evtimer_set(&ev, time_cb, NULL);
event_add(&ev, &tv);
event_base_dispatch(base); return ;
}
libeventTest.c
执行编译命令并运行 gcc -o eventexe libeventTest.c -levent
./eventexe 执行无错误则验证通过
编写hiredis示例代码
#include <stdio.h>
#include <hiredis/hiredis.h>
int main()
{
redisContext *conn = redisConnect("127.0.0.1",);
if(conn != NULL && conn->err)
{
printf("connection error: %s\n",conn->errstr);
return ;
}
redisReply *reply = (redisReply*)redisCommand(conn,"set foo 1234");
freeReplyObject(reply); reply = redisCommand(conn,"get foo");
printf("%s\n",reply->str);
freeReplyObject(reply); redisFree(conn);
return ;
}
执行编译命令并运行 gcc -o hiredisCli hiredisTest.c -lhiredis
./hiredisCli 执行无错误则验证通过
libevent和hiredis都确认无误后 开始测试异步代码
编写异步示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h> #include <hiredis/hiredis.h>
#include <hiredis/async.h>
#include <hiredis/adapters/libevent.h> #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h> void getCallback(redisAsyncContext *c, void *r, void *privdata) {
redisReply *reply = r;
if (reply == NULL) return;
printf("argv[%s]: %s\n", (char*)privdata, reply->str); /* Disconnect after receiving the reply to GET */
redisAsyncDisconnect(c);
} void connectCallback(const redisAsyncContext *c, int status) {
if (status != REDIS_OK) {
printf("Error: %s\n", c->errstr);
return;
}
printf("Connected...\n");
} void disconnectCallback(const redisAsyncContext *c, int status) {
if (status != REDIS_OK) {
printf("Error: %s\n", c->errstr);
return;
}
printf("Disconnected...\n");
} int main (int argc, char **argv) {
signal(SIGPIPE, SIG_IGN);
struct event_base *base = event_base_new(); redisAsyncContext *c = redisAsyncConnect("127.0.0.1", );
if (c->err) {
/* Let *c leak for now... */
printf("Error: %s\n", c->errstr);
return ;
} redisLibeventAttach(c,base);
redisAsyncSetConnectCallback(c,connectCallback);
redisAsyncSetDisconnectCallback(c,disconnectCallback);
redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-], strlen(argv[argc-]));
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
event_base_dispatch(base);
return ;
}
执行编译命令并运行
gcc -o async async.c -lhiredis -levent
./async
测试成功
利用redis制作消息队列的更多相关文章
- Java利用Redis实现消息队列
应用场景 为什么要用redis?二进制存储.java序列化传输.IO连接数高.连接频繁 一.序列化 这里编写了一个java序列化的工具,主要是将对象转化为byte数组,和根据byte数组反序列化成ja ...
- Springboot21 整合redis、利用redis实现消息队列
1 前提准备 1.1 创建一个springboot项目 技巧01:本博文基于springboot2.0创建 1.2 安装redis 1.2.1 linux版本 参考博文 1.2.2 windows版本 ...
- PHP中利用redis实现消息队列处理高并发请求
将请求存入redis 为了模拟多个用户的请求,使用一个for循环替代 //redis数据入队操作 $redis = new Redis(); $redis->connect('127.0.0.1 ...
- Redis 做消息队列
一般来说,消息队列有两种场景,一种是发布者订阅者模式,一种是生产者消费者模式.利用redis这两种场景的消息队列都能够实现.定义: 生产者消费者模式:生产者生产消息放到队列里,多个消费者同时监听队列, ...
- Redis除了做缓存--Redis做消息队列/Redis做分布式锁/Redis做接口限流
1.用Redis实现消息队列 用命令lpush入队,rpop出队 Long size = jedis.lpush("QueueName", message);//返回存放的数据条数 ...
- 程序员过关斩将--redis做消息队列,香吗?
Redis消息队列 在程序员这个圈子打拼了太多年,见过太多的程序员使用redis,其中一部分喜欢把redis做缓存(cache)使用,其中最典型的当属存储用户session,除此之外,把redis作为 ...
- Redis作为消息队列服务场景应用案例
NoSQL初探之人人都爱Redis:(3)使用Redis作为消息队列服务场景应用案例 一.消息队列场景简介 “消息”是在两台计算机间传送的数据单位.消息可以非常简单,例如只包含文本字符串:也可以更 ...
- redis resque消息队列
Resque 目前正在学习使用resque .resque-scheduler来发布异步任务和定时任务,为了方便以后查阅,所以记录一下. resque和resque-scheduler其优点在于功能比 ...
- 利用System V消息队列实现回射客户/服务器
一.介绍 在学习UNIX网络编程 卷1时,我们当时可以利用Socket套接字来实现回射客户/服务器程序,但是Socket编程是存在一些不足的,例如: 1. 服务器必须启动之时,客户端才能连上服务端,并 ...
随机推荐
- maven打包时报错:-source 1.5 中不支持 diamond 运算符
报错现象: 解决方法: 在pom文件中加入下面依赖 <build> <plugins> <plugin> <groupId>org.apache.mav ...
- JVM优化系列之一(-Xss调整Stack Space的大小)
Java程序中,每个线程都有自己的Stack Space(堆栈).这个Stack Space不是来自Heap的分配.所以Stack Space的大小不会受到-Xmx和-Xms的影响,这2个JVM参数仅 ...
- 20175311 2018-2019-2 《Java程序设计》第1周学习总结
教材学习内容总结 第一周我们主要尝试了怎么安装各种以后可能需要用到的软件,根据老师提供的博客教程进行自主学习安装软件,然后编写一些简单的语言程序. 教材学习中的问题和解决过程 问题1:在学习过程中主要 ...
- JDBC 中preparedStatement和Statement区别
一.概念 PreparedStatement是用来执行SQL查询语句的API之一,Java提供了 Statement.PreparedStatement 和 CallableStatement三种方式 ...
- Sqlite之事务
12.Sqlite事务介绍: 11.android SQLite 批量插入数据慢的解决方案 (针对于不同的android api 版本) ========== 12.Sqlite事务介绍: 应用程序初 ...
- LeetCode【88. 合并两个有序数组】
首先想到的方法就是,假设一个nums3数组,然后,比较nums1与nums2的数值大小,然后,放在nums3中,再将nums3转移到nums1中. 实现起来很麻烦,1.没有考虑到下标问题,结果就Arr ...
- Pycharm 设置上下左右快捷键
Pycharm的版本 Note:英文版的Pycharm,使用中文版的对照即可. 1. 打开Pycharm软件→File→Settings 2.Keymap→Editor Actions→搜索(up)→ ...
- rest api方式实现对文档库的管理
写在前面 刚入职一家新公司,在对接app的时候需要获取到某公司的sharepoint上面的文档库,获取文档库列表,团队文档库中的文件和文件夹列表,个人文档库中的文件文件夹列表,及在app端进入文件夹的 ...
- @method_decorator() 源码解析
# coding:utf-8 """ 作用: 原理:闭包 >> 1. 函数内部定义函数 2.内部函数使用外部函数的变量 3.外部函数返回内部函数的引用 带参数 ...
- uva-10382-贪心
题意:对于长为L,宽为W的矩形草地,需要对它进行浇水,总共有n个水龙头,给每个水龙头的浇水半径,和位置.求覆盖整个草地需要的最小水龙头数量. 如图,把浇水的面积转换成矩形,然后就和区间覆盖一样了,直接 ...