├── README.md ├── async_http_post ├── async_http_post.pro ├── async_http_post.pro.user └── main.cpp └── build-async-http-post-Debug ├── Makefile └── default.conf /README.md: -------------------------------------------------------------------------------- 1 | # async-http-post 2 | 多线程异步http post数据发送程序,使用redis做队列,可接收php/java等其他语言的异步任务。 3 | 4 | ## 使用场景 5 | 6 | 例如:用户信息的修改后需将修改后的用户信息同步到N个不同机房的服务器 7 | 异步模式:用户修改信息接口将要发送的数据压入redis队列,async-http-post在后台监听redis队列,当有消息时,按队列顺序多线程发送数据至N个不同机房的服务器。 8 | 9 | ## 特点 10 | 11 | - 消息必达:当接收的服务器中有任何一个未返回http状态码200时,消息重新压入队列,等待下一次重试。 12 | - 消息不丢失:没一次待发送的数据都有缓存到本地,即便进程被kill,下次启动程序数据将从缓存重新压入队列。 13 | - 多线程:每一个url将启动一个单独的线程来处理,性能更高。 14 | - 多进程:支持多个进程同时运行,当数据量太大时,只需多运行几次,产生多个进程即可。 15 | - 自定义配置文件:每个进程可指定一个配置文件,可通过不同的配置文件监听不同的队列与发送至不同的服务器。 16 | 17 | ## 依赖库 (libraries) 18 | 使用本人开发的C++ 敏捷开发框架开发 [EasyCpp](https://github.com/onanying/easycpp) 19 | 20 | ## 安装 (install) 21 | 22 | ``` 23 | # make 24 | ``` 25 | 26 | ## 运行 (run) 27 | 28 | ``` 29 | # ./async_http_post default.conf 30 | ``` 31 | 32 | ## 配置 (config) 33 | 34 | > 每个配置文件会生成一个 .DATA 的数据文件夹,里面有log与cache两个文件夹 35 | 36 | redis_list_key: redis内list类型的key (“queue_user_token” 实际存储key为 “list:queue_user_token”) 37 | http_timeout: http请求的超时时间,单位:秒 38 | post_urls: 接收数据的url清单,POST请求 39 | 40 | ``` 41 | { 42 | 43 | "redis_host" : "127.0.0.1", 44 | 45 | "redis_port" : 6379, 46 | 47 | "redis_auth" : "123456", 48 | 49 | "redis_list_key" : "queue_user_token", 50 | 51 | "http_timeout" : 30, 52 | 53 | "post_urls" : [ 54 | {"url" : "http://114.119.4.115:8888/test.php"}, 55 | {"url" : "http://114.119.4.115:8888/test.php"} 56 | ] 57 | 58 | } 59 | ``` 60 | 61 | ## 日志 (log) 62 | 63 | 日志在log目录里,按日期每天生成一个日志文件,请定期手动清理日志。 64 | 每次http请求与响应占一行记录,error.log记录错误日志,info.log记录所有日志。 65 | 66 | ## 缓存 (cache) 67 | 68 | 缓存在cache目录里,程序会自动管理,请勿删除,否则会丢失数据。 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /async_http_post/async_http_post.pro: -------------------------------------------------------------------------------- 1 | #------------------------------------------------- 2 | # 3 | # Project created by QtCreator 2016-12-15T15:05:04 4 | # 5 | #------------------------------------------------- 6 | 7 | QT += core 8 | 9 | QT -= gui 10 | 11 | TARGET = async_http_post 12 | CONFIG += console 13 | CONFIG -= app_bundle 14 | 15 | TEMPLATE = app 16 | 17 | 18 | SOURCES += main.cpp 19 | 20 | INCLUDEPATH += /usr/local/include/ 21 | 22 | LIBS += -lboost_system -lboost_thread -lboost_filesystem -lhiredis -levent -leasycpp 23 | -------------------------------------------------------------------------------- /async_http_post/async_http_post.pro.user: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ProjectExplorer.Project.ActiveTarget 7 | 0 8 | 9 | 10 | ProjectExplorer.Project.EditorSettings 11 | 12 | true 13 | false 14 | true 15 | 16 | Cpp 17 | 18 | CppGlobal 19 | 20 | 21 | 22 | QmlJS 23 | 24 | QmlJSGlobal 25 | 26 | 27 | 2 28 | UTF-8 29 | false 30 | 4 31 | false 32 | true 33 | 1 34 | true 35 | 0 36 | true 37 | 0 38 | 8 39 | true 40 | 1 41 | true 42 | true 43 | true 44 | false 45 | 46 | 47 | 48 | ProjectExplorer.Project.PluginSettings 49 | 50 | 51 | 52 | ProjectExplorer.Project.Target.0 53 | 54 | 桌面 55 | 桌面 56 | {1e7fc4e4-8378-4d85-a7e3-17ef5899eac2} 57 | 0 58 | 0 59 | 0 60 | 61 | 62 | 63 | true 64 | qmake 65 | 66 | QtProjectManager.QMakeBuildStep 67 | false 68 | true 69 | 70 | false 71 | 72 | 73 | true 74 | Make 75 | 76 | Qt4ProjectManager.MakeStep 77 | 78 | -w 79 | -r 80 | 81 | false 82 | 83 | 84 | 85 | 2 86 | 构建 87 | 88 | ProjectExplorer.BuildSteps.Build 89 | 90 | 91 | 92 | true 93 | Make 94 | 95 | Qt4ProjectManager.MakeStep 96 | 97 | -w 98 | -r 99 | 100 | true 101 | clean 102 | 103 | 104 | 1 105 | 清理 106 | 107 | ProjectExplorer.BuildSteps.Clean 108 | 109 | 2 110 | false 111 | 112 | Debug 113 | 114 | Qt4ProjectManager.Qt4BuildConfiguration 115 | 2 116 | /home/github/async-http-post/build-async-http-post-Debug 117 | true 118 | 119 | 120 | 121 | 122 | true 123 | qmake 124 | 125 | QtProjectManager.QMakeBuildStep 126 | false 127 | true 128 | 129 | false 130 | 131 | 132 | true 133 | Make 134 | 135 | Qt4ProjectManager.MakeStep 136 | 137 | -w 138 | -r 139 | 140 | false 141 | 142 | 143 | 144 | 2 145 | 构建 146 | 147 | ProjectExplorer.BuildSteps.Build 148 | 149 | 150 | 151 | true 152 | Make 153 | 154 | Qt4ProjectManager.MakeStep 155 | 156 | -w 157 | -r 158 | 159 | true 160 | clean 161 | 162 | 163 | 1 164 | 清理 165 | 166 | ProjectExplorer.BuildSteps.Clean 167 | 168 | 2 169 | false 170 | 171 | Release 172 | 173 | Qt4ProjectManager.Qt4BuildConfiguration 174 | 0 175 | /home/github/async-http-post/build-Release 176 | true 177 | 178 | 2 179 | 180 | 181 | 0 182 | 部署 183 | 184 | ProjectExplorer.BuildSteps.Deploy 185 | 186 | 1 187 | 在本地部署 188 | 189 | ProjectExplorer.DefaultDeployConfiguration 190 | 191 | 1 192 | 193 | 194 | true 195 | 196 | false 197 | false 198 | false 199 | false 200 | true 201 | 0.01 202 | 10 203 | true 204 | 25 205 | 206 | true 207 | valgrind 208 | 209 | 0 210 | 1 211 | 2 212 | 3 213 | 4 214 | 5 215 | 6 216 | 7 217 | 8 218 | 9 219 | 10 220 | 11 221 | 12 222 | 13 223 | 14 224 | 225 | 2 226 | 227 | async_http_post 228 | 229 | Qt4ProjectManager.Qt4RunConfiguration:/home/github/async-http-post/async_http_post/async_http_post.pro 230 | 231 | async_http_post.pro 232 | false 233 | true 234 | 235 | 3768 236 | true 237 | false 238 | false 239 | false 240 | true 241 | 242 | 1 243 | 244 | 245 | 246 | ProjectExplorer.Project.TargetCount 247 | 1 248 | 249 | 250 | ProjectExplorer.Project.Updater.EnvironmentId 251 | {9f863264-3678-4123-b9ff-8977c67f50db} 252 | 253 | 254 | ProjectExplorer.Project.Updater.FileVersion 255 | 14 256 | 257 | 258 | -------------------------------------------------------------------------------- /async_http_post/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | using namespace std; 17 | using namespace easycpp; 18 | 19 | /// 变量定义 20 | string redisHost; 21 | int redisPort; 22 | string redisAuth; 23 | string redisListKey; 24 | vector postUrls; 25 | int httpTimeout; 26 | string nowPid; 27 | string cacheDirPath; 28 | string logDirPath; 29 | 30 | /// 获取配置文件 31 | void init_config(string file) 32 | { 33 | string confStr = helpers::file_get_contents(file); 34 | if(confStr == ""){ 35 | string msg = "配置文件 " + file + " 读取错误"; 36 | throw libraries::Exception(msg); 37 | } 38 | try{ 39 | libraries::JsonObject jsonObj = helpers::json_init(confStr); 40 | redisHost = helpers::json_get_string(jsonObj, "redis_host"); 41 | redisPort = helpers::json_get_int(jsonObj, "redis_port"); 42 | redisAuth = helpers::json_get_string(jsonObj, "redis_auth"); 43 | redisListKey = helpers::json_get_string(jsonObj, "redis_list_key"); 44 | postUrls = helpers::json_get_array(jsonObj, "post_urls"); 45 | httpTimeout = helpers::json_get_int(jsonObj, "http_timeout"); 46 | } catch (exception& /* ex */) { 47 | string msg = "配置文件 " + file + " 参数错误"; 48 | throw libraries::Exception(msg); 49 | } 50 | } 51 | 52 | /// post数据 53 | void post(string url, libraries::JsonObject params, string paramsStr, int * postErrCode) 54 | { 55 | string response; 56 | int errCode = helpers::http_post(url, params, response, httpTimeout); 57 | 58 | // 写入log文件 59 | string msg = url + " | " + paramsStr + " | " + response; 60 | helpers::log_info("http_post", msg, logDirPath); 61 | 62 | if (errCode == 200) { 63 | // 成功 64 | *postErrCode = 200; 65 | } else { 66 | // 失败 67 | // 写入log文件 68 | string msg = url + " | " + paramsStr + " | " + response; 69 | helpers::log_error("post", msg, logDirPath); 70 | } 71 | } 72 | 73 | /// 启动 74 | void start() 75 | { 76 | vector postThread; 77 | vector postErrCode; 78 | 79 | try { 80 | models::RedisModel rs(redisHost, redisPort, redisAuth); 81 | // 将当前进程的缓存重新压入队列 82 | string cacheStr = helpers::file_get_contents(cacheDirPath + "/" + nowPid); 83 | if(!cacheStr.empty()){ 84 | rs.pushList(redisListKey, cacheStr); // 数据重新压入队列 85 | } 86 | // 从redis取出数据 87 | while (true) { 88 | string paramsStr = rs.pullList(redisListKey, 3600); // 堵塞1小时 89 | if(!paramsStr.empty()){ 90 | // 存入缓存 91 | helpers::file_put_contents(cacheDirPath + "/" + nowPid, paramsStr, FILE_REPLACE); 92 | // 转换为json 93 | libraries::JsonObject params = helpers::json_init(paramsStr); 94 | // 循环发送给push_list里面的url 95 | BOOST_FOREACH (libraries::JsonObject &item, postUrls) { 96 | string url = helpers::json_get_string(item, "url"); 97 | // 启动多线程 98 | postErrCode.push_back(new int(-3)); 99 | postThread.push_back(new boost::thread(boost::bind(&post, url, params, paramsStr, postErrCode.back()))); 100 | } 101 | // 等待全部线程完成 102 | BOOST_FOREACH (boost::thread *item, postThread) { 103 | item->join(); 104 | } 105 | // 检查执行结果 106 | bool allPostState = true; 107 | BOOST_FOREACH (int *value, postErrCode) { 108 | if(*value != 200){ 109 | allPostState = false; 110 | } 111 | } 112 | // 释放内存 113 | BOOST_FOREACH (boost::thread *item, postThread) { 114 | delete item; 115 | } 116 | BOOST_FOREACH (int *value, postErrCode) { 117 | delete value; 118 | } 119 | // 清空容器 120 | postThread.clear(); 121 | postErrCode.clear(); 122 | // post失败 123 | if(!allPostState){ 124 | rs.pushList(redisListKey, paramsStr); // 数据重新压入队列 125 | } 126 | } 127 | } 128 | } catch (exception& ex) { 129 | // 将错误写入log文件 130 | helpers::log_error("start", ex.what(), logDirPath); 131 | // 出错后堵塞一段时间 132 | sleep(60); 133 | } 134 | } 135 | 136 | /// 读取所有缓存 137 | void read_all_cache() 138 | { 139 | // 将所有缓存重新压入队列 140 | models::RedisModel rs(redisHost, redisPort, redisAuth); 141 | vector filenames = helpers::readdir(cacheDirPath); 142 | BOOST_FOREACH (string item, filenames) { 143 | // 读取缓存 144 | string filename = string(item.c_str()); 145 | string cacheStr = helpers::file_get_contents(filename); 146 | if(!cacheStr.empty()){ 147 | rs.pushList(redisListKey, cacheStr); // 数据重新压入队列 148 | } 149 | // 删除缓存 150 | boost::filesystem::remove(filename); 151 | } 152 | } 153 | 154 | int main(int argc, char *argv[]) 155 | { 156 | // fork新进程, 使终端可关闭 157 | int pid = fork(); 158 | if(pid == -1){ 159 | // fork出错 160 | cout << "程序fork进程出错" << endl; 161 | return 1; 162 | } 163 | if(pid > 0){ 164 | // 结束父进程 165 | cout << "程序在进程 " << pid << " 启动成功" << endl; 166 | return 0; 167 | } 168 | 169 | // 初始化参数 170 | try { 171 | init_config(argc >= 2 ? argv[1] : "default.conf"); 172 | } catch (exception& ex) { 173 | cout << "INIT CONFIG ERROR: " << ex.what() << endl; 174 | return 1; 175 | } 176 | 177 | // 获取当前进程id 178 | nowPid = helpers::strval(getpid()); 179 | 180 | // 配置目录 181 | string configFileName = argc >= 2 ? argv[1] : "default.conf"; 182 | configFileName += ".DATA"; 183 | cacheDirPath = configFileName + "/cache"; 184 | logDirPath = configFileName + "/log"; 185 | 186 | // 读取所有缓存 187 | try { 188 | read_all_cache(); 189 | } catch (exception& ex) { 190 | cout << "READ ALL CACHE ERROR: " << ex.what() << endl; 191 | return 2; 192 | } 193 | 194 | // 循环启动 195 | while (true) { 196 | start(); 197 | } 198 | 199 | return 0; 200 | } 201 | -------------------------------------------------------------------------------- /build-async-http-post-Debug/Makefile: -------------------------------------------------------------------------------- 1 | ############################################################################# 2 | # Makefile for building: async_http_post 3 | # Generated by qmake (2.01a) (Qt 4.8.7) on: ?? 12? 21 17:20:25 2016 4 | # Project: ../async_http_post/async_http_post.pro 5 | # Template: app 6 | # Command: /usr/lib/x86_64-linux-gnu/qt4/bin/qmake -spec /usr/share/qt4/mkspecs/linux-g++-64 CONFIG+=debug CONFIG+=declarative_debug -o Makefile ../async_http_post/async_http_post.pro 7 | ############################################################################# 8 | 9 | ####### Compiler, tools and options 10 | 11 | CC = gcc 12 | CXX = g++ 13 | DEFINES = -DQT_CORE_LIB -DQT_SHARED 14 | CFLAGS = -m64 -pipe -g -Wall -W -D_REENTRANT $(DEFINES) 15 | CXXFLAGS = -m64 -pipe -g -Wall -W -D_REENTRANT $(DEFINES) 16 | INCPATH = -I/usr/share/qt4/mkspecs/linux-g++-64 -I../async_http_post -I/usr/include/qt4/QtCore -I/usr/include/qt4 -I/usr/local/include -I. -I../async_http_post -I. 17 | LINK = g++ 18 | LFLAGS = -m64 19 | LIBS = $(SUBLIBS) -L/usr/lib/x86_64-linux-gnu -lboost_system -lboost_thread -lboost_filesystem -lhiredis -levent -leasycpp -lQtCore -lpthread 20 | AR = ar cqs 21 | RANLIB = 22 | QMAKE = /usr/lib/x86_64-linux-gnu/qt4/bin/qmake 23 | TAR = tar -cf 24 | COMPRESS = gzip -9f 25 | COPY = cp -f 26 | SED = sed 27 | COPY_FILE = $(COPY) 28 | COPY_DIR = $(COPY) -r 29 | STRIP = strip 30 | INSTALL_FILE = install -m 644 -p 31 | INSTALL_DIR = $(COPY_DIR) 32 | INSTALL_PROGRAM = install -m 755 -p 33 | DEL_FILE = rm -f 34 | SYMLINK = ln -f -s 35 | DEL_DIR = rmdir 36 | MOVE = mv -f 37 | CHK_DIR_EXISTS= test -d 38 | MKDIR = mkdir -p 39 | 40 | ####### Output directory 41 | 42 | OBJECTS_DIR = ./ 43 | 44 | ####### Files 45 | 46 | SOURCES = ../async_http_post/main.cpp 47 | OBJECTS = main.o 48 | QMAKE_TARGET = async_http_post 49 | DESTDIR = 50 | TARGET = async_http_post 51 | 52 | first: all 53 | ####### Implicit rules 54 | 55 | .SUFFIXES: .o .c .cpp .cc .cxx .C 56 | 57 | .cpp.o: 58 | $(CXX) -c $(CXXFLAGS) $(INCPATH) -o "$@" "$<" 59 | 60 | .cc.o: 61 | $(CXX) -c $(CXXFLAGS) $(INCPATH) -o "$@" "$<" 62 | 63 | .cxx.o: 64 | $(CXX) -c $(CXXFLAGS) $(INCPATH) -o "$@" "$<" 65 | 66 | .C.o: 67 | $(CXX) -c $(CXXFLAGS) $(INCPATH) -o "$@" "$<" 68 | 69 | .c.o: 70 | $(CC) -c $(CFLAGS) $(INCPATH) -o "$@" "$<" 71 | 72 | ####### Build rules 73 | 74 | all: Makefile $(TARGET) 75 | 76 | $(TARGET): $(OBJECTS) 77 | $(LINK) $(LFLAGS) -o $(TARGET) $(OBJECTS) $(OBJCOMP) $(LIBS) 78 | 79 | qmake: FORCE 80 | @$(QMAKE) -spec /usr/share/qt4/mkspecs/linux-g++-64 CONFIG+=debug CONFIG+=declarative_debug -o Makefile ../async_http_post/async_http_post.pro 81 | 82 | dist: 83 | @$(CHK_DIR_EXISTS) .tmp/async_http_post1.0.0 || $(MKDIR) .tmp/async_http_post1.0.0 84 | $(COPY_FILE) --parents $(SOURCES) $(DIST) .tmp/async_http_post1.0.0/ && $(COPY_FILE) --parents ../async_http_post/main.cpp .tmp/async_http_post1.0.0/ && (cd `dirname .tmp/async_http_post1.0.0` && $(TAR) async_http_post1.0.0.tar async_http_post1.0.0 && $(COMPRESS) async_http_post1.0.0.tar) && $(MOVE) `dirname .tmp/async_http_post1.0.0`/async_http_post1.0.0.tar.gz . && $(DEL_FILE) -r .tmp/async_http_post1.0.0 85 | 86 | 87 | clean:compiler_clean 88 | -$(DEL_FILE) $(OBJECTS) 89 | -$(DEL_FILE) *~ core *.core 90 | 91 | 92 | ####### Sub-libraries 93 | 94 | distclean: clean 95 | -$(DEL_FILE) $(TARGET) 96 | -$(DEL_FILE) Makefile 97 | 98 | 99 | check: first 100 | 101 | ####### Compile 102 | 103 | main.o: ../async_http_post/main.cpp 104 | $(CXX) -c $(CXXFLAGS) $(INCPATH) -o main.o ../async_http_post/main.cpp 105 | 106 | ####### Install 107 | 108 | install: FORCE 109 | 110 | uninstall: FORCE 111 | 112 | FORCE: 113 | 114 | -------------------------------------------------------------------------------- /build-async-http-post-Debug/default.conf: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "redis_host" : "127.0.0.1", 4 | 5 | "redis_port" : 6379, 6 | 7 | "redis_auth" : "123456", 8 | 9 | "redis_list_key" : "queue_user_token", 10 | 11 | "http_timeout" : 30, 12 | 13 | "post_urls" : [ 14 | {"url" : "http://114.119.4.115:8888/test.php"}, 15 | {"url" : "http://114.119.4.115:8888/test.php"} 16 | ] 17 | 18 | } 19 | --------------------------------------------------------------------------------