一次RPC调用的流程:
客户端将调用的类名,方法名,参数名,参数值等信息,序列化成二进制流
客户端将二进制流,通过网络发送给服务端
服务端收到二进制流后,进行反序列化,得到要调用的类名,方法名,参数名和参数值,再通过动态代理的方式,调用对应的方法得到返回值
服务端将返回值序列化,再通过网络发送给客户端
客户端反序列化,得到调用结果。
网络通信框架、网络传输协议(序列化)、服务注册和服务发现
服务启动时,服务提供者向注册中心注册服务,暴露自己的地址和端口号。注册中心会更新服务列表。
消费者启动时向注册中心请求可用的服务地址,并在本地缓存一份。
为什么要有服务注册和服务发现:方便业务水平扩展,添加新的业务只需要向注册中心注册即可。
比如etcd就可以用于服务注册和服务发现。
RPC的性能取决与网络传输和反序列化
选择一个高性能的IO模型,及选择IO多路复用。
网络参数调优,比如开启tcp_nodelay,及关闭nagle算法。
序列化要考虑性能,时间和空间开销
再考虑跨平台,跨语言。
对性能要求不高,就json
有性能要求就protobuf
c++ 11中引入了原子指令
为了避免锁成为性能瓶颈。
为了wait-free和lock-free
原子指令 (x均为std::atomic) | 作用 |
---|---|
x.load() | 返回x的值。 |
x.store(n) | 把x设为n,什么都不返回。 |
x.exchange(n) | 把x设为n,返回设定之前的值。 |
x.compare_exchange_strong(expected_ref, desired) | 若x等于expected_ref,则设为desired,返回成功;否则把最新值写入expected_ref,返回失败。 |
x.compare_exchange_weak(expected_ref, desired) | 相比compare_exchange_strong可能有spurious wakeup。 |
x.fetch_add(n), x.fetch_sub(n) | 原子地做x += n, x-= n,返回修改之前的值。 |
一个线程把数据A变为了B,然后又重新变成了A。
解决方法:加入版本号
指令重排问题。
缓存一致性使得对cache中的更改必须同步到其他cache副本中,且这个过程是原子的,造成原子指令需要等待最新的cacheline。
解决方法:
位于同一个cacheline中的变量可能自身不变,而其他变量频繁改变,同样要等待cacheline同步,因此频繁被多线程修改的变量应该放在独立的cacheline中。
雪崩指流量过大造成请求超时,且流量减少后仍无法恢复的现象。
雪崩式多服务才会发生的。比如请求访问A服务,A服务又访问了B服务,这时B被打满请求,正常情况下还是不会雪崩的,但是有例外:
流量放大,A可能对B发起了过于频繁的基于超时的重试,造成了恶性循环:B无法恢复->A超时->A继续重试->B无法恢复
A或B没限制缓冲队列的长度
一些场景,我们希望同样的请求落到一台服务器上,这样可以利用缓存,不同的机器也不用缓存所有内容。hash可以满足这个要求:输入x总是会发送到第hash(x) % n的服务器。但是有个缺点,m增加时,之前的发送目的地会变,如果目的地时缓存服务,所有缓存就会失效。
一致性哈希解决了这个问题,新增加服务器时,发向旧节点的请求只会有一部分转向新节点。
KML: Using Machine Learning to Improve Storage Systems
1 | adduser username |
adduser和useradd的区别是adduser会在/home下创建同名目录,也会设置密码,设置shell,用adduser就可以。
1 | usermod -aG sudo username |
1 | rsync |
1 | ssh-keygen |
配置SSH
打开/etc/ssh/sshd_config
文件设置
1 | Port XXXX#修改默认端口号 |
然后重启SSH
service sshd restart
本地使用私钥登录
使用密钥登录这个就用不到了
1 | sudo apt install fail2ban |
选择自己的显卡型号,系统
https://www.nvidia.cn/Download/index.aspx?lang=cn
wget url
下载NVIDIA-Linux-x86_64-xx.xx.run
驱动
不少机场都是给一个订阅url,然而v2ray命令行版并不支持订阅,需要解析工具:https://github.com/arkrz/v2sub
1 | 因 ping 与 服务重启 权限需要,以 root 权限运行: |
vmess可以直接用下面脚本转json
https://github.com/boypt/vmess2json
没梯子的话安装脚本大概率下载不了,自己下载预编译文件后传到服务器上,把文件copy到对应文件夹
https://github.com/v2fly/v2ray-core/releases
安装v2gen,生成config,默认端口号是1080和1081,需要在
https://github.com/teasiu/v2gen/blob/main/README_zh_cn.md
1 | sudo apt install samba |
编辑/etc/samba/smb.config
1 | [shareFold] #共享文件夹名称 |
常用命令:
1 | pdbedit -L #列出samba用户 |
公网机器上需要在ect/ssh/sshd_config
下修改GatewayPorts yes
然后sudo service sshd restart
内网机器:
1 | autossh -p 公网ssh端口号 -M 本地监听端口号 -NR '*:对外端口号:localhost:本地ssh端口号' 公网username@公网ip |
内网机器添加service
1 | [Unit] |
id_rsa_cloud生成:
ssh-keygen
ssh-copy-id -p port 公网主机username@ip
注意公网机器的端口是否开放
1、在线安装zerotier
curl -s https://install.zerotier.com/ | sudo bash
2、添加开机自启
$ sudo systemctl enable zerotier-one.service
3、启动zerotier-one.service
$ sudo systemctl start zerotier-one.service
4、加入网络
$ sudo zerotier-cli join xxxxxxx
https://blog.csdn.net/weixin_41855380/article/details/109553353
1 | //基本用法 |
管程用来管理类额成员变量和成员方法,让这个类是线程安全的。下面以阻塞队列的实现为例
管程解决了并发编程中的俩个核心问题:互斥,同步
首先是一个队列,有插入和删除操作。当队列中没有元素时,弹出操作将会被阻塞,直到有元素被插入时才会被唤醒。队列满时,队列的插入操作被阻塞,直到有元素被弹出后才会被唤醒。
阻塞队列的应用:在线程池实现中,使用阻塞队列来保存任务,直到线程空闲之后从阻塞队列弹出任务来执行。一旦队列为空,线程就会被阻塞,直到有新任务被插入为止。
1 | template <typename T, typename D = std::deque<T> > |
C++可以看成由几个模块组成:
这样区分的目的是:不同的模块有不同的编程策略
尽量以编译器代替预处理器
方便在编译出错的时候找到错误位置,因为编译器对变量报错是根据符号表来的,宏至少简单的文本替换,出错的时候不容易定位。
const int* ptr
被指的是常量,也就是指向的内存不能修改int* const ptr
指针本身是常量,指针指向的位置不能修改const int* ptr
和int const* ptr
,意义相同std::vector<int>::const_iterator const_itr = vec.begin()
const可以修饰函数的返回值、函数参数、函数自身
const Rational operator* (const Rational& lhs, const Rational& rhs);
指明该函数不会修改类的任何成员数据的值
1 | class BigArray { |
这个是编译不过的。我们希望的是getItem不会修改核心成员,而不考虑非核心成员,是logical constness。
编译器只认bitwise constness,为了解决这个问题,用mutable修饰accessCounter:mutable int accessCounter;
。
为了避免代码重复,使用non-const函数调用const函数。
从变量的名称开始,顺时针移动到下一个指针或者类型,直到表达式结束
或者是从右到左的语法解码,后面的修饰前面的
*
读作pointer to
int const *p
:p是一个指针,指向一个const int,也就是说p本身指向的位置是可以修改的,但是指向的内存不能修改int * const p
:p是一个const指针,指向int,就是说这个指针指向的位置不能修改,但是允许修改存储在地址的值const int* cont p
:p是一个const 指针,指向一个const int。p既不能修改指向的位置,也不能修改里面的值对于基本数据类型,手工完成。对于类,写构造函数,使得对象的每一个成员都被初始化。
主要:赋值和初始化不同
http://www.noobyard.com/article/p-yfcqfueo-su.html
为什么要有虚函数:为了实现动态绑定,即在运行时决定调用哪个函数。举个例子:person为基类,派生出student 和 teacher子类,现在学校大门口有一个队列,存放了一系列person指针,每次pop,通过ptr调用函数“出校”,区别是学生需要扫码,而教师不需要,但是编译器在编译程序的时候并不知道要调用哪个出校函数,这取决与运行时队列pop出来的到底是student还是teacher,也就是动态绑定的含义,通过对象的虚表指针调用,因此也可以理解为:虚函数是将函数绑定到特定类的一种手段。
https://zhuanlan.zhihu.com/p/37439983
static出现在两种场景:面向对象和面向过程
为什么要用static成员变量?每个对象有用相同的某项属性,就用static,比如student类,包含static int num 学生总人数,这是每个学生实例公用的。
static成员变量分配在数据段
跟全局对象相比:静态成员变量的命名空间在类中,不会与全局命名空间的命名产生冲突。static可以加入访问控制,比如private。
为什么用static成员函数?类似的,static成员函数为类服务,而不是为对象服务,因此static成员函数也没有this指针,因此也仅可以访问静态成员函数和静态成员变量
静态成员函数不能访问非静态成员函数和变量
反过来非静态成员函数可以任意访问静态成员函数和变量
static函数仅在声明他的函数中可见,不能被其他文件使用
为什么用?不同文件中可以定义相同名字的函数,不会发生冲突。
RAII是c++特有的资源管理方式。把依赖栈和析构函数,对所有资源进行管理(包括堆内存,下面有一个例子)。栈上的指针在释放的时候会调用析构函数,在析构函数里释放堆内存。
例子:
create_shape在堆上new了对象,返回对象的指针,如何让这块内存不会泄露?
把这个函数的返回值(对象的指针),包裹到一个局部对象中,该局部对象的析构函数中delete掉这个指针。
1 |
|
经典RAII的例子:
1 |
|
而不写成:
1 |
|
从刚刚shape_wrapper类改造成模板类,就是一个简单的智能指针:
1 |
|
但是这样跟指针的行为还有点差异,比如不能用*解引用,不能用->访问成员,不能用在bool表达式里,因此添加:
1 |
|
然后对于拷贝构造呢?smart_ptr<shape> ptr2(ptr1)
应该怎么表现?
转移指针所有权,这就是auto_ptr的实现了,现在已经废除,因为这样设计的smart_ptr一旦进行了赋值或拷贝,就不在拥有这个对象了。
1 |
|
再改下,就是unique_ptr:
1 |
|
现在添加引用计数,形成shared_ptr:
1 |
|
c++11引入atomic模板,可以应用到任何类型上,但实现不一样
对于整型量和指针等简单类型,通常结果是无锁的原子对象;
而对于另外一些类型,比如 64 位机器上大小不是 1、2、4、8(有些平台 / 编译器也支持对更大的数据进行无锁原子操作)的类型,编译器会自动为这些原子对象的操作加上锁。
编译器提供了一个原子对象的成员函数 is_lock_free,可以检查这个原子对象上的操作是否是无锁的。
std::function是一个可调用对象的包装器
可调用对象可以是:函数指针、一个有operator成员函数的类对象、可以被转换成函数指针的类对象、类成员函数指针。
比如:
1 | // 普通函数 |
虽然他们类型不同,但是调用形式是相同的,调用形式就是参数和返回值,也就是function初始化时候的模板参数,于是就可以这样写:
1 | std::function<int(int ,int)> a = add; |
用来取代函数指针,特别适合作为回调函数使用。
把可调用实体的某些传入参数,绑定到已有的变量,返回一个std::function
1 | struct Foo { |
通过std::bind和std::function配合使用,所有的可调用对象均有了统一的操作方法。
auto 的推导用在初始化时候,但目前的c++标准不允许类成员变量使用auto。
auto总是推导出值类型,不可能是引用。
auto可以加上const、volatile、*、& 这样的类型修饰符,得到新的类型。
Update your browser to view this website correctly. Update my browser now