initial import

This commit is contained in:
kevin
2020-07-26 17:09:05 +08:00
commit 7e3a369a8f
647 changed files with 54754 additions and 0 deletions

4
doc/breaker.md Normal file
View File

@@ -0,0 +1,4 @@
# 熔断机制设计
## 设计目的
* 依赖的服务出现大规模故障时,调用方应该尽可能少调用,降低故障服务的压力,使之尽快恢复服务

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

142
doc/kubernetes_setup.md Normal file
View File

@@ -0,0 +1,142 @@
# kubernetes集群搭建(centos7)
* 修改每台主机的hostname如果需要的话
* `hostname <hostname>`
* 修改/etc/hostname
* 选择一台机器安装ansible为了便于从一台机器上操作所有机器
* 安装zsh & oh-my-zsh为了更方便的使用命令行可选
```
yum install -y zsh
yum install -y git
sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"
```
* `yum install -y ansible`
* 解决错误 `RequestsDependencyWarning: urllib3 (1.22) or chardet (2.2.1) doesn't match a supported version`
```
pip uninstall -y urllib3
pip uninstall -y chardet
pip install requests
```
* 禁用command_warnings在/etc/ansible/ansible.cfg里将`command_warnings = False`前面的#去掉
* 将所有机器的内网ip按照分组增加到/etc/ansible/hosts如下:
```
[master]
172.20.102.[208:210]
[node]
172.20.102.[211:212]
```
* 用root账号通过ssh-keygen生成内网无需密码root登录其它服务器使用默认选项
* 用ssh-copy-id将生成的id_rsa.pub传送到所有主机的authorized_hosts里包括本机
`ssh-copy-id root@172.20.102.208`
* 验证ansible是否可以登录所有服务器如下:
```
[root@172 ~]# ansible all -m ping -u root
172.20.102.208 | SUCCESS => {
"changed": false,
"ping": "pong"
}
...
```
* 更新所有服务器
`ansible all -u root -m shell -a "yum update -y"`
* 所有服务器上安装docker
```
ansible all -u root -m shell -a "yum remove docker docker-client docker-client-latest docker-core docker-latest docker-latest-logrotate docker-logrotate docker-selinux docker-engine-selinux docker-engine"
ansible all -u root -m shell -a "yum install -y yum-utils"
ansible all -u root -m shell -a "yum install -y device-mapper-persistent-data"
ansible all -u root -m shell -a "yum install -y lvm2"
ansible all -u root -m shell -a "yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo"
ansible all -u root -m shell -a "yum install -y docker-ce"
ansible all -u root -m shell -a "systemctl enable docker"
ansible all -u root -m shell -a "systemctl start docker"
```
* 每台机器上添加阿里云的kubernetes repo
```
# cat k8srepo.yaml
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=http://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=0
repo_gpgcheck=0
gpgkey=http://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg
http://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF
# ansible-playbook k8srepo.yaml
```
* 安装kubelet, kubeadm, kubectl, ipvsadm
`ansible all -u root -m shell -a "yum install -y kubelet kubeadm kubectl ipvsadm"`
* 禁用所有服务器上的swap
`ansible all -u root -m shell -a "swapoff -a"`
* 允许所有服务器进行转发因为k8s的NodePort需要在所有服务器之间进行转发
`ansible all -u root -m shell -a "iptables -P FORWARD ACCEPT"`
* 由于k8s.gcr.io不能访问需要从本机科学上网docker pull如下几个image
```
k8s.gcr.io/kube-proxy-amd64:v1.11.1
k8s.gcr.io/kube-controller-manager-amd64:v1.11.1
k8s.gcr.io/kube-scheduler-amd64:v1.11.1
k8s.gcr.io/kube-apiserver-amd64:v1.11.1
k8s.gcr.io/coredns:1.1.3
k8s.gcr.io/etcd-amd64:3.2.18
k8s.gcr.io/pause:3.1
```
通过命令一次完成拉取
`while IFS= read -r line; do docker pull $line; done`
然后上传到一台服务器
`while IFS= read -r line; do docker save <image> | pv | ssh <user>@<server ip> "docker load"; done`
同步到所有k8s服务器其中$i是为了匹配所有内网ip
`while IFS= read -r line; do for ((i=208;i<213;i++)); do docker save $line | ssh root@172.20.102.$i "docker load"; done; done`
* 在一台master服务器上初始化集群
`kubeadm init --api-advertise-addresses <本机内网ip> --kubernetes-version=v1.11.1`
注意最后的`kubeadm join`一行,用来在其它服务器加入集群(稍后用)
初始化配置master上执行如下命令
```
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
```
添加calico网络
`kubectl apply -f https://docs.projectcalico.org/v3.1/getting-started/kubernetes/installation/hosted/kubeadm/1.7/calico.yaml`
* 从所有其它服务器执行master上获得的kubeadm join那行命令里面包含了加入的token
* 执行`kubectl get nodes`验证集群是否成功

46
doc/loadshedding.md Normal file
View File

@@ -0,0 +1,46 @@
# 服务自适应降载保护设计
## 设计目的
* 保证系统不被过量请求拖垮
* 在保证系统稳定的前提下,尽可能提供更高的吞吐量
## 设计考虑因素
* 如何衡量系统负载
* 是否处于虚机或容器内需要读取cgroup相关负载
* 用1000m表示100%CPU推荐使用800m表示系统高负载
* 尽可能小的Overhead不显著增加RT
* 不考虑服务本身所依赖的DB或者缓存系统问题这类问题通过熔断机制来解决
## 机制设计
* 计算CPU负载时使用滑动平均来降低CPU负载抖动带来的不稳定关于滑动平均见参考资料
* 滑动平均就是取之前连续N次值的近似平均N取值可以通过超参beta来决定
* 当CPU负载大于指定值时触发降载保护机制
* 时间窗口机制用滑动窗口机制来记录之前时间窗口内的QPS和RT(response time)
* 滑动窗口使用5秒钟50个桶的方式每个桶保存100ms时间内的请求循环利用最新的覆盖最老的
* 计算maxQPS和minRT时需要过滤掉最新的时间没有用完的桶防止此桶内只有极少数请求并且RT处于低概率的极小值所以计算maxQPS和minRT时按照上面的50个桶的参数只会算49个
* 满足以下所有条件则拒绝该请求
1. 当前CPU负载超过预设阈值或者上次拒绝时间到现在不超过1秒(冷却期)。冷却期是为了不能让负载刚下来就马上增加压力导致立马又上去的来回抖动
2. `averageFlying > max(1, QPS*minRT/1e3)`
* averageFlying = MovingAverage(flying)
* 在算MovingAverage(flying)的时候超参beta默认取值为0.9表示计算前十次的平均flying值
* 取flying值的时候有三种做法
1. 请求增加后更新一次averageFlying见图中橙色曲线
2. 请求结束后更新一次averageFlying见图中绿色曲线
3. 请求增加后更新一次averageFlying请求结束后更新一次averageFlying
我们使用的是第二种,这样可以更好的防止抖动,如图:
![flying策略对比](images/shedding_flying.jpg)
* QPS = maxPass * bucketsPerSecond
* maxPass表示每个有效桶里的成功的requests
* bucketsPerSecond表示每秒有多少个桶
* 1e3表示1000毫秒minRT单位也是毫秒QPS*minRT/1e3得到的就是平均每个时间点有多少并发请求
## 降载的使用
* 已经在ngin和rpcx框架里增加了可选激活配置
* CpuThreshold如果把值设置为大于0的值则激活该服务的自动降载机制
* 如果请求被drop那么错误日志里会有`dropreq`关键字
## 参考资料
* [滑动平均](https://www.cnblogs.com/wuliytTaotao/p/9479958.html)
* [Sentinel自适应限流](https://github.com/alibaba/Sentinel/wiki/%E7%B3%BB%E7%BB%9F%E8%87%AA%E9%80%82%E5%BA%94%E9%99%90%E6%B5%81)
* [Kratos自适应限流保护](https://github.com/bilibili/kratos/blob/master/doc/wiki-cn/ratelimit.md)

29
doc/mapreduce.md Normal file
View File

@@ -0,0 +1,29 @@
# mapreduce用法
## Map
> channel是Map的返回值
由于Map是个并发操作如果不用range或drain的方式那么在使用返回值的时候可能Map里面的代码还在读写这个返回值可能导致数据不全或者`concurrent read write错误`
* 如果需要收集Map生成的结果那么使用如下方式
```
for v := range channel {
// v is with type interface{}
}
```
* 如果不需要收集结果那么就需要显式的调用mapreduce.Drain
```
mapreduce.Drain(channel)
```
## MapReduce
* mapper和reducer方法里可以调用cancel调用了cancel之后返回值会是`nil, false`
* mapper里面如果有item不写入writer那么这个item就不会被reduce收集
* mapper里面如果有处理item时panic那么这个item也不会被reduce收集
* reduce是单线程所有mapper出来的结果在这里串行处理
* reduce里面不写writer或者panic会导致返回`nil, false`

15
doc/periodicalexecutor.md Normal file
View File

@@ -0,0 +1,15 @@
# PeriodicalExecutor设计
# 添加任务
* 当前没有未执行的任务
* 添加并启动定时器
* 已有未执行的任务
* 添加并检查是否到达最大缓存数
* 如到,执行所有缓存任务
* 未到,只添加
# 定时器到期
* 清除并执行所有缓存任务
* 再等待N个定时周期如果等待过程中一直没有新任务则退出

13
doc/rpc.md Normal file
View File

@@ -0,0 +1,13 @@
# rpc设计规范
* 目录结构
* service/remote目录下按照服务所属模块存放比如用户的profile接口目录如下
`service/remote/user/profile.proto`
* 生成的profile.pb.go也放在该目录下并且profile.proto文件里要加上`package user;`
* 错误处理
* 需要使用status.Error(code, desc)来定义返回的错误
* code是codes.Code类型尽可能使用grpc已经定义好的code
* codes.DeadlineExceeded, codes.Internal, codes.Unavailable, codes.DataLoss错误会被自动熔断

24
doc/sql-cache.md Normal file
View File

@@ -0,0 +1,24 @@
# DB缓存机制
## QueryRowIndex
* 没有查询条件到Primary映射的缓存
* 通过查询条件到DB去查询行记录然后
* **把Primary到行记录的缓存写到redis里**
* **把查询条件到Primary的映射保存到redis里***框架的Take方法自动做了*
* 可能的过期顺序
* 查询条件到Primary的映射缓存未过期
* Primary到行记录的缓存未过期
* 直接返回缓存行记录
* Primary到行记录的缓存已过期
* 通过Primary到DB获取行记录并写入缓存
* 此时存在的问题是查询条件到Primary的缓存可能已经快要过期了短时间内的查询又会触发一次数据库查询
* 要避免这个问题,可以让**上面粗体部分**第一个过期时间略长于第二个比如5秒
* 查询条件到Primary的映射缓存已过期不管Primary到行记录的缓存是否过期
* 查询条件到Primary的映射会被重新获取获取过程中会自动写入新的Primary到行记录的缓存这样两种缓存的过期时间都是刚刚设置
* 有查询条件到Primary映射的缓存
* 没有Primary到行记录的缓存
* 通过Primary到DB查询行记录并写入缓存
* 有Primary到行记录的缓存
* 直接返回缓存结果