引言
並發是什么?企業在進行產品開發過程中為什么需要考慮這個問題?想象一下天貓的雙11和京東的618活動,一秒的點擊量就有幾十萬甚至上百萬,這么多請求一下子涌入到服務器,服務器需要對這么多的請求逐個進行消化掉,假如服務器一秒的處理能力就幾萬,那么剩下的不能及時得到處理的這些請求作何處理?總不能讓用戶界面一直等着,因此消息隊列應運而生,所有的請求都統一放入消息隊列,工作線程從消息隊列不斷的消費,消息隊列相當於一個緩沖區,可達到解藕、異步和削峰的目的。
Kafka、ActiveMQ、RabbitMQ和RockerMQ都是消息隊列的典型,每一種都有其自身的優勢和劣勢。本文我用自己編寫的Buffer類模擬消息隊列,如果是企業級需要上線的應用,一般都是基於業界已有的MQ框架上開發。
需求原型
- N個Client從標准輸入接收數據,然后連續不斷的發送到Server端;
- Server端接收來自每個Client的數據,將數據中的小寫字母全部轉換成大寫字母,其他字符保持不變,最后把轉換結果發送給對應的Client。
需求分解
- 拿到需求,第一步要做的就是分析需求並選擇合適的設計架構,考慮到Server需要和Client進行通信,Client來自四面八方,端對端通信自然選擇TCP,因此Server端需要能夠監聽新的連接請求和已有連接的業務請求;
- 又由於Server需要響應多個Client的業務請求,我們希望把業務處理交給Server端的工作線程(消費者)來做;
- 同時還需要一個IO線程負責監聽Socket描述符,當IO線程監聽到已有連接的業務請求時,立即把請求內容封裝成一個任務推入消息隊列尾;
- IO線程與工作線程互斥訪問消息隊列,當然工作線程消費一個任務或者IO線程添加一個任務都需要通知對方,也就是同步;
- 工作線程處理完畢后,把處理結果交給IO線程,由IO線程負責把結果發送給對應的Client,也就是IO線程與工作線程的分離,這里工作線程通知IO線程的方式我用eventfd來實現;
- 我們希望引入Log4cpp記錄服務端的日志,並能夠保存到文件中;
- 分析完這些,一個整體架構和大體的樣子在腦海中就已經形成了,接着就需要編寫設計文檔和畫流程圖、類圖和時序圖了。
詳細設計文檔
1.UML靜態類圖:
2.UML動態時序圖:
效果
1.如圖,開了三個Client,運行結果正確:
2.Server端通過Log4cpp把日志寫到文件中:
源碼獲取
https://github.com/icoty/cs_threadpool_epoll_mq
目錄結構
.
├── client // 客戶端Demo
│ ├── Client.cc
│ ├── Client.exe
│ ├── client.sh // 進入該目錄下啟動Client Demo: sh client.sh
│ ├── Log4func.cc // 引入日志模塊重新瘋轉
│ ├── Log4func.h
│ └── Makefile // 編譯方式:make
├── conf
│ └── my.conf // IP,Port配置文件, 從這里進行修改
├── include // 頭文件
│ ├── Configuration.hpp // 配置文件,單例類,my.conf的內存化
│ ├── FileName.hpp // 全局定義,Configuration會用到
│ ├── log // 日志模塊頭文件
│ │ └── Log4func.hpp
│ ├── net // 網絡框架模塊頭文件
│ │ ├── EpollPoller.hpp
│ │ ├── InetAddress.hpp
│ │ ├── Socket.hpp
│ │ ├── SockIO.hpp
│ │ ├── TcpConnection.hpp
│ │ └── TcpServer.hpp
│ ├── String2Upper.hpp // 工作線程轉換成大寫實際走的這里面的接口
│ ├── String2UpperServer.hpp // Server端的整個工廠
│ └── threadpool // 線程池、鎖、條件變量和消息隊列的封裝
│ ├── Buffer.hpp
│ ├── Condition.hpp
│ ├── MutexLock.hpp
│ ├── Noncopyable.hpp
│ ├── Pthread.hpp
│ ├── Task.hpp
│ └── Threadpool.hpp
├── log // Server端的日志通過Log4cpp記錄到這個文件中
│ └── log4test.log
├── Makefile // 編譯方式:make
├── README.md
├── server // server端Demo
│ ├── server.exe
│ └── server.sh // 進入該目錄下啟動Server Demo:sh server.sh
└── src // 源文件
├── Configuration.cpp
├── log
│ └── Log4func.cpp
├── main.cpp
├── net
│ ├── EpollPoller.cpp
│ ├── InetAddress.cpp
│ ├── Socket.cpp
│ ├── SockIO.cpp
│ ├── TcpConnection.cpp
│ └── TcpServer.cpp
├── String2Upper.cpp
├── String2UpperServer.cpp
└── threadpool
├── Buffer.cpp
├── Condition.cpp
├── MutexLock.cpp // MutexLockGuard封裝
├── Pthread.cpp
└── Threadpool.cpp
參考文獻
[1] UNIX環境高級編程第3版
[2] cpp reference
[3] UML時序圖
[4] Log4cpp官網下載
[5] Log4cpp安裝