免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
楼主: wlmqgzm
打印 上一主题 下一主题

[C++] ASIO,无锁,高并发,高可靠, 统一,网络架构,抗DOS,低端4核心服务器CPU 每秒87万QPS ECHO [复制链接]

论坛徽章:
9
程序设计版块每日发帖之星
日期:2015-10-18 06:20:00程序设计版块每日发帖之星
日期:2015-11-01 06:20:00程序设计版块每日发帖之星
日期:2015-11-02 06:20:00每日论坛发贴之星
日期:2015-11-02 06:20:00程序设计版块每日发帖之星
日期:2015-11-03 06:20:00程序设计版块每日发帖之星
日期:2015-11-04 06:20:00程序设计版块每日发帖之星
日期:2015-11-06 06:20:00数据库技术版块每周发帖之星
日期:2015-12-02 15:02:47数据库技术版块每日发帖之星
日期:2015-12-08 06:20:00
141 [报告]
发表于 2015-11-09 23:39 |只看该作者
本帖最后由 wlmqgzm 于 2015-11-10 20:43 编辑

谈一点核心关键技术的东西, 就是无锁的安全和高性能机制是如何实现的:

下面对socket中的各个变量进行了详细分析, 在代码中对socket的数据进行了集中管理, 并且没有使用锁,

线程使用策略:
有3类线程:管理线程, socket线程, 主线程.  
管理线程是单线程,
socket线程使用 多个io_service, 每个io_sevice单线程策略, 实现单线程处理.
主线程默认只有一个是单线程.

内存使用策略:
一方面避免False Sharing, 另外一方面就是"内存屏障"是否设置的问题.
其实, 我的理解, 基本上这里的每个变量都不需要设置"内存屏障", 因为这些变量都是原子变量, 都对更新速度不敏感, 过一会才更新, 对程序运行结果无影响.
另外一方面, 使用这些变量的函数中, 后面都有post/async_write/async_write/async_accept等内部有"内存屏障"的处理代码.
但是, 考虑到规范化也很重要, 在任何情况下, 以及有新增删改代码都不会出现问题, 为了可靠性, 最后还是在所有的使用这些变量的程序中都设置了"内存屏障", 降低了一点运行速度

下面是每个socket的主要数据:

//  这里使用alignas(64), 浪费了内存, 但是解决了false sharing,
class alignas(64) Struct_socket_data
{
  public:
    unsigned int  int_second_receive;              //  socket单线程内部变量, 对外每秒一次用post传递给管理线程, 最多每秒更新数万次
    unsigned int  int_socket_receive;                //  socket单线程内部变量, 不对外, 最多每秒更新数万次
    unsigned int  int_second_receive_byte;    //  socket单线程内部变量, 对外每秒一次用post传递给管理线程,最多每秒更新数万次
    unsigned int  int_socket_receive_byte;     //  socket单线程内部变量, 不对外, 最多每秒更新数万次
    unsigned int  int_second_send;                  //  socket单线程内部变量, 对外每秒一次用post传递给管理线程,最多每秒更新数万次
    unsigned int  int_socket_send;                    //  socket单线程内部变量, 不对外, 最多每秒更新数万次
    unsigned int  int_second_send_byte;        //  socket单线程内部变量, 对外每秒一次用post传递给管理线程,最多每秒更新数万次
    unsigned int  int_socket_send_byte;         //  socket单线程内部变量, 不对外 ,最多每秒更新数万次
    std::vector<char>  vt_read;      //  socket单线程内部变量, 不对外, 最多每秒更新数万次
    std::vector<char>  vt_left;        //  socket单线程内部变量, 不对外, 最多每秒更新数万次
    std::vector<char>  vt_write;     //  socket单线程内部变量, 不对外,最多每秒更新数万次
    volatile bool  bool_socket_debug;   //    socket单线程内部变量,  不对外,  外部用post设置此参数,  很少使用,
    volatile bool  bool_in_read_wait;    //  socket单线程更新, 管理线程只读,  共享变量,  最多每秒更新数万次,   整个连接过程中管理线程一般只读一次(管理线程先要满足超时等因素才读,此时此变量已经很久未更新了), 对更新速度不敏感,, 使用写内存屏障
    volatile bool  bool_in_write_wait;    //  socket单线程更新, 管理线程只读,  共享变量,  最多每秒更新数万次,   整个连接过程中管理线程一般只读一次(管理线程先要满足超时等因素才读,此时此变量已经很久未更新了), 对更新速度不敏感,, 使用写内存屏障

    alignas(64)
    volatile std::time_t  time_read_wait_begin;         //  socket单线程更新, 管理线程只读, 共享变量,  大约每秒更新一次,  每秒读一次 ,  对更新速度不敏感,, 使用写内存屏障
    volatile std::time_t  time_write_wait_begin;         //  socket单线程更新, 管理线程只读, 共享变量,  大约每秒更新一次,  每秒读一次 ,  对更新速度不敏感, 使用写内存屏障
    volatile unsigned long long   u_seconds_shutdown_receive_begin;   //  socket单线程更新, 管理线程只读, 共享变量,  整个连接过程中最多更新一次,  对更新速度不敏感 , 使用写内存屏障
    volatile unsigned long long  u_seconds_shutdown_send_begin;       //  socket单线程更新, 管理线程只读, 共享变量,  整个连接过程中最多更新一次,  对更新速度不敏感 , 使用写内存屏障
    volatile bool  bool_socket_shutdown_receive;    //   socket单线程更新, 管理线程只读, 共享变量,  整个连接过程中最多更新一次,  对更新速度不敏感 , 使用写内存屏障
    volatile bool  bool_socket_shutdown_send;        //   socket单线程更新, 管理线程只读, 共享变量,  整个连接过程中最多更新一次,  对更新速度不敏感 , 使用写内存屏障

    alignas(64)
    unsigned long long  u_seconds_accept;   //  一直保持初始化值,  管理线程初始化写,  使用写内存屏障
    boost::asio::ip::tcp::socket  socket1;    //  一直保持初始化值, 管理线程初始化写,  使用写内存屏障
    int   socket_handle_init;            //  一直保持初始化值, 管理线程初始化写,  使用写内存屏障
    unsigned int  int_client_ip;    //  一直保持初始化值, 管理线程初始化写,  使用写内存屏障
    unsigned short short_client_port;        //  一直保持初始化值, 管理线程初始化写,  使用写内存屏障
    unsigned short short_server_port;      //  一直保持初始化值, 管理线程初始化写,  使用写内存屏障
    unsigned short short_vector_ios_id;    //  一直保持初始化值, 管理线程初始化写,  使用写内存屏障
    bool  bool_is_server;                                     //  一直保持初始化值, 管理线程初始化写,  使用写内存屏障
    std::string   str_client_ip;            //  一直保持初始化值, 管理线程初始化写,  使用写内存屏障
    std::string   str_server_ip;           //  一直保持初始化值, 管理线程初始化写,  使用写内存屏障
    bool  bool_socket_read_wait_timeout;   //   管理线程内部变量, 不对外,  整个连接过程中最多更新一次,
    bool  bool_socket_write_wait_timeout;   //   管理线程内部变量, 不对外,  整个连接过程中最多更新一次,

  public:
    Struct_socket_data( std::shared_ptr<boost::asio::ip::tcp::socket>  sp_socket1_in );
};

论坛徽章:
44
15-16赛季CBA联赛之浙江
日期:2021-10-11 02:03:59程序设计版块每日发帖之星
日期:2016-07-02 06:20:0015-16赛季CBA联赛之新疆
日期:2016-04-25 10:55:452016科比退役纪念章
日期:2016-04-23 00:51:2315-16赛季CBA联赛之山东
日期:2016-04-17 12:00:2815-16赛季CBA联赛之福建
日期:2016-04-12 15:21:2915-16赛季CBA联赛之辽宁
日期:2016-03-24 21:38:2715-16赛季CBA联赛之福建
日期:2016-03-18 12:13:4015-16赛季CBA联赛之佛山
日期:2016-02-05 00:55:2015-16赛季CBA联赛之佛山
日期:2016-02-04 21:11:3615-16赛季CBA联赛之天津
日期:2016-11-02 00:33:1215-16赛季CBA联赛之浙江
日期:2017-01-13 01:31:49
142 [报告]
发表于 2015-11-10 00:28 |只看该作者
回复 139# wlmqgzm

在你的“无锁socket”中,我不光看到了shared_ptr,甚至还看到了vector/string……你真相信这些东西也是“无锁”的…………

论坛徽章:
9
程序设计版块每日发帖之星
日期:2015-10-18 06:20:00程序设计版块每日发帖之星
日期:2015-11-01 06:20:00程序设计版块每日发帖之星
日期:2015-11-02 06:20:00每日论坛发贴之星
日期:2015-11-02 06:20:00程序设计版块每日发帖之星
日期:2015-11-03 06:20:00程序设计版块每日发帖之星
日期:2015-11-04 06:20:00程序设计版块每日发帖之星
日期:2015-11-06 06:20:00数据库技术版块每周发帖之星
日期:2015-12-02 15:02:47数据库技术版块每日发帖之星
日期:2015-12-08 06:20:00
143 [报告]
发表于 2015-11-10 10:31 |只看该作者
本帖最后由 wlmqgzm 于 2015-11-10 12:23 编辑
windoze 发表于 2015-11-10 00:28
回复 139# wlmqgzm
在你的“无锁socket”中,我不光看到了shared_ptr,甚至还看到了vector/string……你 ...


vector/string 是无锁的, 这个应该是这样, 一般在多线程**享使用vector/string时, 代码要加锁, 单线程使用不加锁,  vector/string内部应该是没有锁的.
我这里使用vector/string都是单线程, 所以, 代码中无锁.

shared_ptr 这个就只是一条  std::shared_ptr<boost::asio::ip::tcp::socket>  sp_socket1, 这个选择也是比较犹豫的选择,
最早期的代码是使用原生方式(直接boost::asio::ip::tcp::socket), 在accept和socket块预分配内存一起申请,  后来为了提高抗DOS性能, 修改为使用shared_ptr, 减少DOS检查成功前的处理量
这个只能综合考虑, 如果确实要去掉这个shared_ptr, 也是可以的, 这个也是我曾经纠结的部分,   刚才改回了最早期代码的原生方式.

不愧是牛人啊, 一上来就找到我的纠结点.

shared_ptr这个始终都是要用的, 就是使用量的多少,  最终, 还是为了性能和其他的综合平衡.
在管理线程相关的代码中, 还使用了一些shared_ptr, 用来包装map list等容器, 这些也是现实的选择,
socket线程中, 使用shared_ptr在异步调用接口read_handler/ write_handler中传递数据, 这些都是现实的选择,

代码中所有的shared_ptr全部替换为使用裸指针, 然后全部内存统一在管理线程中申请和释放, 也测试过, 性能只有微弱的提高, 但是, 现代化的C++中, 总感觉不应该出现裸指针这样的东西, 可靠性生疑.
最终, 代码中, 没有采用裸指针, 使用了不少的shared_ptr, 也算无锁吧, 因为:

阅读了shared_ptr代码, linux下默认使用原子量+内存屏障, 没有使用锁(mutex)

while (!__atomic_compare_exchange_n(&_M_use_count, &__count, __count + 1,
                                          true, __ATOMIC_ACQ_REL,
                                          __ATOMIC_RELAXED));
-------------------------------------------------------------------------------
_M_release() // nothrow
      {
        // Be race-detector-friendly.  For more info see bits/c++config.
        _GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_M_use_count);
        if (__gnu_cxx::__exchange_and_add_dispatch(&_M_use_count, -1) == 1)
          {
            _GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_use_count);
            _M_dispose();
            // There must be a memory barrier between dispose() and destroy()
            // to ensure that the effects of dispose() are observed in the
            // thread that runs destroy().          
            if (_Mutex_base<_Lp>::_S_need_barriers)
              {
                _GLIBCXX_READ_MEM_BARRIER;
                _GLIBCXX_WRITE_MEM_BARRIER;
              }

            // Be race-detector-friendly.  For more info see bits/c++config.
            _GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_M_weak_count);
            if (__gnu_cxx::__exchange_and_add_dispatch(&_M_weak_count,
                                                       -1) == 1)
              {
                _GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_weak_count);
                _M_destroy();
              }
          }
      }

论坛徽章:
15
射手座
日期:2014-11-29 19:22:4915-16赛季CBA联赛之青岛
日期:2017-11-17 13:20:09黑曼巴
日期:2017-07-13 19:13:4715-16赛季CBA联赛之四川
日期:2017-02-07 21:08:572015年亚冠纪念徽章
日期:2015-11-06 12:31:58每日论坛发贴之星
日期:2015-08-04 06:20:00程序设计版块每日发帖之星
日期:2015-08-04 06:20:00程序设计版块每日发帖之星
日期:2015-07-12 22:20:002015亚冠之浦和红钻
日期:2015-07-08 10:10:132015亚冠之大阪钢巴
日期:2015-06-29 11:21:122015亚冠之广州恒大
日期:2015-05-22 21:55:412015年亚洲杯之伊朗
日期:2015-04-10 16:28:25
144 [报告]
发表于 2015-11-10 12:53 |只看该作者
wlmqgzm 发表于 2015-11-10 10:31
vector/string 是无锁的, 这个应该是这样, 一般在多线程**享使用vector/string时, 代码要加锁, 单线程使 ...

请问,服务器完全空闲时的CPU消耗是多少呢?

论坛徽章:
44
15-16赛季CBA联赛之浙江
日期:2021-10-11 02:03:59程序设计版块每日发帖之星
日期:2016-07-02 06:20:0015-16赛季CBA联赛之新疆
日期:2016-04-25 10:55:452016科比退役纪念章
日期:2016-04-23 00:51:2315-16赛季CBA联赛之山东
日期:2016-04-17 12:00:2815-16赛季CBA联赛之福建
日期:2016-04-12 15:21:2915-16赛季CBA联赛之辽宁
日期:2016-03-24 21:38:2715-16赛季CBA联赛之福建
日期:2016-03-18 12:13:4015-16赛季CBA联赛之佛山
日期:2016-02-05 00:55:2015-16赛季CBA联赛之佛山
日期:2016-02-04 21:11:3615-16赛季CBA联赛之天津
日期:2016-11-02 00:33:1215-16赛季CBA联赛之浙江
日期:2017-01-13 01:31:49
145 [报告]
发表于 2015-11-10 13:12 |只看该作者
回复 141# wlmqgzm

vector/string自己当然是没有锁的,但是它们都用到了heap,heap allocation可都是有锁的,除非你确保每个vector和string一定满足SBO/SSO的约束。

论坛徽章:
9
程序设计版块每日发帖之星
日期:2015-10-18 06:20:00程序设计版块每日发帖之星
日期:2015-11-01 06:20:00程序设计版块每日发帖之星
日期:2015-11-02 06:20:00每日论坛发贴之星
日期:2015-11-02 06:20:00程序设计版块每日发帖之星
日期:2015-11-03 06:20:00程序设计版块每日发帖之星
日期:2015-11-04 06:20:00程序设计版块每日发帖之星
日期:2015-11-06 06:20:00数据库技术版块每周发帖之星
日期:2015-12-02 15:02:47数据库技术版块每日发帖之星
日期:2015-12-08 06:20:00
146 [报告]
发表于 2015-11-10 13:48 |只看该作者
回复 142# yulihua49

%Cpu(s):  0.3 us,  0.3 sy,  0.0 ni, 99.4 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st

99.4%-99.8% idle


   

论坛徽章:
9
程序设计版块每日发帖之星
日期:2015-10-18 06:20:00程序设计版块每日发帖之星
日期:2015-11-01 06:20:00程序设计版块每日发帖之星
日期:2015-11-02 06:20:00每日论坛发贴之星
日期:2015-11-02 06:20:00程序设计版块每日发帖之星
日期:2015-11-03 06:20:00程序设计版块每日发帖之星
日期:2015-11-04 06:20:00程序设计版块每日发帖之星
日期:2015-11-06 06:20:00数据库技术版块每周发帖之星
日期:2015-12-02 15:02:47数据库技术版块每日发帖之星
日期:2015-12-08 06:20:00
147 [报告]
发表于 2015-11-10 13:54 |只看该作者
本帖最后由 wlmqgzm 于 2015-11-10 14:53 编辑
windoze 发表于 2015-11-10 13:12
回复 141# wlmqgzm

vector/string自己当然是没有锁的,但是它们都用到了heap,heap allocation可都是有 ...


这个采用了内存预分配内存的策略, 连接后管理线程给它们分配了一些内存,  收发包一般都不需要再分配内存, 尤其是ECHO SERVER, 应该够用.   
特殊应用可能会在收发包的fuction调用过程, 形成较大的write data, 这就需要内存的再分配和回收, 这个没有什么好办法, 只能听天由命了, 这个是系统的锁, 没有办法去掉. 只能减少锁出现的概率.

其实, 无锁的最终目标是高性能, 多数情况下, 多线程编程中减少锁的调用的各类技术方案, 都可以提高性能, 这个就是无锁的意义.
我其实也没有那么追求彻底的无锁, 核心的思想, 还是为了提高性能

论坛徽章:
44
15-16赛季CBA联赛之浙江
日期:2021-10-11 02:03:59程序设计版块每日发帖之星
日期:2016-07-02 06:20:0015-16赛季CBA联赛之新疆
日期:2016-04-25 10:55:452016科比退役纪念章
日期:2016-04-23 00:51:2315-16赛季CBA联赛之山东
日期:2016-04-17 12:00:2815-16赛季CBA联赛之福建
日期:2016-04-12 15:21:2915-16赛季CBA联赛之辽宁
日期:2016-03-24 21:38:2715-16赛季CBA联赛之福建
日期:2016-03-18 12:13:4015-16赛季CBA联赛之佛山
日期:2016-02-05 00:55:2015-16赛季CBA联赛之佛山
日期:2016-02-04 21:11:3615-16赛季CBA联赛之天津
日期:2016-11-02 00:33:1215-16赛季CBA联赛之浙江
日期:2017-01-13 01:31:49
148 [报告]
发表于 2015-11-10 16:58 |只看该作者
回复 145# wlmqgzm

你可以用per-session allocator来解决这个问题,就是给每个session创建一个slab allocator,之前和你提过的。

论坛徽章:
36
子鼠
日期:2013-08-28 22:23:29黄金圣斗士
日期:2015-12-01 11:37:51程序设计版块每日发帖之星
日期:2015-12-14 06:20:00CU十四周年纪念徽章
日期:2015-12-22 16:50:40IT运维版块每日发帖之星
日期:2016-01-25 06:20:0015-16赛季CBA联赛之深圳
日期:2016-01-27 10:31:172016猴年福章徽章
日期:2016-02-18 15:30:3415-16赛季CBA联赛之福建
日期:2016-04-07 11:25:2215-16赛季CBA联赛之青岛
日期:2016-04-29 18:02:5915-16赛季CBA联赛之北控
日期:2016-06-20 17:38:50技术图书徽章
日期:2016-07-19 13:54:03程序设计版块每日发帖之星
日期:2016-08-21 06:20:00
149 [报告]
发表于 2015-11-10 18:09 |只看该作者
我之前写,刚开始为了省事用stl ,后面全干掉用纯c搞了

论坛徽章:
15
射手座
日期:2014-11-29 19:22:4915-16赛季CBA联赛之青岛
日期:2017-11-17 13:20:09黑曼巴
日期:2017-07-13 19:13:4715-16赛季CBA联赛之四川
日期:2017-02-07 21:08:572015年亚冠纪念徽章
日期:2015-11-06 12:31:58每日论坛发贴之星
日期:2015-08-04 06:20:00程序设计版块每日发帖之星
日期:2015-08-04 06:20:00程序设计版块每日发帖之星
日期:2015-07-12 22:20:002015亚冠之浦和红钻
日期:2015-07-08 10:10:132015亚冠之大阪钢巴
日期:2015-06-29 11:21:122015亚冠之广州恒大
日期:2015-05-22 21:55:412015年亚洲杯之伊朗
日期:2015-04-10 16:28:25
150 [报告]
发表于 2015-11-11 14:21 |只看该作者
wlmqgzm 发表于 2015-11-10 13:48
回复 142# yulihua49

%Cpu(s):  0.3 us,  0.3 sy,  0.0 ni, 99.4 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 ...

无锁是否是忙等?那么忙等的响应时间是多少呢?或者,你不用忙等,用什么技术等待资源呢?
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP