赞
踩
由于学习的需要, 需要学一下RPC和ZK怎么使用, 这里笔记一下石磊老师课程。
传输协议使用probuff ,它是一种二进制存储的协议, 因此存储小, 速度更快。 它是一个开源的跨语言的项目。我们可以使用一个probuf工具将我们定义的数据转换成对应语言的代码。 在项目中导入使用。 这里面生成的代码还是非常复杂的, 有精力还是需要学习一下。
如何使用probuf呢?
RPC中如何使用probuf?
秋招在急,简历中没有一个合适的项目供面试官去展开提问和自己去练习。 而且在学习c++的过程中最大的问题就是没有合适的项目去融汇贯通, 不知道做什么才好,像java可以做很多有意思的后台项目, 但是c++貌似如果只是懂语法去做这些后台项目很难, 不知道从什么点出发去做。我想通过这个项目去掌握C++如何开发后台项目。 通过这个项目明白后台的开发流程, 让自己的c++能够做到实际开发的水平, 敢写c++。
实现一个集群聊天服务器, 能够高并发的接收客服端的请求。 通过这个项目掌握
客户端:
用户过来进入登录或者注册页面。 如果选择登录的话, 我们发送一个json给服务器, 服务器返回该用户的信息, 例如好友列表, 群组, 离线消息。 登录成功之后, 我们将个人信息展示出来, 离线的群组消息和个人消息展示出来。 然后开启一个线程阻塞等待接受到的消息。之后主线程进入到聊天页表内。 另外一个主线程的业务是注册, 这个就不说了,很简单的一个。 我们继续说一下聊天业务中, 子线程阻塞去接受网卡的数据,这是因为别人发送消息自己要去接收,有消息就展示出来。
服务器 :
整体的框架如下, 多个服务器通过redis构建互通信息, 然后将多个服务器绑定到niginx上,与客户端交互。 服务器采用muduo库作为网络库底层, 以muduo库提供的回调作为业务层, 以自己封装的数据库类作为数据层的MVC结构进行开发。最终实现了一个高并发的集群服务器。
C++, linux , cmake , mysql , redis , nginx , muduo , gdb , git, json
CREATE TABLE IF NOT EXISTS user (id INT AUTO_INCREMENT PRIMARY KEY , name VARCHAR(50) NOT NULL UNIQUE , password VARCHAR(50) NOT NULL , state ENUM('online', 'offline') DEFAULT 'offline' )ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS friend (userid INT NOT NULL , friendid INT NOT NULL ,PRIMARY KEY (userid,friendid) )ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS allgroup (id INT AUTO_INCREMENT PRIMARY KEY , groupname VARCHAR(50) NOT NULL UNIQUE , groupdesc VARCHAR(200) DEFAULT '' )ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS groupuser (groupid INT NOT NULL , userid int NOT NULL , grouprole ENUM('creator', 'normal') DEFAULT 'normal' ,PRIMARY KEY (groupid,userid) )ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS offlinemessage (userid INT NOT NULL , message VARCHAR(500) NOT NULL )ENGINE=InnoDB DEFAULT CHARSET=utf8;
创建源代码目录
整个项目在src,include中分为后台和客户端。 各种目录的编译通过各级的cmakelist.txt去控制。
开发网络模块,搭建业务模块, 定义消息类型
完成服务器的搭建。 高并发高可用。
define 内容别的地方引用时候要在头文件里面define才能用到。
g++ muduo_server.cpp -o server -lmuduo_net -lmuduo_base -lpthread 这个连接库是有依赖关系的, 最基础的在最前面。
hpp代表head file combine with cpp file
sudo netstat -tanp 查看端口对应的进程。
回调之前一直不理解, c++回调基本全部用bind+ functional 去代替了, 因为普通函数回调限制较多不能携带使用类变量。 回调贯穿了整个程序oop的解耦操作中, 当一个函数什么时候发生和发生时候怎么做不再一起,就要事先设置回调。 供其他程序调用发生时候启动。(其实可以写到其他程序那部分, 但是不能解耦。)这个项目中我们将业务封装到了业务类中, 当网络这块回调被epoller-wait启动后根据数据的id拿到业务类的对应方法进行调用(通过bind+ fc+ map)。 业务再怎么改, 这边代码都不动了。 真正解耦。
头文件负责定义类还有添加头文件(定义时候需要用到的) , 注意只需要编译一次。 对应的.cpp 负责具体实现, 当我们将其编译成库的时候, 就只需要头文件了, 具体源码实现都在.so中, 不用库的话, 也会自动去找.cpp的实现。当然我们有时候不需要这个cpp文件,类直接在hpp里面定义并声明完成。
当形参变量和类成员变量的名字一样时候,一定要加this区分。
如何设计开源文件的目录, bin放可执行文件 , lib是生成的库文件 , include是头文件, src 是源码, build 项目编译时候产生的临时文件, test放的代码, cmakelist.txt 设计编译文件的规则, autobuild.sh 自动编译, readme.md 。
公网链接的时候, 服务器绑定自己内网网卡的地址, 客户端访问服务器所连接的公网地址, 请求过来之后自然会通过路由找到内网所在的网卡。建立链接。
当程序从看的角度找不到问题, 就只能调试了, gdb打断点break到出问题的之前点, 然后run, next,排除问题。 而且很多知名的开源代码会经常使用基类指针的运行时多态, 如果不走运行调试分析, 很难理清整体的框架。
rpc框架的实现逻辑:
服务方:
客户端:
客户端调用stub, stub里面传入一个指针, 这个指针在login方法中去调用callmethod函数, 这个callmethod函数至关重要, 主要实现了
组织要发送的 request_str 字符串
从zookeeper中拿到服务端的 ip 和 port,连接服务端
发送 request_str
接受服务端返回过来的 response 字符串并反序列化出结果
set 模型: 整个ByteTalk整体设计抽象为由多台服务器组合而成的一台超级性能的服务器,这些服务器形成一个小集合,部署一整套对外的服务。set模型弥补了单机能力的不足,对业务组合搭配成一个单元。本质上是对服务的一个高内聚的封装。
2. 分布式项目整体的框架如下。 首先的也是采用nginx去转发用户请求, 转发到多个超级集群中,每个超级集群是一整套业务。这个集群中有一个网关服务器转发到各个服务,实际上就是集群服务中的具体业务功能不在本地实现,使用rpc去调用这个逻辑。 因此我们现在来看这个框架就明显了。
ProxyService 作为整个服务单元的入口(底层就是一个网络类加一个service类。 网络类转发数据到单例的service类的handle函数中得到函数对象, 拿到这个函数对象后将数据转发过去),整个服务单元对外暴露的也是它的 Host 信息,他从文件配置中拿到ip后将本地服务配置成网关服务器, 也就是nginx的下一层。 在封装了muduo库的server类中,对于客户端的请求信息,它会首先通过单例业务类中函数对象map表找到函数对象, 然后调用。 也就是实现了判断这个信息是哪个业务(难免要把所有probuf全部引用过来),如果是服务器的业务,它就会去Zookeeper注册中心,找到提供这个服务的服务节点,并把这个请求序列后发送过去。 (需要注意这个其实可以类比之前我们的集群服务器,但是我们的函数和数据类型全部用probuf桩代替了, 之前的基于数据和函数调用的操作全部用这个stub了。 此外找到的用户id和socket链接仍然存在这里, 所有访问直接通过这个抽象服务器去链接到具体的服务器, 在线用户所在的服务器ip也是在这里注册的)
这个项目将之前的集群项目升级到分布式项目,第一个项目是nginx集群服务器项目。 首先我们请求过来之后, 我们会访问到nginx, nginx转发到我们的业务服务器上。 我们的业务服务器采用mvc架构, 通过muduo库作为网络层, 将触发事件绑定到类成员函数中。 services是v层, 通过传入定义的信号id在map中得到函数对象后调用。
在此基础上我对这个项目进行了升级, 采用微服务的形式去做了升级, 将各个业务抽离了,形成一个个集群服务。通过rpc来调来调去。 虚拟服务节点做了很多服务的绑定,虽然这个服务都是在做rpc的调用者。
客户端:
客户端调用stub, stub里面传入一个指针, 这个指针在login方法中去调用callmethod函数, 这个callmethod函数至关重要, 主要实现了
组织要发送的 request_str 字符串
从zookeeper中拿到服务端的 ip 和 port,连接服务端
发送 request_str
接受服务端返回过来的 response 字符串并反序列化出结果
之前的项目是采用本地的异步的单例日志, 这个日志是借鉴web服务器项目的, 首先创建一个线程,然后资源给shared_ptr , 这个指针循环从队列里面取string数据, 然后写入。 我们调用方只需要调用这个单例类, 将数据放到这个信息队列中(需要拿到锁,向buf中写入数据,把数据加入到队列中后才释放锁。但是这都只是在内存中,速度很快)。 这个日志类底层的另外一个线程会检测这个队列中的数据, 循环写入到硬盘。
但是升级到分布式的项目之后, 我们的日志采用了rpc交互, 但是交互的底层应该去模拟之前的日志模块去设计的,通过异步队列实现。
正反码
内存碎片
分为内部和外部, 内部是指没有充分利用,这没办法, 但是外部可以通过段页方式管理或者内存池。 每次配置一大块内存,并维护对应的16个空闲链表,大小从8字节到128字节。如果有小额内存被释放,则回收到空闲链表中。
(1)如果有相同大小的内存需求,则直接从空闲链表中查找对应大小的子链表。
(2)如果在自由链表中查找不到或者空间不够,则向内存池进行申请。
请你说说malloc内存管理原理(内存管理池)
brk、sbrk、mmap都属于系统调用,若每次申请内存,都调用这三个,那么每次都会产生系统调用,影响性能;其次,这样申请的内存容易产生碎片,因为堆是从低地址到高地址,如果高地址的内存没有被释放,低地址的内存就不能被回收。
所以malloc采用的是内存池的管理方式(ptmalloc), 避免内存碎片
拷贝构造函数的参数类型为什么必须是引用⭐⭐⭐⭐⭐
说说静态变量什么时候初始化?
说说初始化列表的使用场景⭐⭐⭐⭐⭐
请说说多重继承的二义性
vector如何正确删除重复元素⭐⭐⭐⭐⭐
unique_ptr指针实现原理⭐⭐⭐⭐⭐
说一说cast类型转换
lambda值捕获可以修改吗
函数对象这几个不熟悉
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。