Kubernetes Service定义了这样一种抽象:一个Pod的逻辑分组,一种可以访问它们的策略 —— 通常称为微服务。这一组Pod能够被Service访问到,通常是通过Label Selector
通俗的讲:SVC负责检测Pod的状态信息,不会因pod的改动IP地址改变(因为关注的是标签),导致Nginx负载均衡影响
Service能够提供负载均衡的能力,但是在使用上有以下限制:
Service 在 K8s 中有以下四种类型
①ClusterIp:默认类型,自动分配一个仅 Cluster 内部可以访问的虚拟 IP
②NodePort:在 ClusterIP 基础上为 Service 在每台机器上绑定一个端口,这样就可以通过
访问node01的30001相当于访问定义的SVC后端的80的三个不pod同服务(RR)
client——》nginx(负载接收器,反向代理)——》node1,node2
③LoadBalancer:在 NodePort 的基础上,借助 cloud provider 创建一个外部负载均衡器,并将请求转发到
④ExternalName:把集群外部的服务引入到集群内部来,在集群内部直接使用。没有任何类型代理被创建,这只有 kubernetes 1.7 或更高版本的 kube-dns 才支持
SVC基础导论
总结:
在 Kubernetes 集群中,每个 Node 运行一个kube-proxy进程。kube-proxy负责为Service实现了一种VIP(虚拟 IP)的形式,而不是ExternalName的形式。在 Kubernetes v1.0 版本,代理完全在 userspace。在Kubernetes v1.1 版本,新增了 iptables 代理,但并不是默认的运行模式。从 Kubernetes v1.2 起,默认就是iptables 代理。在 Kubernetes v1.8.0-beta.0 中,添加了 ipvs 代理
代理层级:userspace——》iptables——》ipvs
在 Kubernetes 1.14 版本开始默认使用ipvs 代理
在 Kubernetes v1.0 版本,Service是 “4层”(TCP/UDP over IP)概念。在 Kubernetes v1.1 版本,新增了Ingress API(beta 版),用来表示 “7层”(HTTP)服务
为何不使用 round-robin DNS?
DNS会在很多的客户端里进行缓存,很多服务在访问DNS进行域名解析完成、得到地址后不会对DNS的解析进行清除缓存的操作,所以一旦有他的地址信息后,不管访问几次还是原来的地址信息,导致负载均衡无效。
ipvs 代理模式(标准)
这种模式,kube-proxy 会监视 Kubernetes Service对象和Endpoints,调用netlink接口以相应地创建ipvs 规则并定期与 Kubernetes Service对象和Endpoints对象同步 ipvs 规则,以确保 ipvs 状态与期望一致。访问服务时,流量将被重定向到其中一个后端 Pod
与 iptables 类似,ipvs 于 netfilter 的 hook 功能,但使用哈希表作为底层数据结构并在内核空间中工作。这意味着 ipvs 可以更快地重定向流量,并且在同步代理规则时具有更好的性能。此外,ipvs 为负载均衡算法提供了更多选项,例如:
①rr:轮询调度
②lc:最小连接数
③dh:目标哈希
④sh:源哈希
⑤sed:最短期望延迟
⑥nq:不排队调度
<–注意;ipvs模式假定在运行 kube-proxy 之前在节点上都已经安装了IPVS内核模块。当kube-proxy以ipvs代理模式启动时,kube-proxy 将验证节点上是否安装了IEVS模块,如果末安装,则kube-proxy 将回退到iptables 代理模式–>
1 | ipvsadm -Ln |
clusterIP 主要在每个 node 节点使用 iptables,将发向 clusterIP 对应端口的数据,转发到 kube-proxy 中。然后 kube-proxy 自己内部实现有负载均衡的方法,并可以查询到这个 service 下对应 pod 的地址和端口,进而把数据转发给对应的 pod 的地址和端口
为了实现图上的功能,主要需要以下几个组件的协同工作:
api将信息写到etcd,kubeproxy监测etcd的变化,得到变化以后写入到ipvs规则
第一步 创建 svc-deployment.yaml 文件
1 | [root@k8s-master01 ~]# vim svc-deployment.yaml |
1 | kubectl apply -f svc-deployment.yaml |
1 | kubectl get pod -o wide |
这样地址访问,不太行。如果pod死亡后会出现新的pod,然后与之前的地址又不一致。因此 为了可靠的访问,需要进行第二步,SVC创建
第二步 给deploy绑定svc,即创建 Service 信息
1 | [root@k8s-master01 ~]# vim svc.yaml |
1 | kubectl apply -f svc.yaml |
这里是两个的原因是因为有一个容器还在创建,没关系
kubectl delete -f svc.yaml 也可以看得到对应的服务也被删除了。
直接访问svc的IP地址,相当于通过ipvs模块,负载均衡,实现代理到后端节点上。
直接访问svc的IP地址,可以看到轮询RR效果
它属于一种特殊的Cluster IP,
有时不需要或不想要负载均衡,以及单独的 Service IP 。遇到这种情况,可以通过指定 ClusterIP(spec.clusterIP) 的值为 “None” 来创建 Headless Service 。这类 Service 并不会分配 Cluster IP, kube-proxy 不会处理它们,而且平台也不会为它们进行负载均衡和路由
1 | [root@k8s-master01 ~]# vim svc-none.yaml |
虽然没有svc了,但是可以通过域名的方案依然可以访问
svc创建成功会把主机名(svc名.名字空间名.集群域名)写入到coredns
1 | [root@k8s-master01 ~]# kubectl get pod -n kube-system -o wide 获取当前dns的地址信息 |
可以在当前的物理机上,暴露一个端口,让内部服务暴露到外部
客户端可以通过物理机IP+端口方式 访问到集群内部
nodePort原理在于在 node 上开了一个端口,将向该端口的流量导入到 kube-proxy,然后由 kube-proxy 进一步(与接口层交互)到给对应的 pod
1 | [root@k8s-master01 ~]# vim nodeport.yaml |
同时可以看出,一组pod可以对应不同的svc的。只要pod标签与svc标签一致就可以关联。多对多的关系 n:m
浏览器访问:master虚拟机IP:端口 10.0.100.10:32642
并且子节点pod也会开启这个端口
10.0.100.11:32642与10.0.100.12:32642
查询流程
1 | ipvsadm -Ln |
loadBalancer和nodePort其实是同一种方式。区别在于loadBalancer比nodePort多了一步,就是可以调用cloud provider去创建LB来向节点导流(LB收费)
别名操作,外部服务引入到集群内
这种类型的 Service 通过返回 CNAME 和它的值,可以将服务映射到 externalName 字段的内容( 例如:hub.atguigu.com )。ExternalName Service 是 Service 的特例,它没有 selector,也没有定义任何的端口和Endpoint。相反的,对于运行在集群外部的服务,它通过返回该外部服务的别名这种方式来提供服务
1 | kind: Service |
当查询主机 my-service-1.defalut.svc.cluster.local ( SVC_NAME.NAMESPACE.svc.cluster.local ) 时,集群的DNS 服务将返回一个值 hub.atguigu.com 的 CNAME 记录。访问这个服务的工作方式和其他的相同,唯一不同的是重定向发生在 DNS 层,而且不会进行代理或转发
1 | vim ex.yaml |
1 | dig -t A my-service-1.default.svc.cluster.local @10.244.0.13 |
这个IP是coredns地址,通过kubectl get pod -n kube-system -o wide
对传统的SVC来说仅支持四层
Ingress-Nginx github 地址:https://github.com/kubernetes/ingress-nginx
Ingress-Nginx 官方网站:https://kubernetes.github.io/ingress-nginx/
其实对Nginx的暴露方案是Nodepod,内部的服务暴露给外部
1 | kubectl apply -f mandatory.yaml |
进入官方下载
1 | cd /usr/local/install-k8s/plugin/ |
第一步:三个节点,一主二子都要解压导入
1 | tar -zxvf ingree.contro.tar.gz #解压 |
第二步:创建pod和svc
1 | kubectl apply -f mandatory.yaml |
deployment、Service、Ingress Yaml 文件
现在想通过Nginx的Ingress方案暴露出去,实现域名访问的这么一个结构
1 | [root@k8s-master01 ~]# vim ingress.http.yaml |
1 | [root@k8s-master01 ~]# vim ingress1.yaml |
在W10下进行测试,修改本地host解析,C:\Windows\System32\drivers\etc\hosts
10.0.100.10 www1.atguigu.com
注意访问的端口不是80,而是ingress的端口32510
1 | kubectl get svc -n ingress-nginx |
第一个deployment和第一个svc
1 | [root@k8s-master01 ~]# mkdir ingress-vh |
第二个deployment和第二个svc
1 | [root@k8s-master01 ingress-vh]# cp -a deployment.yaml deployment2.yaml |
写Ingress1、2规则
1 | [root@k8s-master01 ~]# vim ingressrule.yaml |
1 | [root@k8s-master01 ingress-vh]# kubectl get pod -n ingress-nginx |
查看Ingress暴露的端口kubectl get svc -n ingress-nginx
kubectl get ingress 查看规则
浏览器访问测试
动态图效果演示虚拟主机
创建证书,以及 cert 存储方式
1 | openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=nginxsvc/O=nginxsvc" |
deployment、Service、Ingress Yaml 文件
1 | apiVersion: extensions/v1beta1 |
操作过程
第一步:创建证书,以及cert存储方式
1 | [root@k8s-master01 ~]# mkdir https |
第二步:创建deployment、Service文件
1 | [root@k8s-master01 https]# cp /root/ingress-vh/deployment.yaml . |
第三步:创建Ingress Yaml文件
多了个tls
1 | [root@k8s-master01 https]# vim https.ingress.yaml |
浏览器访问看效果
https://www3.atguigu.com:31401
对于nginx来说采用的apache认证的模块
1 | mkdir basic-auth |
vim ingress.yaml
1 | apiVersion: extensions/v1beta1 |
1 | [root@k8s-master01 basic-auth]# kubectl apply -f ingress.yaml |
访问的是80端口对应的32510端口
浏览器访问
实验操作
访问www4,跳到www3。 https访问
vim re.yaml
1 | apiVersion: extensions/v1beta1 |
ps:遇到粘贴错乱可以在set paste
浏览器访问:http://re.atguigu.com:32510/
跳转到 https://www3.atguigu.com:31401/hostname.html
我们在运维架构建设中,资产管理是核心环节。所有环节都需要围绕这个核心来做,包括但不限于资产监控,资产授权,资产规划和部署应用,资源费用核算,资产盘点,资产回收等等。
而jumpserver作为一款安全级别在5A的开源堡垒机,受到了所有互联网大厂的青睐和日常使用。我们这些运维、开发或架构的技术型日常工作都是在类unix环境,通常是linux环境,和服务器打交道,用命令或编写脚本来高效的维护和操作处理。所以默认堡垒机对linux的支持是必须的也是常规的,堡垒机对linux资产管理参考官网文档,这里就不再赘述了。但有时我们的业务需要在windows server上来操作,但官方文档也没有详细说明。本文重点介绍堡垒机jumpserver要如何去管理windows server。
添加windows主机那个特权用户要怎么写呢?是不是有这样的困惑,只需普通用户就可以。
操作步骤:系统用户–>普通用户–>创建RDP–>填写用户信息提交。
先添加administrator用户,该用户要和登入windows的远程用户账户一致。
操作步骤:资产列表–>创建–>填写windows server资产信息提交。
注意,这里有几点要说明下:
1,我使用的是目前最新版(v2.22.1)特权用户没有号,就是非必填项,所以可以不写,如果使用版本不一样带号为必填项,可以随便选个,虽然特权用户在windows上没用。
2,平台选windows,协议组选rdp。其他和添加linux一样。
操作步骤:资产授权–>填写授权信息(和linux一致)–>系统用户选择要选择创建windows server的普通用户(登录windows server时通过该用户远程进行操作)。
到终端列表中,选择该资产,就会用选择的windows server系统普通用户登录了。
堡垒机jumpserver添加windows server管理不需要纠结特权用户,只需建立普通系统用户即可,但对该普通用户是有要求的,该普通系统用户必须是可以远程登录windows server的用户。
]]>本文详细介绍了python项目(flask或django等)在部署到linux服务器上后,uwsgi常用配置和nginxd对应通信配置,以及supervisor常用配置详解。本篇为高级篇,至于怎么安装请参考博客中其他文档,谢谢。
作为模板uwsgi.ini,当然也可以根据uwsgi –help来查看或自定义。官方参数详解
1 | master = true |
1 | # 指定加载的WSGI文件 |
1 | # 模块名:可调用对象app |
1 | module=manager |
其中上面配置有几处,是可以选择的。
uWSGI和Nginx之间有3种通信方式,: unix socket,TCP socket和http。而Nginx的配置必须与uwsgi配置保持一致
1 | # 以下uwsgi与nginx通信手段3选一即可 |
如果你的nginx与uwsgi在同一台服务器上,优先使用本地机器的unix socket进行通信,这样速度更快。
即uwsgi配置了选项1,此时nginx的配置文件如下所示:
1 | location / { |
如果nginx与uwsgi不在同一台服务器上,可以使用选项2和3。这里使用TCP socket通信,nginx应如下配置:
1 | location / { |
同样的,如果nginx与uwsgi不在同一台服务器上,用http协议进行通信,nginx配置如下:
1 | location / { |
1 | #uwsgi --ini uwsgi.ini # 启动 |
supervisor就是用Python开发的一套通用的进程管理程序,能将一个普通的命令行进程变为后台守护进程daemon,并监控进程状态,异常退出时能自动重启。
通过这种方式安装后,会自动设置为开机启动
1 | #Ubuntu: |
也可以通过 pip install supervisor
进行安装,但是需要手动启动,然后设置为开机启动(不推荐这种安装方式)
1 | systemctl start supervisord.service #启动supervisor并加载默认配置文件 |
Supervisor 是一个 C/S 模型的程序,supervisord
是 server 端,supervisorctl
是 client 端。
下面介绍 supervisord 配置方法。supervisord 的配置文件默认位于 /etc/supervisord.conf
,内容如下(;
后面为注释):
1 | ; supervisor config file |
program 的配置文件就写在,supervisord 配置中 include
项的路径下:/etc/supervisor/conf.d/
,然后 program 的配置文件命名规则推荐:app_name.conf
1 | [program:uwsgi] |
supervisorctl 是 supervisord 的命令行客户端工具,使用的配置和 supervisord 一样,这里就不再说了。下面,主要介绍 supervisorctl 操作的常用命令:
输入命令 supervisorctl
进入 supervisorctl 的 shell 交互界面(还是纯命令行😓),就可以在下面输入命令了。:
也可以直接通过 shell 命令操作:
启动supervisor之后就可以通过ip:9001
访问supervisor的管理页面,前提是配置中supervisorctl 配置这种http的访问方式,而不是像上面用socket套接字。
Linux 的命令行里面有用来停止正在运行的进程的所有所需工具。这里将为您讲述细节。
想像一下:你打开了一个程序(可能来自于你的桌面菜单或者命令行),然后开始使用这个程序,没想到程序会锁死、停止运行、或者意外死机。你尝试再次运行该程序,但是它反馈说原来的进程没有完全关闭。
你该怎么办?你要结束进程。但该如何做?不管你信与不信,最好的解决方法大都在命令行里。值得庆幸的是, Linux 有供用户杀死错误的进程的每个必要的工具,然而,你在执行杀死进程的命令之前,你首先需要知道进程是什么。该如何处理这一类的任务。一旦你能够掌握这种工具,它实际是十分简单的……
我来概述的步骤是每个 Linux 发行版都能用的,不论是桌面版还是服务器版。我将限定只使用命令行,请打开你的终端开始输入命令吧。
杀死一个没有响应的进程的第一个步骤是定位这个进程。我用来定位进程的命令有两个:top 和 ps 命令。top 是每个系统管理员都知道的工具,用 top 命令,你能够知道到所有当前正在运行的进程有哪些。在命令行里,输入 top 命令能够就看到你正在运行的程序进程(图1)
从显示的列表中你能够看到相当重要的信息,举个例子,Chrome 浏览器反映迟钝,依据我们的 top 命令显示,我们能够辨别的有四个 Chrome 浏览器的进程在运行,进程的 pid 号分别是 3827、3919、10764 和 11679。这个信息是重要的,可以用一个特殊的方法来结束进程。
尽管 top 命令很是方便,但也不是得到你所要信息最有效的方法。 你知道你要杀死的 Chrome 进程是那个,并且你也不想看 top 命令所显示的实时信息。 鉴于此,你能够使用 ps 命令然后用 grep 命令来过滤出输出结果。这个 ps 命令能够显示出当前进程列表的快照,然后用 grep 命令输出匹配的样式。我们通过 grep 命令过滤 ps 命令的输出的理由很简单:如果你只输入 ps 命令,你将会得到当前所有进程的列表快照,而我们需要的是列出 Chrome 浏览器进程相关的。所以这个命令是这个样子:
1 | ps aux | grep chrome |
当你搜索图形化程序的信息时,这个 x 参数是很重要的。
当你输入以上命令的时候,你将会得到比图 2 更多的信息,而且它有时用起来比 top 命令更有效。
现在我们开始结束进程的任务。我们有两种可以帮我们杀死错误的进程的信息。
1 | 进程的名字 |
你用哪一个将会决定终端命令如何使用,通常有两个命令来结束进程:
1 | kill - 通过进程 ID 来结束进程 |
有两个不同的信号能够发送给这两个结束进程的命令。你发送的信号决定着你想要从结束进程命令中得到的结果。举个例子,你可以发送 HUP(挂起)信号给结束进程的命令,命令实际上将会重启这个进程。当你需要立即重启一个进程(比如就守护进程来说),这是一个明智的选择。你通过输入 kill -l 可以得到所有信号的列表,你将会发现大量的信号。
最经常使用的结束进程的信号是:
好的是,你能用信号值来代替信号名字。所以你没有必要来记住所有各种各样的信号名字。
所以,让我们现在用 kill 命令来杀死 Chrome 浏览器的进程。这个命令的结构是:
1 | kill SIGNAL PID |
这里 SIGNAL 是要发送的信号,PID 是被杀死的进程的 ID。我们已经知道,来自我们的 ps 命令显示我们想要结束的进程 ID 号是 3827、3919、10764 和 11679。所以要发送结束进程信号,我们输入以下命令:
1 | kill -9 3827 |
一旦我们输入了以上命令,Chrome 浏览器的所有进程将会成功被杀死。
我们有更简单的方法!如果我们已经知道我们想要杀死的那个进程的名字,我们能够利用 killall 命令发送同样的信号,像这样:
1 | killall -9 chrome |
附带说明的是,上边这个命令可能不能捕捉到所有正在运行的 Chrome 进程。如果,运行了上边这个命令之后,你输入 ps aux | grep chrome 命令过滤一下,看到剩下正在运行的 Chrome 进程有那些,最好的办法还是回到 kIll 命令通过进程 ID 来发送信号值 9 来结束这个进程。
正如你看到的,杀死错误的进程并没有你原本想的那样有挑战性。当我让一个顽固的进程结束的时候,我趋向于用 killall命令来作为有效的方法来终止,然而,当我让一个真正的活跃的进程结束的时候,kill命令是一个好的方法。
根据上面的kill停止,大家基本上了解了信号的概念,那么这些信号是怎么产生的呢?每个信号有有什么用呢?这里详细和大家聊聊。
linux中信号,编号为1 ~ 31的信号为传统UNIX支持的信号,是不可靠信号(非实时的),编号为32 ~ 63的信号是后来扩充的,称做可靠信号(实时信号)。
不可靠信号和可靠信号的区别在于前者不支持排队,可能会造成信号丢失,而后者不会。
下面我们对编号小于SIGRTMIN的信号进行讨论。
1) SIGHUP 该信号在用户终端连接(正常或非正常)结束时发出, 通常是在终端的控制进程结束时, 通知同一session内的各个作业, 这时它们与控制终端不再关联。 当登录Linux时,系统会分配给登录用户一个终端(Session)。在这个终端运行的所有程序,包括前台进程组和后台进程组,一般都属于这个Session。当用户退出Linux登录时,前台进程组和后台有对终端输出的进程将会收到SIGHUP信号。这个信号的默认操作为终止进程,因此前台进程组和后台有终端输出的进程就会中止。不过可以捕获这个信号,比如wget能捕获SIGHUP信号,并忽略它,这样就算退出了Linux登录,wget也能继续下载。 此外,对于与终端脱离关系的守护进程,这个信号用于通知它重新读取配置文件。
2) SIGINT 程序终止(interrupt)信号, 在用户键入INTR字符(通常是Ctrl-C)时发出,用于通知前台进程组终止进程。
3) SIGQUIT 和SIGINT类似, 但由QUIT字符(通常是Ctrl-)来控制. 进程在因收到SIGQUIT退出时会产生core文件, 在这个意义上类似于一个程序错误信号。
4) SIGILL 执行了非法指令. 通常是因为可执行文件本身出现错误, 或者试图执行数据段. 堆栈溢出时也有可能产生这个信号。
5) SIGTRAP 由断点指令或其它trap指令产生. 由debugger使用。
6) SIGABRT 调用abort函数生成的信号。
7) SIGBUS 非法地址, 包括内存地址对齐(alignment)出错。比如访问一个四个字长的整数, 但其地址不是4的倍数。它与SIGSEGV的区别在于后者是由于对合法存储地址的非法访问触发的(如访问不属于自己存储空间或只读存储空间)。
8) SIGFPE 在发生致命的算术运算错误时发出. 不仅包括浮点运算错误, 还包括溢出及除数为0等其它所有的算术的错误。
9) SIGKILL 用来立即结束程序的运行. 本信号不能被阻塞、处理和忽略。如果管理员发现某个进程终止不了,可尝试发送这个信号。
10) SIGUSR1 留给用户使用
11) SIGSEGV 试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据.
12) SIGUSR2 留给用户使用
13) SIGPIPE 管道破裂。这个信号通常在进程间通信产生,比如采用FIFO(管道)通信的两个进程,读管道没打开或者意外终止就往管道写,写进程会收到SIGPIPE信号。此外用Socket通信的两个进程,写进程在写Socket的时候,读进程已经终止。
14) SIGALRM 时钟定时信号, 计算的是实际的时间或时钟时间. alarm函数使用该信号.
15) SIGTERM 程序结束(terminate)信号, 与SIGKILL不同的是该信号可以被阻塞和处理。通常用来要求程序自己正常退出,shell命令kill缺省产生这个信号。如果进程终止不了,我们才会尝试SIGKILL。
17) SIGCHLD 子进程结束时, 父进程会收到这个信号。 如果父进程没有处理这个信号,也没有等待(wait)子进程,子进程虽然终止,但是还会在内核进程表中占有表项,这时的子进程称为僵尸进程。这种情况我们应该避免(父进程或者忽略SIGCHILD信号,或者捕捉它,或者wait它派生的子进程,或者父进程先终止,这时子进程的终止自动由init进程来接管)。
18) SIGCONT 让一个停止(stopped)的进程继续执行. 本信号不能被阻塞. 可以用一个handler来让程序在由stopped状态变为继续执行时完成特定的工作. 例如, 重新显示提示符
19) SIGSTOP 停止(stopped)进程的执行. 注意它和terminate以及interrupt的区别:该进程还未结束, 只是暂停执行. 本信号不能被阻塞, 处理或忽略.
20) SIGTSTP 停止进程的运行, 但该信号可以被处理和忽略. 用户键入SUSP字符时(通常是Ctrl-Z)发出这个信号
21) SIGTTIN 当后台作业要从用户终端读数据时, 该作业中的所有进程会收到SIGTTIN信号. 缺省时这些进程会停止执行.
22) SIGTTOU 类似于SIGTTIN, 但在写终端(或修改终端模式)时收到.
23) SIGURG 有”紧急”数据或out-of-band数据到达socket时产生.
24) SIGXCPU 超过CPU时间资源限制. 这个限制可以由getrlimit/setrlimit来读取/改变。
25) SIGXFSZ 当进程企图扩大文件以至于超过文件大小资源限制。
26) SIGVTALRM 虚拟时钟信号. 类似于SIGALRM, 但是计算的是该进程占用的CPU时间.
27) SIGPROF 类似于SIGALRM/SIGVTALRM, 但包括该进程用的CPU时间以及系统调用的时间.
28) SIGWINCH 窗口大小改变时发出.
29) SIGIO 文件描述符准备就绪, 可以开始进行输入/输出操作.
30) SIGPWR Power failure
31) SIGSYS 非法的系统调用。
在以上列出的信号中
程序不可捕获、阻塞或忽略的信号有:SIGKILL,SIGSTOP
不能恢复至默认动作的信号有:SIGILL,SIGTRAP
默认会导致进程流产的信号有:SIGABRT,SIGBUS,SIGFPE,SIGILL,SIGIOT,SIGQUIT,SIGSEGV,SIGTRAP,SIGXCPU,SIGXFSZ
默认会导致进程退出的信号有:SIGALRM,SIGHUP,SIGINT,SIGKILL,SIGPIPE,SIGPOLL,SIGPROF,SIGSYS,SIGTERM,SIGUSR1,SIGUSR2,SIGVTALRM 默认会导致进程停止的信号有:SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU
默认进程忽略的信号有:SIGCHLD,SIGPWR,SIGURG,SIGWINCH
此外,SIGIO在SVR4是退出,在4.3BSD中是忽略;SIGCONT在进程挂起时是继续,否则是忽略,不能被阻塞
]]>很多朋友不清楚linux如何禁止普通用户su到root,这里需要修改两个配置文件,具体详细配置大家通过本文了解下吧
为禁止普通用户su至root,需要分别修改/etc/pam.d/su和/etc/login.defs两个配置文件。
(1)去除/etc/pam.d/su文件中如下行的注释:
1 | #auth required pam_wheel.so use_uid |
(2)在/etc/login.defs文件中加入如下配置项:
1 | SU_WHEEL_ONLY yes |
经过上述配置后,普通用户将被禁止su至root,如果希望指定普通用户su至root,可以执行如下命令将该用户添加至wheel组中:
1 | usermod -G wheel username |
1 | [root@titan ~]# id apple |
验证apple
1 | [apple@titan ~]$ su - root |
验证banana
1 | [banana@titan ~]$ su - root |
以上所述是站长给大家介绍的Linux禁止普通用户su至root的解决方法,希望对大家有所帮助,如果大家有任何疑问请给我留言,站长会及时回复大家的。
]]>现在系统功能越来越丰富,那么响应的开发资源需要的越来越多,文件存储也越来越必要。无论是你用nfs、glusterfs等等,都需要在linux服务器中设置挂载点并执行挂载后才可使用,但如果fs文件系统有调整,那么可能就需要卸载umount,重新挂载,但是你真的可以顺顺利利的卸载吗?不见得,因为可能有应用在占用该磁盘或者系统在fstab中写入了磁盘自动挂载,本文就详细给你介绍个小技巧,帮你解决该烦恼。
但出现这种情况时,可以根据提示用lsof 或fuser来判断有哪些进程正在占用该磁盘,停掉改进程,重新挂载后再重新启动进程应用即可。
可以根据图上看到,找到了进程16011占用了该文件系统,并可以准确看到该进程是哪些应用,并占用了哪些文件等信息。
科普
1 | fuser -m -v /mnt |
可以查看到当前占用/mnt目录的进程号,然后用kill杀死它。
也可以直接杀死这个进程
1 | fuser -m -k /mnt |
如果你不是很明确是否要杀死所有霸占设备的程序,你还可以加一个 -i 参数,这样每杀死一个程序前,都会询问,加参数-i
1 | fuser -m -v -i -k /mnt |
-m : 表明指定的路径是一个挂载点显示所有使用指定文件系统的进程。后面可以跟挂载点或dev设备
-v : 给出详细的输出。可以给出了占用磁盘程序的详细信息,如进程号等。
应用程序占用可以根据场景一操作拿到进程id,你就可以对它为所欲为了。但是场景一图中有一个隐藏的信息,可能有同学已经发现
PID: kernel这个是内核占用着该磁盘,要怎么去杀掉呢。这又是怎么造成的呢?
出现这种情况是因为在linux系统fstab中添加的文件磁盘,那么在系统启动时,内核自动挂载该磁盘,所有就是内核进程。
那有办法解决吗?答案是肯定的,要不就不会有本教程了。
方法一
既然内核占有,那么先把fstab中fs挂载点删掉,并重启服务器,那么重启时内核重新加载,就不会再占用了。
但是对于生产环境,业务应用在线上跑,有没有不用重启,还能解决挂载问题的呢?
方法二
lazy umount法,使用如下命令和参数:
1 | umount -l /mnt |
–l :并不是马上umount,而是在该目录空闲后再umount。
请注意,该方法并不是完全安全的,它主要完成如下操作:
1,立即从目录结构中实现卸载,即新进程将无法通过/media/disk访问,该磁盘。
2,正在访问该文件系统的程序不受影响。即正在操作/media/disk的进程不会被打断,且仍可以读写磁盘中的所有文件。如果所有进程对/media/disk的操作都执行完,那么才真正地umount。
由此可知,lazy umount并没有真正实现umount,仅用于特殊需要的情况。
被应用程序占用,找到进程号,停掉应用解除占用就可卸载。如果是内核占用,可以重启或用lazy umount来解决。但都有优劣点,需要自行把握。
]]>今天要给大家介绍的一个类Unix下的一个网络数据采集分析工具 – Tcpdump,也就是我们常说的抓包工具。与它功能类似的工具有 wireshark。不同的是wireshark有图形化界面,而tcpdump 则只有命令行。
作为一个运维,经常和服务器打交道,但服务器追求性能很少安装图形界面,因此直接跳过wireshark,直接给大家介绍这个tcpdump神器。
这篇文章借助于很多帮助文档,终于把tcpdump的用法全部研究了个遍。毫不夸张的说,应该可以算是中文里把 tcpdump 讲得最清楚明白,并且最全的文章了。所以本文值得你收藏分享,就怕你错过了,就再也找不到像这样把 tcpdump 讲得直白而且特全的文章了。
操作系统:CentOS 7.2
tcpdump版本:v4.5.1
tcpdump核心参数图解
大家都知道,网络上的流量、数据包非常的多,因此要想抓到我们所需要的数据包,就需要我们定义一个精准的过滤器,把这些目标数据包,从巨大的数据包网络中抓取出来。
所以学习抓包工具,其实就是学习如何定义过滤器的过程。
而在 tcpdump 的世界里,过滤器的实现,都是通过一个又一个的参数组合起来,一个参数不够精准,那就再加一个,直到我们能过滤掉无用的数据包,只留下我们感兴趣的数据包。
tcpdump 的参数非常的多,初学者在没有掌握 tcpdump 时,会对这个命令的众多参数产生很多的疑惑。
就比如下面这个命令,我们要通过 host 参数指定 host ip 进行过滤
1 | $ tcpdump host 192.168.10.100 |
主程序 + 参数名+ 参数值 这样的组合才是我们正常认知里面命令行该有的样子。
可 tcpdump 却不走寻常路,我们居然还可以在 host 前再加一个限定词,来缩小过滤的范围?
1 | $ tcpdump src host 192.168.10.100 |
从字面上理解,确实很容易理解,但是这不符合编写命令行程序的正常逻辑,导致我们会有所疑虑:
除 src ,dst 还有其它可以的限定词?src,host 应该如何理解它们,叫参数名?不合适,因为 src 明显不合适。如果你在网上看到有关 tcpdump 的博客、教程,无一不是给你一个参数组合,告诉你这是实现了怎样的一个过滤器?这样的教学方式,很容易让你依赖别人的文章来使用 tcpdump,而不能将 tcpdump 这样神器消化,并达到灵活应用,灵活搭配过滤器的效果。
上面加了 src 本身就颠覆了我们的认知,你可知道在 src 之前还可以加更多的条件,比如 tcp, udp, icmp 等词,在你之前的基础上再过滤一层。如下:
1 | $ tcpdump tcp src host 192.168.10.100 |
这种参数的不确定性,让大多数人对 tcpdump 的学习始终无法得其精髓。
因此,在学习 tcpdump 之前,我觉得有必要要先让你知道:tcpdump 的参数是如何组成的?这非常重要。
为此画了一张图,方便你直观的理解 tcpdump 的各种参数:
proto、type、direction 这三类过滤器的内容比较简单,也最常用,因此我将其放在最前面,也就是 第三章:常规过滤规则一起介绍。
而 option 可选的参数非常多,有的甚至也不经常用到,因此我将其放到后面一点,也就是 第四章:可选参数解析
当你看完前面六章,你对 tcpdump 的认识会上了一个台阶,至少能够满足你 80% 的使用需求。
你一定会问了,还有 20% 呢?
其实 tcpdump 还有一些过滤关键词,它不符合以上四种过滤规则,可能需要你单独记忆。关于这部分我会在 第六章:特殊过滤规则 里进行介绍。
理解 tcpdump 的输出
2.1 输出内容结构
tcpdump 输出的内容虽然多,却很规律。
这里以我随便抓取的一个 tcp 包为例来看一下
1 | 21:26:49.013621 IP 172.20.20.1.15605 > 172.20.20.2.5920: Flags [P.], seq 49:97, ack 106048, win 4723, length 48 |
从上面的输出来看,可以总结出:
2.2 Flags 标识符
使用 tcpdump 抓包后,会遇到的 TCP 报文 Flags,有以下几种:
常规过滤规则
3.1 基于IP地址过滤:host
使用 host 就可以指定 host ip 进行过滤
1 | $ tcpdump host 192.168.10.100 |
数据包的 ip 可以再细分为源ip和目标ip两种
1 | # 根据源ip进行过滤 |
3.2 基于网段进行过滤:net
若你的ip范围是一个网段,可以直接这样指定
1 | $ tcpdump net 192.168.10.0/24 |
网段同样可以再细分为源网段和目标网段
1 | # 根据源网段进行过滤 |
3.3 基于端口进行过滤:port
使用 port 就可以指定特定端口进行过滤
1 | $ tcpdump port 8088 |
端口同样可以再细分为源端口,目标端口
1 | # 根据源端口进行过滤 |
如果你想要同时指定两个端口你可以这样写
1 | $ tcpdump port 80 or port 8088 |
但也可以简写成这样
1 | $ tcpdump port 80 or 8088 |
如果你的想抓取的不再是一两个端口,而是一个范围,一个一个指定就非常麻烦了,此时你可以这样指定一个端口段。
1 | $ tcpdump portrange 8000-8080 |
对于一些常见协议的默认端口,我们还可以直接使用协议名,而不用具体的端口号。如http=80,https = 443 等
1 | $ tcpdump tcp port http |
3.4 基于协议进行过滤:proto
常见的网络协议有:tcp, udp, icmp, http, ip,ipv6 等
若你只想查看 icmp 的包,可以直接这样写
1 | $ tcpdump icmp |
3.5 基本IP协议的版本进行过滤
当你想查看 tcp 的包,你也许会这样写
1 | $ tcpdump tcp |
这样写也没问题,就是不够精准,为什么这么说呢?
ip 根据版本的不同,可以再细分为 IPv4 和 IPv6 两种,如果你只指定了 tcp,这两种其实都会包含在内。
那有什么办法,能够将 IPv4 和 IPv6 区分开来呢?
很简单,如果是 IPv4 的 tcp 包 ,就这样写(友情提示:数字 6 表示的是 tcp 在ip报文中的编号。)
1 | $ tcpdump 'ip proto tcp' |
而如果是 IPv6 的 tcp 包 ,就这样写
1 | $ tcpdump 'ip6 proto tcp' |
关于上面这几个命令示例,有两点需要注意:
跟在 proto 和 protochain 后面的如果是 tcp, udp, icmp ,那么过滤器需要用引号包含,这是因为 tcp,udp, icmp 是 tcpdump 的关键字。跟在ip 和 ip6 关键字后面的 proto 和 protochain 是两个新面孔,看起来用法类似,它们是否等价,又有什么区别呢?关于第二点,网络上没有找到很具体的答案,我只能通过 man tcpdump 的提示, 给出自己的个人猜测,但不保证正确。
proto 后面跟的
而 protochain 后面跟的 protocol 要求就没有那么严格,它可以是任意词,只要 tcpdump 的 IP 报文头部里的 protocol 字段为
理论上来讲,下面两种写法效果是一样的
1 | $ tcpdump 'ip && tcp'$ tcpdump 'ip proto tcp' |
同样的,这两种写法也是一样的
1 | $ tcpdump 'ip6 && tcp'$ tcpdump 'ip6 proto tcp' |
可选参数解析
4.1 设置不解析域名提升速度
4.2 过滤结果输出到文件
使用 tcpdump 工具抓到包后,往往需要再借助其他的工具进行分析,比如常见的 wireshark 。
而要使用wireshark ,我们得将 tcpdump 抓到的包数据生成到文件中,最后再使用 wireshark 打开它即可。
使用 -w 参数后接一个以 .pcap 后缀命令的文件名,就可以将 tcpdump 抓到的数据保存到文件中。
1 | $ tcpdump icmp -w icmp.pcap |
4.3 从文件中读取包数据
使用 -w 是写入数据到文件,而使用 -r 是从文件中读取数据。
读取后,我们照样可以使用上述的过滤器语法进行过滤分析。
1 | $ tcpdump icmp -r all.pcap |
4.4 控制详细内容的输出
4.5 控制时间的显示
4.6 显示数据包的头部
4.7 过滤指定网卡的数据包
-i:指定要过滤的网卡接口,如果要查看所有网卡,可以 -i any
4.8 过滤特定流向的数据包
-Q:选择是入方向还是出方向的数据包,可选项有:in, out, inout,也可以使用 –direction=[direction] 这种写法4.9 其他常用的一些参数
4.10 对输出内容进行控制的参数
过滤规则组合
有编程基础的同学,对于下面三个逻辑运算符应该不陌生了吧
1 | $ tcpdump src 10.5.2.3 and dst port 3389 |
当你在使用多个过滤器进行组合时,有可能需要用到括号,而括号在 shell 中是特殊符号,因为你需要使用引号将其包含。例子如下:
1 | $ tcpdump 'src 10.0.2.4 and (dst port 3389 or 22)' |
而在单个过滤器里,常常会判断一条件是否成立,这时候,就要使用下面两个符号
当你使用这两个符号时,tcpdump 还提供了一些关键字的接口来方便我们进行判断,比如
比如我现在要过滤来自进程名为 nc 发出的流经 en0 网卡的数据包,或者不流经 en0 的入方向数据包,可以这样子写
1 | $ tcpdump "( if=en0 and proc =nc ) || (if != en0 and dir=in)" |
特殊过滤规则
6.1 根据 tcpflags 进行过滤
通过上一篇文章,我们知道了 tcp 的首部有一个标志位。
TCP 报文首部
tcpdump 支持我们根据数据包的标志位进行过滤
proto [ expr:size ]
接下来,我将举几个例子,让人明白它的写法,不过在那之前,有几个点需要你明白,这在后面的例子中会用到:
1、tcpflags 可以理解为是一个别名常量,相当于 13,它代表着与指定的协议头开头相关的字节偏移量,也就是标志位,所以 tcp[tcpflags] 等价于 tcp[13] ,对应下图中的报文位置。
2、tcp-fin, tcp-syn, tcp-rst, tcp-push, tcp-ack, tcp-urg 这些同样可以理解为别名常量,分别代表 1,2,4,8,16,32,64。这些数字是如何计算出来的呢?
以 tcp-syn 为例,你可以参照下面这张图,计算出来的值 是就是 2
由于数字不好记忆,所以一般使用这样的“别名常量”表示。
因此当下面这个表达式成立时,就代表这个包是一个 syn 包。
tcp[tcpflags] == tcp-syn
要抓取特定数据包,方法有很多种。
下面以最常见的 syn包为例,演示一下如何用 tcpdump 抓取到 syn 包,而其他的类型的包也是同样的道理。
据我总结,主要有三种写法:
1、第一种写法:使用数字表示偏移量
1 | $ tcpdump -i eth0 "tcp[13] & 2 != 0" |
2、第二种写法:使用别名常量表示偏移量
1 | $ tcpdump -i eth0 "tcp[tcpflags] & tcp-syn != 0" |
3、第三种写法:使用混合写法
1 | $ tcpdump -i eth0 "tcp[tcpflags] & 2 != 0"# or$ tcpdump -i eth0 "tcp[13] & tcp-syn != 0" |
如果我想同时捕获多种类型的包呢,比如 syn + ack 包
1、第一种写法
1 | $ tcpdump -i eth0 'tcp[13] == 2 or tcp[13] == 16' |
2、第二种写法
1 | $ tcpdump -i eth0 'tcp[tcpflags] == tcp-syn or tcp[tcpflags] == tcp-ack' |
3、第三种写法
1 | $ tcpdump -i eth0 "tcp[tcpflags] & (tcp-syn|tcp-ack) != 0" |
4、第四种写法:注意这里是 单个等号,而不是像上面一样两个等号,18(syn+ack) = 2(syn) + 16(ack)
1 | $ tcpdump -i eth0 'tcp[13] = 18'# or$ tcpdump -i eth0 'tcp[tcpflags] = 18' |
tcp 中有 类似 tcp-syn 的别名常量,其他协议也是有的,比如 icmp 协议,可以使用的别名常量有
icmp-echoreply, icmp-unreach, icmp-sourcequench, icmp-redirect, icmp-echo, icmp-routeradvert,icmp-routersolicit, icmp-timx-ceed, icmp-paramprob, icmp-tstamp, icmp-tstampreply,icmp-ireq, icmp-ireqreply, icmp-maskreq, icmp-maskreply
5.2 基于包大小进行过滤
若你想查看指定大小的数据包,也是可以的
1 | $ tcpdump less 32 $ tcpdump greater 64 $ tcpdump <= 128 |
5.3 根据 mac 地址进行过滤
例子如下,其中 ehost 是记录在 /etc/ethers 里的 name
1 | $ tcpdump ether host [ehost]$ tcpdump ether dst [ehost]$ tcpdump ether src [ehost] |
5.4 过滤通过指定网关的数据包
1 | $ tcpdump gateway [host] |
5.5 过滤广播/多播数据包
1 | $ tcpdump ether broadcast$ tcpdump ether multicast$ tcpdump ip broadcast$ tcpdump ip multicast$ tcpdump ip6 multicast |
如何抓取到更精准的包?
先给你抛出一个问题:如果我只想抓取 HTTP 的 POST 请求该如何写呢?
如果只学习了上面的内容,恐怕你还是无法写法满足这个抓取需求的过滤器。
在学习之前,我先给出答案,然后再剖析一下,这个过滤器是如何生效的,居然能让我们对包内的内容进行判断。
1 | $ tcpdump -s 0 -A -vv 'tcp[((tcp[12:1] & 0xf0) >> 2):4]' |
命令里的可选参数,在前面的内容里已经详细讲过了。这里不再细讲。
本节的重点是引号里的内容,看起来很复杂的样子。
将它逐一分解,我们只要先理解了下面几种用法,就能明白
&:是位运算里的 and 操作符,比如 0011 & 0010 = 0010
>>:是位运算里的右移操作,比如 0111 >> 2 = 0011
0xf0:是 10 进制的 240 的 16 进制表示,但对于位操作来说,10进制和16进制都将毫无意义,我们需要的是二进制,将其转换成二进制后是:11110000,这个数有什么特点呢?前面个 4bit 全部是 1,后面4个bit全部是0,往后看你就知道这个特点有什么用了。
分解完后,再慢慢合并起来看
1、tcp[12:1] & 0xf0 其实并不直观,但是我们将它换一种写法,就好看多了,假设 tcp 报文中的 第12 个字节是这样组成的 10110000,那么这个表达式就可以变成 10110110 && 11110000 = 10110000,得到了 10110000 后,再进入下一步。
2、tcp[12:1] & 0xf0) >> 2 :如果你不理解 tcp 报文首部里的数据偏移,请先点击这个前往我的上一篇文章,搞懂数据偏移的意义,否则我保证你这里会绝对会听懵了。
tcp[12:1] & 0xf0) >> 2 这个表达式实际是 (tcp[12:1] & 0xf0) >> 4 ) << 2 的简写形式。所以要搞懂 tcp[12:1] & 0xf0) >> 2 只要理解了(tcp[12:1] & 0xf0) >> 4 ) << 2 就行了 。
从上一步我们算出了 tcp[12:1] & 0xf0 的值其实是一个字节,也就是 8 个bit,但是你再回去看下上面的 tcp 报文首部结构图,表示数据偏移量的只有 4个bit,也就是说 上面得到的值 10110000,前面 4 位(1011)才是正确的偏移量,那么为了得到 1011,只需要将 10110000 右移4位即可,也就是 tcp[12:1] & 0xf0) >> 4,至此我们是不是已经得出了实际数据的正确位置呢,很遗憾还没有,前一篇文章里我们讲到 Data Offset 的单位是 4个字节,因为要将 1011 乘以 4才可以,除以4在位运算中相当于左移2位,也就是 <<2,与前面的 >>4 结合起来一起算的话,最终的运算可以简化为 >>2
至此,我们终于得出了实际数据开始的位置是 tcp[12:1] & 0xf0) >> 2 (单位是字节)。
找到了数据的起点后,可别忘了我们的目的是从数据中打到 HTTP 请求的方法,是 GET 呢 还是 POST ,或者是其他的?
有了上面的经验,我们自然懂得使用 tcp[((tcp[12:1] & 0xf0) >> 2):4] 从数据开始的位置再取出四个字节,然后将结果与 GET (注意 GET最后还有个空格)的 16进制写法(也就是 0x47455420)进行比对。
0x47 –> 71 –> G0x45 –> 69 –> E0x54 –> 84 –> T0x20 –> 32 –> 空格
如果相等,则该表达式为True,tcpdump 认为这就是我们所需要抓的数据包,将其输出到我们的终端屏幕上。
抓包实战应用例子
以下例子摘自:https://fuckcloudnative.io/posts/tcpdump-examples/
8.1 提取 HTTP 的 User-Agent
从 HTTP 请求头中提取 HTTP 用户代理:
1 | $ tcpdump -nn -A -s1500 -l | grep "User-Agent:" |
通过 egrep 可以同时提取用户代理和主机名(或其他头文件):
1 | $ tcpdump -nn -A -s1500 -l | egrep -i 'User-Agent:|Host:' |
8.2 抓取 HTTP GET 和 POST 请求
抓取 HTTP GET 请求包:
1 | $ tcpdump -s 0 -A -vv 'tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x47455420'# or$ tcpdump -vvAls0 | grep 'GET' |
可以抓取 HTTP POST 请求包:
1 | $ tcpdump -s 0 -A -vv 'tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x504f5354'# or $ tcpdump -vvAls0 | grep 'POST' |
注意:该方法不能保证抓取到 HTTP POST 有效数据流量,因为一个 POST 请求会被分割为多个 TCP 数据包。
8.3 找出发包数最多的 IP
找出一段时间内发包最多的 IP,或者从一堆报文中找出发包最多的 IP,可以使用下面的命令:
1 | $ tcpdump -nnn -t -c 200 | cut -f 1,2,3,4 -d '.' | sort | uniq -c | sort -nr | head -n 20 |
8.4 抓取 DNS 请求和响应
DNS 的默认端口是 53,因此可以通过端口进行过滤
1 | $ tcpdump -i any -s0 port 53 |
8.5 切割 pcap 文件
当抓取大量数据并写入文件时,可以自动切割为多个大小相同的文件。例如,下面的命令表示每 3600 秒创建一个新文件 capture-(hour).pcap,每个文件大小不超过 200*1000000 字节:
1 | $ tcpdump -w /tmp/capture-%H.pcap -G 3600 -C 200 |
这些文件的命名为 capture-{1-24}.pcap,24 小时之后,之前的文件就会被覆盖。
8.6 提取 HTTP POST 请求中的密码
从 HTTP POST 请求中提取密码和主机名:
1 | $ tcpdump -s 0 -A -n -l | egrep -i "POST /|pwd=|passwd=|password=|Host:" |
8.7 提取 HTTP 请求的 URL
提取 HTTP 请求的主机名和路径:
1 | $ tcpdump -s 0 -v -n -l | egrep -i "POST /|GET /|Host:" |
8.8 抓取 HTTP 有效数据包
抓取 80 端口的 HTTP 有效数据包,排除 TCP 连接建立过程的数据包(SYN / FIN / ACK):
1 | $ tcpdump 'tcp port 80 and (((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0)' |
8.9 结合 Wireshark 进行分析
通常 Wireshark(或 tshark)比 tcpdump 更容易分析应用层协议。一般的做法是在远程服务器上先使用 tcpdump 抓取数据并写入文件,然后再将文件拷贝到本地工作站上用 Wireshark 分析。
还有一种更高效的方法,可以通过 ssh 连接将抓取到的数据实时发送给 Wireshark 进行分析。以 MacOS 系统为例,可以通过 brew cask install wireshark 来安装,然后通过下面的命令来分析:
1 | $ ssh root@remotesystem 'tcpdump -s0 -c 1000 -nn -w - not port 22' | /Applications/Wireshark.app/Contents/MacOS/Wireshark -k -i - |
例如,如果想分析 DNS 协议,可以使用下面的命令:
1 | $ ssh root@remotesystem 'tcpdump -s0 -c 1000 -nn -w - port 53' | /Applications/Wireshark.app/Contents/MacOS/Wireshark -k -i - |
抓取到的数据:
-c 选项用来限制抓取数据的大小。如果不限制大小,就只能通过 ctrl-c 来停止抓取,这样一来不仅关闭了 tcpdump,也关闭了 wireshark。
到这里,我已经将我所知道的 tcpdump 的用法全部说了一遍,如果你有认真地看完本文,相信会有不小的收获,掌握一个上手的抓包工具,对于以后我们学习网络、分析网络协议、以及定位网络问题,会很有帮助,而 tcpdump 是我推荐的一个抓包工具。
]]>前面文章对数据库中间层进行了选型,那么要怎么安装,怎么验证,怎么优化,又有哪些坑可以避免呢?本文就详细介绍下。
1 | 1) 采用yum方式安装 |
ProxySQL有配置文件/etc/proxysql.cnf和配置数据库文件/var/lib/proxysql/proxysql.db。这里需要特别注意:如果存在如果存在”proxysql.db”文件(在/var/lib/proxysql目录下),则ProxySQL服务只有在第一次启动时才会去读取proxysql.cnf文件并解析;后面启动会就不会读取proxysql.cnf文件了!如果想要让proxysql.cnf文件里的配置在重启proxysql服务后生效(即想要让proxysql重启时读取并解析proxysql.cnf配置文件),则需要先删除/var/lib/proxysql/proxysql.db数据库文件,然后再重启proxysql服务。这样就相当于初始化启动proxysql服务了,会再次生产一个纯净的proxysql.db数据库文件(如果之前配置了proxysql相关路由规则等,则就会被抹掉)。 官方推荐用admin interface方式!(即在proxysql本机使用mysql客户端连接管理端口)
1 | [root@mysql-proxy ~]# egrep -v "^#|^$" /etc/proxysql.cnf |
proxysql的6032端口是管理入口,账号密码是admin(可以动态修改),允许客户端连接;6033端口就是客户端入口,账号密码通过管理接口去设置。在proxysql本机使用mysql客户端连接到ProxySQL的管理接口(admin interface), 该接口的默认管理员用户和密码都是admin。
mysql_ifaces
也就是说proxysql有一个admin接口专门来做配置,相当于一个mysql shell可以通过sql来让配置实时生效。
mysql_ifaces配置了允许连接proxysql的ip和port
1 | [root@mysql-proxy ~]# vim /etc/proxysql.cnf |
如果ip配置为0.0.0.0表示不限制ip,但是出于安全考虑,admin用户无论怎么设置都只能在本机登录!!!
admin_credentials
这个key保存所有可以操作proxysql的用户名和密码,格式为:user:pass;user1:pass1,这里可以修改密码或定义一个非admin的用户用于远程登录。 前提是保证想要管理proxysql的机器安装有mysql client客户端!
1 | 先在proxysql本机登录 (因为初始账号密码是admin:admin,只能在本机登录), 这里的proxysql本机地址是172.16.60.214 |
ProxySQL的库、表说明 (默认管理端口是6032,客户端服务端口是6033。默认的用户名密码都是 admin)
1 | 通过管理端口6032去连接的 (注意, 下面连接命令中后面的--prompt 'admin'字段可以不加,也是可以登录进去的) |
global_variables 有80多个变量可以设置,其中就包括监听的端口、管理账号、禁用monitor等
1 | (admin@127.0.0.1:6032) [(none)]> show tables; |
- hostgroup_id: ProxySQL通过 hostgroup (下称HG) 的形式组织后端db实例。一个 HG 代表同属于一个角色
- 该表的主键是 (hostgroup_id, hostname, port),可以看到一个 hostname:port 可以在多个hostgroup里面,如上面的 10.0.100.100:3307,这样可以避免 HG 1000 的从库全都不可用时,依然可以把读请求发到主库上。
- 一个 HG 可以有多个实例,即多个从库,可以通过 weight 分配权重
- hostgroup_id 0 是一个特殊的HG,路由查询的时候,没有匹配到规则则默认选择 HG 0
- status:
- ONLINE: 当前后端实例状态正常
- SHUNNED: 临时被剔除,可能因为后端 too many connections error,或者超过了可容忍延迟阀值 max_replication_lag
- OFFLINE_SOFT: “软离线”状态,不再接受新的连接,但已建立的连接会等待活跃事务完成。
- OFFLINE_HARD: “硬离线”状态,不再接受新的连接,已建立的连接或被强制中断。当后端实例宕机或网络不可达,会出现。
- max_connections: 允许连接到该后端mysql实例的最大连接数。不要大于MySQL设置的 max_connections,如果后端实例 hostname:port 在多个 hostgroup 里,以较大者为准,而不是各自独立允许的最大连接数。
- max_replication_lag: 允许的最大延迟,主库不受这个影响,默认0。如果 > 0, monitor 模块监控主从延迟大于阀值时,会临时把它变为 SHUNNED 。
- max_latency_ms: mysql_ping 响应时间,大于这个阀值会把它从连接池剔除(即使是ONLINE)
- comment: 备注,不建议留空。可以通过它的内容如json格式的数据,配合自己写的check脚本,完成一些自动化的工作。
表 mysql_users
1 | MySQL [(none)]> show create table mysql_users\G; |
- username, password: 连接后端db的用户密码。
这个密码你可以插入明文,也可以插入hash加密后的密文,proxysql会检查你插入的时候密码是否以 * 开头来判断,而且密文要在其它地方使用 PASSWORD()生成。但到 runtime_mysql_users 里,都统一变成了密文,所以可以明文插入,再 SAVE MYSQL USERS TO MEM,此时看到的也是HASH密文。
- active: 是否生效该用户。
- default_hostgroup: 这个用户的请求没有匹配到规则时,默认发到这个 hostgroup,默认0
- default_schema: 这个用户连接时没有指定 database name 时,默认使用的schema
注意表面上看默认为NULL,但实际上受到变量 mysql-default_schema 的影响,默认为 information_schema。关于这个参考我所提的 issue #988
- transaction_persistent: 如果设置为1,连接上ProxySQL的会话后,如果在一个hostgroup上开启了事务,那么后续的sql都继续维持在这个hostgroup上,不伦是否会匹配上其它路由规则,直到事务结束。
虽然默认是0,但我建议还是设成1,虽然一般来说由于前段应用的空值,为0出问题的情况几乎很小。作者也在考虑默认设成 1,refer this issue #793
- frontend, backend: 目前版本这两个都需要使用默认的1,将来有可能会把 Client -> ProxySQL (frontend) 与 ProxySQL -> BackendDB (backend)的认证分开。从 runtime_mysql_users 表内容看到,记录数比 mysql_users 多了一倍,就是把前端认证与后端认证独立出来的结果。
- fast_forward: 忽略查询重写/缓存层,直接把这个用户的请求透传到后端DB。相当于只用它的连接池功能,一般不用,路由规则 .* 就行了。
表 mysql_replication_hostgroups
1 | MySQL [(none)]> show create table mysql_replication_hostgroups\G; |
定义 hostgroup 的主从关系。ProxySQL monitor 模块会监控 HG 后端所有servers 的 read_only
变量,如果发现从库的 read_only 变为0、主库变为1,则认为角色互换了,自动改写 mysql_servers 表里面 hostgroup 关系,达到自动 Failover 效果。
表 mysql_query_rules
mysql_query_rules 是ProxySQL非常核心一个表,定义查询路由规则
1 | MySQL [(none)]> show create table mysql_query_rules\G; |
- rule_id: 表主键,自增。规则处理是以 rule_id 的顺序进行。
- active: 只有 active=1 的规则才会参与匹配。
- username: 如果非 NULL,只有连接用户是 username 的值才会匹配。
- schemaname: 如果非 NULL,只有查询连接使用的db是 schemaname 的值才会匹配。
注意如果是 NULL,不代表连接没有使用schema,而是不伦任何schema都进一步匹配。
- flagIN, flagOUT, apply: 用来定义路由链 chains of rules。
- 首先会检查 flagIN=0 的规则,以rule_id的顺序;如果都没匹配上,则走这个用户的 default_hostgroup。
- 当匹配一条规则后,会检查 flagOUT。
- 如果不为NULL,并且 flagIN != flagOUT ,则进入以flagIN为上一个flagOUT值的新规则链。
- 如果不为NULL,并且 flagIN = flagOUT,则应用这条规则。
- 如果为NULL,或者 apply=1,则结束,应用这条规则。
- 如果最终没有匹配到,则找到这个用户的 default_hostgroup。
- client_addr: 匹配客户端来源IP
- proxy_addr, proxy_port: 匹配本地proxysql的IP、端口。我目前没有想到它的应用场景,可能是把proxysql监听在多个接口上,分发到不同的业务?
- digest: 精确的匹配一类查询。
- match_digest: 正则匹配一类查询。query digest 是指对查询去掉具体值后进行“模糊化”后的查询,类似 pt-fingerprint / pt-query-digest 的效果。
- match_pattern: 正则匹配查询。
以上都是匹配查询的规则,1.3.5版本使用的正则引擎只有 RE2 ,1.4版本可以通过变量 mysql-query_processor_regex 设置 RE2 或者 PCRE,且1.4开始默认是PCRE。
推荐用 match_digest 。关于每条查询都会计算digest对性能的影响,计算query digest确实会有性能损失,但是这却是proxysql里面非常重要的特性,主要是两点:
- proxysql无法知道连接复用(multipexing)是否必须被自动禁用,比如连接里面有variables/tmp tables/lock table等特殊命令,是不能复用的。
- 完整的查询去匹配正则的效率,一般没有参数化后的查询匹配效率高,因为有很长的字符串内容需要处理。再者,SELECT * FROM randomtable WHERE comment LIKE ‘%INTO sbtest1 % FROM sbtest2 %’字符串里有类似这样的语句,很难排除误匹配。
- negate_match_pattern: 反向匹配,相当于对 match_digest/match_pattern 的匹配取反。
- re_modifiers: 修改正则匹配的参数,比如默认的:忽略大小写CASELESS、禁用GLOBAL.
上面都是匹配规则,下面是匹配后的行为
- replace_pattern: 查询重写,默认为空,不rewrite。
- rewrite规则要遵守 RE2::Replace 。
destination_hostgroup: 路由查询到这个 hostgroup。当然如果用户显式 start transaction 且 transaction_persistent=1,那么即使匹配到了,也依然按照事务里第一条sql的路由规则去走。
- cache_ttl: 查询结果缓存的毫秒数。
proxysql这个 Query Cache 与 MySQL 自带的query cache不是同一个。proxysql query cache也不会关心后端数据是否被修改,它所做的就是针对某些特定种类的查询结果进行缓存,比如一些历史数据的count结果。一般不设。
- timeout: 这一类查询执行的最大时间(毫秒),超时则自动kill。
这是对后端DB的保护机制,相当于阿里云RDS loose_max_statement_time 变量的功能,但是注意不同的是,阿里云这个变量的时间时不包括DML操作出现InnoDB行锁等待的时间,而ProxySQL的这个 timeout 是计算从发送sql到等待响应的时间。默认mysql-default_query_timeout给的是 10h .
- retries: 语句在执行时失败时,重试次数。默认由 mysql-query_retries_on_failure变量指定,为1 。
个人建议把它设成0,即不重试。因为执行失败,对select而言很少见,主要是dml,但自己重试对数据不放心。
- delay: 查询延迟执行,这是ProxySQL提供的限流机制,会让其它的查询优先执行。
默认值 mysql-default_query_delay,为0。我们一般不用,其实还是要配合应用端使用,比如这边延迟执行,但上层等待你返回,那前端不就堵住了,没准出现雪崩效应。
- mirror_flagOUT,mirror_hostgroup
这两个高级了,目前这部分文档不全,功能是SQL镜像。顾名思义,就是把匹配到的SQL除了发送到 destination_hostgroup,同时镜像一份到这里的hostgroup,比如我们的测试库。比如这种场景,数据库要从5.6升级到5.7,要验证现有查询语句对5.7的适用情况,就可以把生产流量镜像到5.7新库上验证。
- error_msg: 默认为NULL,如果指定了则这个查询直接被 block 掉,马上返回这个错误信息。
这个功能也很实用,比如线上突然冒出一个 “坏查询”,应用端不方便马上发版解决,我们就可以在这配置一个规则,把查询屏蔽掉,想正常的mysql报错那样抛异常。下一篇文章有演示。
- multiplex: 连接是否复用。
- log: 是否记录查询日志。可以看到log是否记录的对象是根据规则。
要开启日志记录,需要设置变量 mysql-eventslog_filename 来指定文件名,然后这个 log 标记为1。但是目前proxysql记录的日志是二进制格式,需要特定的工具才能读取: eventslog_reader_sample 。这个工具在源码目录 tools下面。
proxysql对后端server健康检查
1 | MySQL [monitor]> show variables like "mysql-monitor%"; |
两种方式,区别在于
1) 一种是在往mysql_servers表中添加server时就为其划分好hostgroup_id(例如0表示写组,1表示读组)
2) 另一种往mysql_servers表中添加server时不区分hostgroup_id(例如全部设为0),然后通过mysql_replication_hostgroups表中的值,
根据proxysql检测到的各server的read_only变量值来自动为后端server设置hostgroup_id
这里强烈推荐用第一种方式
因为第一种是完全由我们控制的;而第二种假如我们误将读server的read_only属性设置为0,则proxysql会将其重新分配到写组,这绝对是不期望的。
ProxySQL下添加与修改配置
1 | 1) 添加配置 |
针对GTID模式的主从同步,另两个从库都要设置read_only=on
接下来通过实战操作来全面了解一下 ProxySQL 的特性和使用场景。
1 | 172.16.60.211 mysql-master 安装Mysql5.7 |
1 | 在三个mysql节点机上使用yum方式安装Mysql5.7,参考:https://www.cnblogs.com/kevingrace/p/8340690.html |
在mysql-master 和 mysql-slave1、mysql-slave2节点上
1 | 1) 主数据库mysql-master (172.16.60.211)的配置操作 |
已经在上面第一步中介绍了安装方法,这里采用rpm包方式安装,安装过程省略……..
向ProxySQL中添加MySQL节点
1 | 使用insert语句添加主机到mysql_servers表中,其中:hostgroup_id 为10表示写组,为20表示读组。 |
监控后端MySQL节点
添加Mysql节点之后,还需要监控这些后端节点。对于后端是主从复制的环境来说,这是必须的,因为ProxySQL需要通过每个节点的read_only值来自动调整
它们是属于读组还是写组。
首先在后端master主数据节点上创建一个用于监控的用户名(只需在master上创建即可,因为会复制到slave上),这个用户名只需具有USAGE权限即可。如果还需
要监控复制结构中slave是否严重延迟于master(这个俗语叫做”拖后腿”,术语叫做”replication lag”),则还需具备replication client权限。
1 | 在mysql-master主数据库节点行执行: |
配置mysql_users
上面的所有配置都是关于后端MySQL节点的,现在可以配置关于SQL语句的,包括:发送SQL语句的用户、SQL语句的路由规则、SQL查询的缓存、SQL语句的重写等等。本小节是SQL请求所使用的用户配置,例如root用户。这要求我们需要先在后端MySQL节点添加好相关用户。这里以root和sqlsender两个用户名为例.
1 | 首先,在mysql-master主数据库节点上执行:(只需master执行即可,会复制给两个slave) |
读写分离:配置路由规则
ProxySQL的路由规则非常灵活,可以基于用户、基于schema以及基于每个语句实现路由规则的定制。本案例作为一个入门配置,实现一个最简单的语句级路由规则,从而实现读写分离。
必须注意: 这只是实验,实际的路由规则绝不应该仅根据所谓的读、写操作进行分离,而是从各项指标中找出压力大、执行频繁的语句单独写规则、做缓存等等。和查询规则有关的表有两个:mysql_query_rules和mysql_query_rules_fast_routing,后者是前者的扩展表,1.4.7之后才支持该快速路由表。本案例只介绍第一个表。插入两个规则,目的是将select语句分离到hostgroup_id=20的读组,但由于select语句中有一个特殊语句SELECT…FOR UPDATE它会申请写锁,所以应该路由到hostgroup_id=10的写组.
1 | [root@mysql-proxy ~]# mysql -uadmin -padmin -P6032 -h127.0.0.1 |
测试读写分离效果
1 | 由于读写操作都记录在proxysql的stats_mysql_query_digest表内。 |
如上已经配置好一主(mysql-master,在hostgroup10写组内)、两从(mysql-slave1和mysql-slave2,在hostgroup20读组内) ,并且已经在”mysql_query_rules”表中配置了路由规则,即写操作转发到hostgroup10组,读操作转发到hostgroup20组.
1 | MySQL [(none)]> select * from mysql_query_rules; |
1 | 首先打开web功能 |
查看web端口是否正常打开
1 | [root@mysql-proxy ~]# lsof -i:6080 |
访问http://172.16.60.214:6080并使用stats:stats登录即可查看一些统计信息。
1 | [root@mysql-proxy ~]# mkdir -p /opt/proxysql/log |
现在微服务几乎成为所有公司的标配,那么业务项目和数据存储的松耦合就成为基本配置,而mysql数据库在互联网公司中应用很广,几乎所有的项目都会有连它的需求。但是如果业务请求量很大,那么最先想到也是最常用的是数据库的读写分离。通常是由dba把数据库分为读写库,对数据进行更新,写入时连接读写库。查询数据时,连接读库。这样可以大大减轻写库的压力。
但是这样是由业务根据需求来区分连哪个数据库,但有些开发说我想只配置一个数据库,运维你根据请求类型来区分定义是连接只读库还是读写库。而且业务对时效性也不是很严格。那要怎么做呢?如下图:
我们就需要增加数据库的代理层,由代理层根据定义的规则来自动区分是连接读写库还是只读库。本文就是聊聊数据库的代理层–ProxySQL。
ProxySQL是灵活强大的MySQL代理层, 是一个能实实在在用在生产环境的MySQL中间件,可以实现读写分离,支持 Query 路由功能,支持动态指定某个 SQL 进行 cache,支持动态加载配置、故障切换和一些 SQL的过滤功能。还有一些同类产品比如 DBproxy、MyCAT、OneProxy 等。但经过反复对比和测试之后,还是觉得ProxySQL是一款性能不谙,靠谱稳定的MySQL 中间件产品 !
ProxySQL是一个高性能的MySQL中间件,拥有强大的规则引擎。它是用C++语言开发的,虽然是一个轻量级产品,但性能很好(据测试,能处理千亿级的数据),功能也足够,能满足中间件所需的绝大多数功能。具有以下特性:
如上可知,ProxySQL集合了很多优秀特性于一身,那么它的缺点呢就是项目不够成熟,好在官方网站一直在及时更新,并且受到 Percona 官方的支持。
ProxySQL有一个完备的配置系统,配置ProxySQL是基于sql命令的方式完成的。ProxySQL支持配置修改之后的在线保存、应用,不需要重启即可生效。整个配置系统分三层设计。
- runtime:运行中使用的配置文件
- memory:提供用户动态修改配置文件
- disk:将修改的配置保存到磁盘SQLit表中(即:proxysql.db)
- config:一般不使用它(即:proxysql.cnf)
如下图所示:
ProxySQL配置系统分为三层的目的:
1) 自动更新;
2) 尽可能的不重启proxysql就可以修改配置;
3) 方便回滚错误配置;
简单说就是配置proxysql分为三个级别,RUNTIME是即时生效的,MEMORY是保存在内存中但并不立即生效的,DISK|CONFIG FILE是持久化或写在配置文件中的。
这三个级别的配置文件互不干扰,在某个层级修改了配置文件,想要加载或保存到另一个层级,需要额外的LOAD或SAVE操作:”LOAD xx_config FROM xx_level | LOAD xx_config TO xx_level | SAVE xx_config TO xx_level | SAVE xx_config FROM xx_level”,达到加载配置或者持久化配置的目的。这三层中每层的功能与含义如下:
- RUNTIME层
代表的是ProxySQL当前生效的配置,包括 global_variables, mysql_servers, mysql_users, mysql_query_rules。无法直接修改这里的配置,必须要从下一层load进来。该层级的配置时在proxysql管理库(sqlite)的main库中以runtime_开头的表,这些表的数据库无法直接修改,只能从其他层级加载;该层代表的是ProxySQL当前生效的正在使用的配置,包括global_variables, mysql_servers, mysql_users, mysql_query_rules表。无法直接修改这里的配置,必须要从下一层load进来。也就是说RUNTIME这个顶级层,是proxysql运行过程中实际使用的那一份配置,这一份配置会直接影响到生产环境的,所以要将配置加载进RUNTIME层时需要三思而行。
- MEMORY层
是平时在mysql命令行修改的 main 里头配置,可以认为是SQLite数据库在内存的镜像。该层级的配置在main库中以mysql_开头的表以及global_variables表,这些表的数据可以直接修改;用户可以通过MySQL客户端连接到此接口(admin接口),然后可以在mysql命令行查询不同的表和数据库,并修改各种配置,可以认为是SQLite数据库在内存的镜像。也就是说MEMORY这个中间层,上面接着生产环境层RUNTIME,下面接着持久化层DISK和CONFIG FILE。MEMORY层是我们修改proxysql的唯一正常入口。一般来说在修改一个配置时,首先修改Memory层,确认无误后再接入RUNTIME层,最后持久化到DISK和CONFIG FILE层。也就是说memeory层里面的配置随便改,不影响生产,也不影响磁盘中保存的数据。通过admin接口可以修改mysql_servers、mysql_users、mysql_query_rules、global_variables等表的数据。
- DISK|CONFIG FILR层
持久存储的那份配置,一般在$(DATADIR)/proxysql.db,在重启的时候会从硬盘里加载。 /etc/proxysql.cnf文件只在第一次初始化的时候用到,完了后,如果要修改监听端口,还是需要在管理命令行里修改,再 save 到硬盘。该层级的配置在磁盘上的sqlite库或配置文件里。DISK/CONFIG FILE层表示持久存储的那份配置,持久层对应的磁盘文件是$(DATADIR)/proxysql.db,在重启ProxySQL的时候,会从proxysql.db文件中加载信息。而 /etc/proxysql.cnf文件只在第一次初始化的时候使用,之后如果要修改配置,就需要在管理端口的SQL命令行里进行修改,然后再save到硬盘。 也就是说DISK和CONFIG FILE这一层是持久化层,我们做的任何配置更改,如果不持久化下来,重启后,配置都将丢失。
需要注意
1) ProxySQL每一个配置项在三层中都存在,但是这三层是互相独立的,也就是说proxysql可以同时拥有三份配置,每层都是独立的,可能三份配置都不一样,也可能三份都一样。
2) RUNTIME层代表 ProxySQL 当前生效的正在使用的配置,无法直接修改这里的配置,必须要从下一层 “load” 进来。
3) MEMORY这一层上面连接 RUNTIME 层,下面连接持久化层。在这层可以正常操作 ProxySQL 配置,随便修改,不会影响生产环境。修改一个配置一般都是先在 MEMORY 层完成,然后确认正常之后再加载到 RUNTIME 和持久化到磁盘上。
4) DISK 和 CONFIG FILE层持久化配置信息,重启后内存中的配置信息会丢失,所以需要将配置信息保留在磁盘中。重启时,可以从磁盘快速加载回来。
ProxySQL配置文件的修改流程一般是:
- 启动时:先修改必要的CONFIG FILE配置,比如管理端口,然后启动;
- 其他配置:修改MEMORY中的表,然后加载到RUNTIME并持久化。
ProxySQL具有一个复杂但易于使用的配置系统,可以满足以下需求:
- 允许轻松动态更新配置(这是为了让ProxySQL用户可以在需要零宕机时间配置的大型基础架构中使用它)。与MySQL兼容的管理界面可用于此目的。
- 允许尽可能多的配置项目动态修改,而不需要重新启动ProxySQL进程
- 可以毫不费力地回滚无效配置
- 这是通过多级配置系统实现的,其中设置从运行时移到内存,并根据需要持久保存到磁盘。
一般,修改的配置都是在memory层。可以load到runtime,使配置在不用重启proxysql的情况下也可以生效,也可以save到disk,将对配置的修改持久化!
需要修改配置时,直接操作的是 MEMORAY,以下命令可用于加载或保存 users (mysql_users): (序号对应上图“运行机制”草图)
1 | [1]: LOAD MYSQL USERS TO RUNTIME / LOAD MYSQL USERS FROM MEMORY #常用。将修改后的配置(在memory层)用到实际生产 |
个人还是比较习惯用 TO,记住往上层是 LOAD,往下层是 SAVE。以下命令加载或保存servers (mysql_servers):
1 | [1]: LOAD MYSQL SERVERS TO RUNTIME #常用,让修改的配置生效 |
后面的使用方法也基本相同,一并列出。以下命令加载或保存query rules (mysql_query_rules):
1 | [1]: load mysql query rules to run #常用 |
以下命令加载或保存 mysql variables (global_variables):
1 | [1]: load mysql variables to runtime |
以下命令加载或保存admin variables (select * from global_variables where variable_name like ‘admin-%’):
1 | [1]: load admin variables to runtime |
ProxySQL启动过程总结:
当proxysql启动时,首先读取配置文件CONFIG FILE(/etc/proxysql.cnf),然后从该配置文件中获取datadir,datadir中配置的是sqlite的数据目录。如果该目录存在,且sqlite数据文件存在,那么正常启动,将sqlite中的配置项读进内存,并且加载进RUNTIME,用于初始化proxysql的运行。如果datadir目录下没有sqlite的数据文件,proxysql就会使用config file中的配置来初始化proxysql,并且将这些配置保存至数据库。sqlite数据文件可以不存在,/etc/proxysql.cnf文件也可以为空,但/etc/proxysql.cnf配置文件必须存在,否则,proxysql无法启动。
今天接了个需求:要把一些资源文件从外网提供给客户下载。处于安全和简单快捷考虑,分享一个快速实现并安全性很强的方案:nginx配置账号密码来控制,并且密码还是加密的,再增加白名单配置。此方案简单快捷和安全。
1 | yum install httpd-tools -y |
设置用户名和密码,并把用户名、密码保存到指定文件中:
1 | [sun@bogon conf]$ sudo mkdir passwd |
注意:上面的 passwd/passwd 是生成密码文件的路径,绝对路径是/etc/nginx/passwd/passwd ,然后sun是用户名,你可以根据需要自行设置成其它用户名。运行命令后,会要求你连续输入两次密码。输入成功后,会提示已经为sun这个用户添加了密码。
查看下生成的密码文件的内容:
1 | [sun@bogon conf]$ cat passwd/passwd |
其中用户名就是sun,分号后面就是密码(已经加过密)。
找到 nginx 配置文件,因为我们要对整个站点开启验证,所以在配置文件中的第一个server修改如下:
1 | server { |
然后nginx重新加载reload:
以上都配置无误后,你重新访问你的站点,如果出现需要身份验证的弹窗就说明修改成功了。
htpasswd命令选项参数说明:
1 | -c 创建一个加密文件 |
利用htpasswd命令添加用户
1 | htpasswd -bc ./.passwd sun pass |
在当前目录下生成一个.passwd文件,用户名sandu,密码:pass,默认采用MD5加密方式
在原有密码文件中增加下一个用户
1 | htpasswd -b ./.passwd sun1 pass |
去掉c选项,即可在第一个用户之后添加第二个用户,依此类推
不更新密码文件,只显示加密后的用户名和密码
1 | htpasswd -nb sun pass |
不更新.passwd文件,只在屏幕上输出用户名和经过加密后的密码
利用htpasswd命令删除用户名和密码
1 | htpasswd -D .passwd sun |
利用 htpasswd 命令修改密码
1 | htpasswd -D .passwd sun |
nginx的使用范围和影响越来越广,很多大厂都在使用,但有些工作多年的同学可能都搞不清楚nginx中location的匹配优先级和匹配顺序是怎样的。今天又有同事不清楚,写配置时总是达不到业务需求,问到我这边帮他搞定了。那么本文就给大家详细聊聊这个问题。
nginx的安装和搭建这里就不再赘述了。无论你是直接命令包库yum或apt-get安装还是下载源码包编译安装等等,看你喜好。
nginx是通过server块中location的配置用来匹配不同url访问:
location配置匹配方式主要包括三种:精准匹配、普通匹配和正则匹配
定义
location = expression 精准匹配
location expression 普通匹配
location ^~ expression 普通匹配
location ~ regex 正则匹配(区分大小写)
location ~* regex 正则匹配(不区分大小写)
要求
精准匹配要求uri与表达式(expression)完全匹配。
普通匹配要求uri与表达式满足前缀匹配。
正则匹配要求uri与正则表达式匹配。
匹配优先级和顺序规则
精准匹配(=) > 普通匹配(^~) > 正则匹配(或*) > 普通匹配(直接目录)
1、首先精准匹配,如能匹配,则进行转发。如未能匹配成功,则进行普通匹配(^)。类型的普通匹配规则进行匹配。如有多条规则均命中,则选择最长匹配。匹配成功后,进行转发。否则,则进行正则匹配。
2、nginx将uri和所有^
3、正则匹配与顺序有关,按编写顺序进行匹配,一旦匹配成功,则转发请求并停止匹配。匹配不成功,则进行普通匹配(location expression )
4、进行普通匹配(location expression),匹配成功则转发,不成功则返回错误码。
glusterfs集群的搭建和使用这里就不再赘述了,可以看以前的教程文档。本文主要聊的是随着服务使用量的增加,那么存储集群势必要扩充空间。服务器迁移,需要先扩容后缩容等等。所以本文的主旨是聊glusterfs集群的横向优化:扩容和缩容。
集群搭建这里忽略
查看glusterfs的节点和客户端挂载情况得知,目前是三个节点的分布式卷。
1 | #查看节点数量 |
创建20个文件
查看文件的分布情况如下:
1 | # 第1台 |
现要对集群进行扩容,增加一个节点 gluster004-hf-aiui.
1 | # 添加一个节点 |
再创建30个文件,如下所示:
1 | root@wyl01:/gsclient# touch {101..130}.txt |
结论:可以看出当扩容后,原先的数据不会均衡到第四台glusterfs上,但是新增加的文件是可以的。
1 | root@wyl01:/gsclient# gluster volume rebalance gv1 start |
可以看到,数据rebalance,第 4 台上的数据明显增加了。
这里有一个需要注意的地方,当数据量太大的时候,对数据进行rebalance必须要考虑的一个问题就是性能,不能因为数据rebalance而影响我们的存储的正常使用。Glusterfs也考虑到了这个问题,在进行数据rebalance时,根据实际场景不同设计了三种不同的“级别”:
lazy:每次仅可以迁移一个文件
normal:默认设置,每次迁移2个文件或者是(CPU逻辑个数-4)/2,哪个大,选哪个
aggressive:每次迁移4个文件或者是(CPU逻辑个数-4)/2
通过以下命令进行配置:
1 | gluster volume set VOLUME-NAME cluster.rebal-throttle [lazy|normal|aggressive] |
如将volume repvol设置为lazy
1 | [root@nwyl01 ~]# gluster volume set gv1 cluster.rebal-throttle lazy |
缩容之前我们先需要将数据迁移到其他的brick上,假设我们移除gluster004-hf-aiui节点
1 | root@wyl01:/gsclient# gluster volume remove-brick gv1 gluster004-hf-aiui:/data help |
移除后,我们看数据的分布情况
1 | # 第 1 台 |
可以看到文件被迁移到其他的brick上了。
1 | root@wyl01:/gsclient# gluster volume info # 卷的基本信息 |
1 | root@wyl01:/gsclient# gluster peer probe 192.168.52.124 |
1 | root@wyl01:/gsclient# gluster peer probe 192.168.52.125 |
1 | root@wyl01:/gsclient# gluster peer status |
发现现在变成2*2了模式了。重新写入20个txt文件,扩容后这里需要注意的是必须先rebalance。然后重新写入文件才会hash到新的节点上。之前的旧数据也会被rebalance。
1 | root@wyl01:/gsclient# gluster volume rebalance gv1 start |
节点的缩容,这里是分布式复制,所以缩容也是成对节点的一起缩容,操作如下:
1 | # 开始移除节点 |
本文讨论Kafka的扩缩容以及故障后如何“补齐”分区。实质上先扩容再缩容也是迁移的操作。
Kafka 版本2.6。
扩容也就是新增节点,扩容后老的数据不会自动迁移,只有新创建的topic才可能会分配到新增的节点上面。如果我们不需要迁移旧数据,那直接把新的节点启动起来就行了,不需要做额外的操作。但有的时候,新增节点后,我们会将一些老数据迁移到新的节点上,以达到负载均衡的目的,这个时候就需要手动操作了。Kafka提供了一个脚本(在bin目录下):kafka-reassign-partitions.sh,通过这个脚本可以重新分配分区的分布。脚本的使用比较简单,提供一个JSON格式的分配方案,然后传给脚本,脚本根据我们的分配方案重新进行平衡。
举个例子,假如现在集群有181、182两个broker,上面有4个topic:test-1,test-2,test-3,test-4,这些topic都有4个分区,2个副本,如下:
1 | 两个broker |
现在扩容了,新增了两个节点:183和184。扩容后,我们想要把test-3,test-4迁移到183,184上面去。
首先我们可以准备如下JSON格式的文件(假设文件名为topics-to-move.json
):
1 | { |
里面写明想要重新分配的topic。然后执行如下命令:
1 | ➜ bin/kafka-reassign-partitions.sh --bootstrap-server localhost:9092 --topics-to-move-json-file topics-to-move.json --broker-list "183,184" --generate |
可以看到上面的命令会列出当前分区的分布情况,并且会给出一个建议的新分区分配方案,都是JSON格式的,内容也很简单。然后我们将建议的分配方案保存为一个文件(假设文件名为expand-cluster-reassignment.json
),当然我们也可以手动修改这个方案,只要格式正确即可。然后执行下面命令使用新的方案进行分区重分配:
1 | ➜ bin/kafka-reassign-partitions.sh --bootstrap-server localhost:9092 --reassignment-json-file expand-cluster-reassignment.json --execute |
这样就提交了重分配的任务,可以使用下面的命令查看任务的执行状态:
1 | ➜ bin/kafka-reassign-partitions.sh --bootstrap-server localhost:9092 --reassignment-json-file expand-cluster-reassignment.json --verify |
完成后,我们检查一下新的test-3和test-4的分区分配情况:
1 | ➜ bin/kafka-topics.sh --describe --topic test-3 --zookeeper localhost:2181/kafka_26 |
可以看到,这两个topic的数据已经全部分配到183和184节点上了。
从上面可以看到,其实数据分配完全是由我们自己把控的,缩容也只是数据迁移而已,只需要提供正确的迁移方案即可。一般生产环境很少有缩容的,但有一个场景比较常见,就是某个节点故障了,且无法恢复。以前的文章提到过,节点故障后,这个节点上的分区就丢了,Kafka不会自动在其它可用节点上重新创建一个副本,这个时候就需要我们自己手动在其他可用节点创建副本,原理和扩容是一样的。接着上面的例子,比如现在184节点故障了,且无法恢复了,而test-3和test-4有部分分区是在该节点上面的,自然也就丢了:
1 | 节点挂了,zk中的节点已经没了 |
这个时候,我们准备把test-3原来在184上的分区分配到181上面去,把test-4在184上的分区分配到182上去,那分配方案就是下面这样的:
1 | ➜ cat expand-cluster-reassignment.json |
然后执行分配方案即可:
1 | 执行分配方案 |
页面操作不支持批量操作topic,需要逐个topic进行操作。
1,进入topic视图,点击 Generate Partition Assignments 生成分区分配。进入分区分配界面,
2,对该topic需要占用的节点进行勾选,再次点击 Generate Partition Assignments
3,分区完成 , go to topic view
4, 重新分配。 Reassign Partitions
5,go to reassign partitions 转到重新分配分区
6,验证查看
不管扩容还是缩容,或者是故障后手动补齐分区,实质都是分区重分配,使用kafka-reassign-partitions.sh
脚本即可。该脚本使用也非常简单:
--generate
生成迁移方案文件;--execute
执行新的分配方案;--verify
查看分配方案执行进度。如果对于分配方案文件格式很熟悉,可以跳过1.
]]>通过本文掌握什么是负载均衡及负载均衡的作用和意义;了解lvs负载均衡的三种模式;了解lvs-DR负载均衡部署方法;掌握nginx实现负载均衡的方法;掌握lvs+nginx负载均衡拓扑结构。
一台普通服务器的处理能力是有限的,假如能达到每秒几万个到几十万个请求,但却无法在一秒钟内处理上百万个甚至更多的请求。但若能将多台这样的服务器组成一个系统,并通过软件技术将所有请求平均分配给所有服务器,那么这个系统就完全拥有每秒钟处理几百万个甚至更多请求的能力。这就是负载均衡最初的基本设计思想。
负载均衡是由多台服务器以对称的方式组成一个服务器集合,每台服务器都具有等价的地位,都可以单独对外提供服务而无须其他服务器的辅助。通过某种负载分担技术,将外部发送来的请求按照某种策略分配到服务器集合的某一台服务器上,而接收到请求的服务器独立地回应客户的请求。负载均衡解决了大量并发访问服务问题,其目的就是用最少的投资获得接近于大型主机的性能。
DNS(Domain Name System,域名系统),因特网上作为域名和IP地址相互映射的一个分布式数据库,能够使用户更方便的访问互联网,而不用去记住能够被机器直接读取的IP数串。通过主机名,最终得到该主机名对应的IP地址的过程叫做域名解析(或主机名解析)。DNS协议运行在UDP协议之上,使用端口号53。
DNS负载均衡技术是最早的负载均衡解决方案,它是通过DNS服务中的随机名字解析来实现的,在DNS服务器中,可以为多个不同的地址配置同一个名字,而最终查询这个名字的客户机将在解析这个名字时得到其中的一个地址。因此,对于同一个名字,不同的客户机会得到不同的地址,它们也就访问不同地址上的Web服务器,从而达到负载均衡的目的。
如下图:
优点
实现简单、实施容易、成本低、适用于大多数TCP/IP应用;
缺点
1、 负载分配不均匀,DNS服务器将Http请求平均地分配到后台的Web服务器上,而不考虑每个Web服务器当前的负载情况;如果后台的Web服务器的配置和处理能力不同,最慢的Web服务器将成为系统的瓶颈,处理能力强的服务器不能充分发挥作用;
2、可靠性低,如果后台的某台Web服务器出现故障,DNS服务器仍然会把DNS请求分配到这台故障服务器上,导致不能响应客户端。
3、变更生效时间长,如果更改NDS有可能造成相当一部分客户不能享受Web服务,并且由于DNS缓存的原因,所造成的后果要持续相当长一段时间(一般DNS的刷新周期约为24小时)。
基于四层交换技术的负载均衡是通过报文中的目标地址和端口,再加上负载均衡设备设置的服务器选择方式,决定最终选择的内部服务器与请求客户端建立TCP连接,然后发送Client请求的数据。
如下图:
client发送请求至4层负载均衡器,4层负载均衡器根据负载策略把client发送的报文目标地址(原来是负载均衡设备的IP地址)修改为后端服务器(可以是web服务器、邮件服务等)IP地址,这样client就可以直接跟后端服务器建立TCP连接并发送数据。
具有代表意义的产品:LVS(开源软件),F5(硬件)
优点
性能高、支持各种网络协议
缺点
对网络依赖较大,负载智能化方面没有7层负载好(比如不支持对url个性化负载),F5硬件性能很高但成本也高需要人民币几十万,对于小公司就望而却步了。
基于七层交换技术的负载均衡也称内容交换,也就是主要通过报文中的真正有意义的应用层内容,再加上负载均衡设备设置的服务器选择方式,决定最终选择的服务器。
如下图:
七层负载均衡服务器起了一个代理服务器的作用,client要访问webserver要先与七层负载设备进行三次握手后建立TCP连接,把要访问的报文信息发送给七层负载均衡;然后七层负载均衡再根据设置的均衡规则选择特定的webserver,然后通过三次握手与此台webserver建立TCP连接,然后webserver把需要的数据发送给七层负载均衡设备,负载均衡设备再把数据发送给client。
具有代表意义的产品:nginx(软件)、apache(软件)
优点
对网络依赖少,负载智能方案多(比如可根据不同的url进行负载)
缺点
网络协议有限,nginx和apache支持http负载,性能没有4层负载高
四层负载使用lvs软件或F5硬件实现。
七层负载使用nginx实现。
如下图是lvs+nginx的拓扑结构:
在keepalived+nginx的主备容灾高可用的架构中,nginx是作为外部访问系统的唯一入口,理论上一台nginx的最大并发量可以高达50000,但是当并发量更大的时候,keepalived+nginx的高可用机制是没办法满足需求的,因为keepalived+nginx的架构中确确实实是一台nginx在工作,只有当master宕机或异常时候,备份机才会上位。那么如何解决更大的高并发问题呢,也许会问能不能搭建nginx集群,直接对外提供访问?
很显然这是欠妥当的,因为当nginx作为外部的唯一访问入口,没办法直接以集群的形式对外提供服务,没有那么多的公网ip资源可用,既太浪费也不友好。但是在内网环境下,是可以用nginx集群(nginx横向扩展服务集合)的,当然总得有一个对外入口,所以需要在nginx集群之上,在加一层负载均衡器,作为系统的唯一入口。
LVS是Linux Virtual Server的简写,意即Linux虚拟服务器,是一个虚拟的服务器集群系统。本项目在1998年5月由章文嵩博士成立,是中国国内最早出现的自由软件项目之一。
运行 lPVS软件的服务器,在整个负载均衡集群中承担一调度角色 软件的服务器,(即 向真实服务器分配从客户端过来的请求。LVS中的调度方法有三种 :NAT(Network Address Translation网络地址转换)、TUN(tunnel 隧道)、DR(direct route 直接路由)
请求由LVS接受,由真实提供服务的服务器(RealServer, RS)直接返回给用户,返回的时候不经过LVS。
DR模式下需要LVS服务器和RS绑定同一个VIP, 一个请求过来时,LVS只需要将网络帧的MAC地址修改为某一台RS的MAC,该包就会被转发到相应的RS处理,注意此时的源IP和目标IP都没变,RS收到LVS转发来的包,发现MAC是自己的,发现IP也是自己的,于是这个包被合法地接受,而当RS返回响应时,只要直接向源IP(即用户的IP)返回即可,不再经过LVS。
DR模式下,lvs接收请求输入,将请求转发给RS,由RS输出响应给用户,性能非常高。
它的不足之处是要求负载均衡器与RS在一个物理段上。
NAT(Network Address Translation)是一种外网和内网地址映射的技术。NAT模式下,LVS需要作为RS的网关,当网络包到达LVS时,LVS做目标地址转换(DNAT),将目标IP改为RS的IP。RS接收到包以后,处理完,返回响应时,源IP是RS IP,目标IP是客户端的IP,这时RS的包通过网关(LVS)中转,LVS会做源地址转换(SNAT),将包的源地址改为VIP,对于客户端只知道是LVS直接返回给它的。
NAT模式请求和响应都需要经过lvs,性能没有DR模式好。
TUN模式是通过ip隧道技术减轻lvs调度服务器的压力,许多Internet服务(例如WEB服务器)的请求包很短小,而应答包通常很大,负载均衡器只负责将请求包分发给物理服务器,而物理服务器将应答包直接发给用户。所以,负载均衡器能处理很巨大的请求量。相比NAT性能要高的多,比DR模式的优点是不限制负载均衡器与RS在一个物理段上。但是它的不足需要所有的服务器(lvs、RS)支持”IP Tunneling”(IP Encapsulation)协议。
vip:192.168.101.100
lvs-director:192.168.101.8
nginx1:192.168.101.3
nginx2:192.168.101.4
在192.168.101.8上安装lvs
centos6.5自带lvs,检查linux内核是否集成lvs模块:
1 | modprobe -l | grep ipvs |
1 | yum install -y gcc gcc-c++ makepcre pcre-devel kernel-devel openssl-devel libnl-devel popt* |
将ipvsadm-1.26.tar.gz拷贝至/usr/local/下
1 | cd /usr/local |
校验是否安装成功:
在192.168.101.3和192.168.101.4上安装nginx。
nginx配置文件
创建nginx-lvs.conf,http内容如下:
1 | http { |
1 | ifconfig eth0:0 192.168.101.100 broadcast 192.168.101.100 netmask 255.255.255.255 up |
此处在eth0设备上绑定了一个虚拟设备eth0:0,同时设置了一个虚拟IP是192.168.101.100,然后指定广播地址也为192.168.101.100,需要特别注意的是,虚拟ip地址的广播地址是它本身,子网掩码是255.255.255.255。
1 | route add -host 192.168.101.100 dev eth0:0 |
1 | echo "1" >/proc/sys/net/ipv4/ip_forward |
参数值为1时启用ip转发,为0时禁止ip转发。
1 | ipvsadm --clear |
1 | ipvsadm -A -t 192.168.101.100:80 -s rr |
-s rr表示采用轮询策略。
:80表示负载转发的端口是80
1 | ipvsadm -a -t 192.168.101.100:80 -r 192.168.101.3:80 -g |
在新加虚拟IP记录中添加两条新的Real Server记录,-g表示指定LVS 的工作模式为直接路由模式。
lvs进行负载转发需要保证lvs负载的端口要和nginx服务的端口的一致,这里都为80。
1 | ipvsadm |
在lvs的DR和TUn模式下,用户的访问请求到达真实服务器后,是直接返回给用户的,而不再经过前端的Director Server,因此,就需要在每个Real server节点上增加虚拟的VIP地址,这样数据才能直接返回给用户。
1 | ifconfig lo:0 192.168.101.100 broadcast 192.168.101.100 netmask 255.255.255.255 up |
arp_announce :定义不同级别:当ARP请求通过某个端口进来是否利用这个接口来回应。
0 -利用本地的任何地址,不管配置在哪个接口上去响应ARP请求;
1 - 避免使用另外一个接口上的mac地址去响应ARP请求;
2 - 尽可能使用能够匹配到ARP请求的最佳地址。
arp_ignore:当ARP请求发过来后发现自己正是请求的地址是否响应;
0 - 利用本地的任何地址,不管配置在哪个接口上去响应ARP请求;
1 - 哪个接口上接受ARP请求,就从哪个端口上回应。
1 | echo "1" >/proc/sys/net/ipv4/conf/lo/arp_ignore |
由于lvs设置为rr轮询策略,当访问虚IP http://192.168.101.100,每次刷新请求通过lvs负载到不同的服务器。
1、测试时需要在nginx的http中设置keepalive_timeout 0; 取消使用http持久连接模式,保证每次客户端发起请求都需要向服务端建立连接,这样做是为了每次刷新页面都要经过lvs负载转发。
2、lvs进行负载转发需要保证lvs负载的端口要和nginx服务的端口的一致,这里都为80。
keepalive_timeout说明:
在nginx中keepalive_timeout的默认值是75秒,默认使用http持久连接模式,可使客户端到服务器端的连接持续有效,当出现对服务器的后继请求时,可避免建立或重新建立连接。生产环境建议keepalive_timeout不要设置为0。
修改192.168.101.3和192.168.101.4下html目录中index.html的内容使之个性化。
第一次请求:http://192.168.101.100
刷新,相当于第二次请求:
依次交替测试,发现每次请求被负载到不同的nginx上。
任意停止掉一个nginx,请求http://192.168.101.100继续可以浏览,由于lvs采用轮询策略如果其中一个nginx请求不可到达则去请求另外的nginx。
为了方便配置启动lvs将上边Director Server和Real Server的配置过程封装在shell脚本中。
在/etc/init.d下创建lvsdr,内容如下:
1 |
|
1 | #修改脚本权限: |
在/etc/init.d下创建lvsdr,内容如下:
1 |
|
1 | #修改脚本权限: |
lvs采用DR模式基本上没有性能瓶颈,用户请求输入至lvs经过负载转发到后台服务上,通过后台服务输出响应给用户。nginx的负载性能远没有lvs好,lvs四层+nginx七层负载的好处是最前端是lvs接收请求进行负载转发,由多个nginx共同完成七层负载,这样nginx的负载性能就可以线性扩展。
vip:192.168.101.100
lvs-director:192.168.101.8
nginx1:192.168.101.3 安装nginx
nginx2:192.168.101.4 安装nginx
tomcat1:192.168.101.5 安装tomcat
tomcat2:192.168.101.6 安装tomcat
vip:192.168.101.100
lvs-director:192.168.101.8
参考lvs四层负载DR模式进行配置
nginx1:192.168.101.3 安装nginx
nginx2:192.168.101.4 安装nginx
参考lvs四层负载DR模式进行配置,需要修改nginx的配置文件使每个nginx对两个tomcat进行负载,如下:
1 | http { |
请求http://192.168.101.100,lvs负载到不同的nginx上,如果停止任意一台nginx或停止任意一台tomcat不影响访问。
lvs作为负载均衡器,所有请求都先到达lvs,可见lvs处于非常重要的位置,如果lvs服务器宕机后端web服务将无法提供服务,影响严重。
为了屏蔽负载均衡服务器的宕机,需要建立一个备份机。主服务器和备份机上都运行高可用(High Availability)监控程序,通过传送诸如“I am alive”这样的信息来监控对方的运行状况。当备份机不能在一定的时间内收到这样的信息时,它就接管主服务器的服务IP并继续提供负载均衡服务;当备份管理器又从主管理器收到“I am alive”这样的信息时,它就释放服务IP地址,这样的主服务器就开始再次提供负载均衡服务。
keepalived是集群管理中保证集群高可用的一个服务软件,用来防止单点故障。
Keepalived的作用是检测web服务器的状态,如果有一台web服务器死机,或工作出现故障,Keepalived将检测到,并将有故障的web服务器从系统中剔除,当web服务器工作正常后Keepalived自动将web服务器加入到服务器群中,这些工作全部自动完成,不需要人工干涉,需要人工做的只是修复故障的web服务器。
keepalived是以VRRP协议为实现基础的,VRRP全称Virtual Router Redundancy Protocol,即虚拟路由冗余协议。
虚拟路由冗余协议,可以认为是实现路由器高可用的协议,即将N台提供相同功能的路由器组成一个路由器组,这个组里面有一个master和多个backup,master上面有一个对外提供服务的vip(该路由器所在局域网内其他机器的默认路由为该vip),master会发组播,当backup收不到VRRP包时就认为master宕掉了,这时就需要根据VRRP的优先级来选举一个backup当master。这样的话就可以保证路由器的高可用了。
keepalived主要有三个模块,分别是core、check和VRRP。core模块为keepalived的核心,负责主进程的启动、维护以及全局配置文件的加载和解析。check负责健康检查,包括常见的各种检查方式。VRRP模块是来实现VRRP协议的。
初始状态
主机宕机
主机恢复
vip:192.168.101.100
lvs-director:192.168.101.8 主lvs
lvs-director:192.168.101.9 备lvs
nginx1:192.168.101.3 安装nginx
nginx2:192.168.101.4 安装nginx
tomcat1:192.168.101.5 安装tomcat
tomcat2:192.168.101.6 安装tomcat
分别在主备lvs上安装keepalived,参考“安装手册”进行安装:
1 | yum install keepalived -y |
修改主lvs下/etc/keepalived/keepalived.conf文件
1 | ! Configuration File for keepalived |
修改备lvs下/etc/keepalived/keepalived.conf文件
配置备lvs时需要注意:需要修改state**为BACKUP , priority比MASTER低,virtual_router_id和master的值一致**
1 | ! Configuration File for keepalived |
注意:使用keepalived就不用手动配置启动lvs,在主、备lvs上启动keepalived即可。
主备lvs(192.168.101.8、192.168.101.9)都启动keepalived。
1 | service keepalived start |
192.168.101.3、192.168.101.4启动nginx和lvs的realserver配置
1 | cd /usr/local/nginx/sbin |
启动lvs的realserver配置:
1 | service lvsdr start |
注意:real server的lvs配置需要使用lvsdr脚本。
略
查看主lvs的eth0设置:
vip绑定在主lvs的eth0上。
查询lvs状态:
查看备lvs的eth0设置:
vip没有绑定在备lvs的eth0上。
访问http://192.168.101.100,可以正常负载。
将主lvs的keepalived停止或将主lvs关机(相当于模拟宕机),查看主lvs的eth0:
eth0没有绑定vip
查看备lvs的eth0:
vip已经漂移到备lvs。
访问http://192.168.101.100,可以正常负载。
将主lvs的keepalived启动。
查看主lvs的eth0:
查看备lvs的eth0:
vip漂移到主lvs。
查看备lvs的eth0:
eth0没有绑定vip
访问http://192.168.101.100,可以正常负载。
上边主备方案是当前只有一台lvs工作,这造成资源浪费,可以采用双主结构,让两台lvs当前都进行工作,采用dns轮询方式,当用户访问域名通过dns轮询每台lvs,双主结构需要两个vip,这两个vip要绑定域名。
同样,在每台lvs上安装keepalived软件,当keepalived检测到其中一个lvs宕机则将宕机的vip漂移到活动lvs上,当lvs恢复则vip又重新漂移回来。
每台lvs绑定一个vip,共两个vip,DNS设置域名对应这两个vip,通过DNS轮询每次解析到不同的vip上即解析到不同的lvs上。
其中一个主机宕机,每台lvs上安装的keepalived程序会检测到对方宕机,将宕机一方的vip漂移至活动的lvs服务器上,这样DNS轮询全部到一台lvs继续对外提供服务。
当主机恢复又回到初始状态,每个vip绑定在不同的lvs上。
前端使用1到2台lvs作为负载基本可以满足中小型网站的并发要求,当lvs的负载成为瓶颈此时就需要对lvs进行优化、扩展。
OSPF(Open Shortest Path First开放式最短路径优先)是一个内部网关协议(Interior Gateway Protocol,简称IGP),用于在单一自治系统(autonomous system,AS)内决策路由。
LVS(DR)通过ospfd,做lvs集群,实现一个VIP,多台LVS同时工作提供服务,这种方案需要依赖三层交换机设备实现。
用户请求(VIP:42.xx.xx.100)到达三层交换机之后,通过对原地址、端口和目的地址、端口的hash,将链接分配到集群中的某一台LVS上,LVS通过内网(10.101.10.x)向后端转发请求,后端再将数据返回给用户。
LVS-ospf集群模式的最大优势就在于:
1.LVS调度机自由伸缩,横向线性扩展(最大8台,受限于三层设备允许的等价路由数目maximum load-balancing);
2.LVS机器同时工作,不存在备机,提高利用率;
3.做到了真正的高可用,某台LVS机器宕机后,不会影响服务
上面讲的是一组双主结构,可以采用多组双主结构达到横向扩展lvs的目的,此方案需要每台lvs都绑定一个vip(公网ip),DNS设置域名轮询多个vip,如下图:
如果资金允许可以购买硬件设置来完成负载均衡,性能不错的有F5、Array等都可以满足超高并发的要求。
]]>Kubernetes跨主机容器之间的通信组件,目前主流的是flannel和calico,本文对两个组件进行简单介绍和对比。
由CoreOS开发的项目Flannel,可能是最直接和最受欢迎的CNI插件。它是容器编排系统中最成熟的网络结构示例之一,旨在实现更好的容器间和主机间网络。随着CNI概念的兴起,Flannel CNI插件算是早期的入门。
与其他方案相比,Flannel相对容易安装和配置。它被打包为单个二进制文件FlannelD,许多常见的Kubernetes集群部署工具和许多Kubernetes发行版都可以默认安装Flannel。Flannel可以使用Kubernetes集群的现有etcd集群来使用API存储其状态信息,因此不需要专用的数据存储。
Flannel配置第3层IPv4 Overlay网络。它会创建一个大型内部网络,跨越集群中每个节点。在此Overlay网络中,每个节点都有一个子网,用于在内部分配IP地址。在配置Pod时,每个节点上的Docker桥接口都会为每个新容器分配一个地址。同一主机中的Pod可以使用Docker桥接进行通信,而不同主机上的pod会使用flanneld将其流量封装在UDP数据包中,以便路由到适当的目标。
Flannel有几种不同类型的后端可用于封装和路由。默认和推荐的方法是使用VXLAN,因为VXLAN性能更良好并且需要的手动干预更少。
calico包括如下重要组件:Felix,etcd,BGP Client,BGP Route Reflector。下面分别说明一下这些组件。
Felix:主要负责路由配置以及ACLS规则的配置以及下发,它存在在每个node节点上。
etcd:分布式键值存储,主要负责网络元数据一致性,确保Calico网络状态的准确性,可以与kubernetes共用;
BGPClient(BIRD), 主要负责把 Felix写入 kernel的路由信息分发到当前 Calico网络,确保 workload间的通信的有效性;
BGPRoute Reflector(BIRD), 大规模部署时使用,摒弃所有节点互联的mesh模式,通过一个或者多个 BGPRoute Reflector 来完成集中式的路由分发;
如下图所示,描述了从源容器经过源宿主机,经过数据中心的路由,然后到达目的宿主机最后分配到目的容器的过程。
从上述的原理可以看出,flannel在进行路由转发的基础上进行了封包解包的操作,这样浪费了CPU的计算资源。下图是从网上找到的各个开源网络组件的性能对比。可以看出无论是带宽还是网络延迟,calico和主机的性能是差不多的。
Jumpserver堡垒机的作用和好处这里就不再赘述,本文教你快速用docker容器安装jumperserver,让你快速体验。本教程是在单机上操作,处于以后扩展的需求,强烈建议在多台服务器上搭建。
1 | cd /opt |
1 | vim config-example.txt |
1 | ./jmsctl.sh install |
服务注册中心本质上是为了解耦服务提供者和服务消费者。对于任何一个微服务,原则上都应存在或者支持多个提供者,这是由微服务的分布式属性决定的。更进一步,为了支持弹性扩缩容特性,一个微服务的提供者的数量和分布往往是动态变化的,也是无法预先确定的。因此,原本在单体应用阶段常用的静态LB机制就不再适用了,需要引入额外的组件来管理微服务提供者的注册与发现,而这个组件就是服务注册中心。
CAP理论是分布式架构中重要理论
1 | 一致性(Consistency) (所有节点在同一时间具有相同的数据) |
P的理解是在整个系统中某个部分挂掉或者宕机了,并不影响整个系统的运作或者使用,是网络层面的,通常认为网络是顺畅流通的。
A可用性是系统的某个节点挂了,但并不影响系统的接受请求或者发出响应。
C一致性是客户端请求系统中的任意节点,获取的返回结果都是一致的。系统中各个节点会实时同步信息来保证
但CAP 3项不可能都取,只能取其中2两项,造成侧重点不同。
如果C是第一需求的话,那么会影响A的性能,因为要数据同步,不然请求结果会有差异,但是数据同步会消耗时间,期间可用性就会降低。
如果A是第一需求,那么只要有一个服务在,就能正常接受请求,但是对与返回结果一致就不能保证,原因是,在分布式部署的时候,数据一致的过程不可能想切线路那么快。
再如果,同事满足一致性和可用性,那么分区容错就很难保证了,只能是单点,也是分布式的基本核心。好了,明白这些理论,就可以在相应的场景选取服务注册与发现了
设计或者选型一个服务注册中心,首先要考虑的就是服务注册与发现机制。纵观当下各种主流的服务注册中心解决方案,大致可归为三类:
注1:对于第一类注册方式,除了Eureka这种一站式解决方案,还可以基于ZooKeeper或者Etcd自行实现一套服务注册机制,这在大公司比较常见,但对于小公司而言显然性价比太低。
注2:由于DNS固有的缓存缺陷,本文不对第三类注册方式作深入探讨。
除了基本的服务注册与发现机制,从开发和运维角度,至少还要考虑如下五个方面:
Consul是支持自动注销服务实例, 请见文档: https://www.consul.io/api-docs/agent/service,在check的 DeregisterCriticalServiceAfter 这个参数
新版本的Dubbo也扩展了对 Consul 的支持。 参考: https://github.com/apache/dubbo/tree/master/dubbo-registry
与 Eureka 有所不同,Zookeeper 在设计时就紧遵CP原则,即任何时候对 Zookeeper 的访问请求能得到一致的数据结果,同时系统对网络分割具备容错性,但是 Zookeeper 不能保证每次服务请求都是可达的。
从 Zookeeper 的实际应用情况来看,在使用 Zookeeper 获取服务列表时,如果此时的 Zookeeper 集群中的 Leader 宕机了,该集群就要进行 Leader 的选举,又或者 Zookeeper 集群中半数以上服务器节点不可用(例如有三个节点,如果节点一检测到节点三挂了 ,节点二也检测到节点三挂了,那这个节点才算是真的挂了),那么将无法处理该请求。所以说,Zookeeper不能保证服务可用性。
当然,在大多数分布式环境中,尤其是涉及到数据存储的场景,数据一致性应该是首先被保证的,这也是 Zookeeper 设计紧遵CP原则的另一个原因。
但是对于服务发现来说,情况就不太一样了,针对同一个服务,即使注册中心的不同节点保存的服务提供者信息不尽相同,也并不会造成灾难性的后果。
因为对于服务消费者来说,能消费才是最重要的,消费者虽然拿到可能不正确的服务实例信息后尝试消费一下,也要胜过因为无法获取实例信息而不去消费,导致系统异常要好(淘宝的双十一,京东的618就是紧遵AP的最好参照)。
当master节点因为网络故障与其他节点失去联系时,剩余节点会重新进行leader选举。问题在于,选举leader的时间太长,30~120s,而且选举期间整个zk集群都是不可用的,这就导致在选举期间注册服务瘫痪。
在云部署环境下, 因为网络问题使得zk集群失去master节点是大概率事件,虽然服务能最终恢复,但是漫长的选举事件导致注册长期不可用是不能容忍的。
Spring Cloud Netflix 在设计 Eureka 时就紧遵AP原则(尽管现在2.0发布了,但是由于其闭源的原因 ,但是目前 Ereka 1.x 任然是比较活跃的)。
Eureka Server 也可以运行多个实例来构建集群,解决单点问题,但不同于 ZooKeeper 的选举 leader 的过程,Eureka Server 采用的是Peer to Peer 对等通信。这是一种去中心化的架构,无 master/slave 之分,每一个 Peer 都是对等的。在这种架构风格中,节点通过彼此互相注册来提高可用性,每个节点需要添加一个或多个有效的 serviceUrl 指向其他节点。每个节点都可被视为其他节点的副本。
在集群环境中如果某台 Eureka Server 宕机,Eureka Client 的请求会自动切换到新的 Eureka Server 节点上,当宕机的服务器重新恢复后,Eureka 会再次将其纳入到服务器集群管理之中。当节点开始接受客户端请求时,所有的操作都会在节点间进行复制(replicate To Peer)操作,将请求复制到该 Eureka Server 当前所知的其它所有节点中。
当一个新的 Eureka Server 节点启动后,会首先尝试从邻近节点获取所有注册列表信息,并完成初始化。Eureka Server 通过 getEurekaServiceUrls() 方法获取所有的节点,并且会通过心跳契约的方式定期更新。
默认情况下,如果 Eureka Server 在一定时间内没有接收到某个服务实例的心跳(默认周期为30秒),Eureka Server 将会注销该实例(默认为90秒, eureka.instance.lease-expiration-duration-in-seconds 进行自定义配置)。
当 Eureka Server 节点在短时间内丢失过多的心跳时,那么这个节点就会进入自我保护模式。
Eureka的集群中,只要有一台Eureka还在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)。除此之外,Eureka还有一种自我保护机制,如果在15分钟内超过85%的节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,此时会出现以下几种情况:
因此,Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像zookeeper那样使得整个注册服务瘫痪。
Consul 是 HashiCorp 公司推出的开源工具,用于实现分布式系统的服务发现与配置。Consul 使用 Go 语言编写,因此具有天然可移植性(支持Linux、windows和Mac OS X)。
Consul 内置了服务注册与发现框架、分布一致性协议实现、健康检查、Key/Value 存储、多数据中心方案,不再需要依赖其他工具(比如 ZooKeeper 等),使用起来也较为简单。
Consul 遵循CAP原理中的CP原则,保证了强一致性和分区容错性,且使用的是Raft算法,比zookeeper使用的Paxos算法更加简单。虽然保证了强一致性,但是可用性就相应下降了,例如服务注册的时间会稍长一些,因为 Consul 的 raft 协议要求必须过半数的节点都写入成功才认为注册成功 ;在leader挂掉了之后,重新选举出leader之前会导致Consul 服务不可用。
Consul本质上属于应用外的注册方式,但可以通过SDK简化注册流程。而服务发现恰好相反,默认依赖于SDK,但可以通过Consul Template(下文会提到)去除SDK依赖。
Consul Template
Consul,默认服务调用者需要依赖Consul SDK来发现服务,这就无法保证对应用的零侵入性。
所幸通过Consul Template,可以定时从Consul集群获取最新的服务提供者列表并刷新LB配置(比如nginx的upstream),这样对于服务调用者而言,只需要配置一个统一的服务调用地址即可。
Consul强一致性(C)带来的是:
服务注册相比Eureka会稍慢一些。因为Consul的raft协议要求必须过半数的节点都写入成功才认为注册成功
Leader挂掉时,重新选举期间整个consul不可用。保证了强一致性但牺牲了可用性。
Eureka保证高可用(A)和最终一致性:
服务注册相对要快,因为不需要等注册信息replicate到其他节点,也不保证注册信息是否replicate成功
当数据出现不一致时,虽然A, B上的注册信息不完全相同,但每个Eureka节点依然能够正常对外提供服务,这会出现查询服务信息时如果请求A查不到,但请求B就能查到。如此保证了可用性但牺牲了一致性。
其他方面,eureka就是个servlet程序,跑在servlet容器中; Consul则是go编写而成。
Nacos是阿里开源的,Nacos 支持基于 DNS 和基于 RPC 的服务发现。在Spring Cloud中使用Nacos,只需要先下载 Nacos 并启动 Nacos server,Nacos只需要简单的配置就可以完成服务的注册发现。
Nacos除了服务的注册发现之外,还支持动态配置服务。动态配置服务可以让您以中心化、外部化和动态化的方式管理所有环境的应用配置和服务配置。动态配置消除了配置变更时重新部署应用和服务的需要,让配置管理变得更加高效和敏捷。配置中心化管理让实现无状态服务变得更简单,让服务按需弹性扩展变得更容易。
一句话概括就是Nacos = Spring Cloud注册中心 + Spring Cloud配置中心。
参考链接:
]]>本文将从零开始在干净的机器上安装 Docker、Kubernetes (使用 kubeadm)、Calico、NFS StorageClass等
,通过手把手的教程演示如何搭建一个 Kubernetes集群
,并在 K8s集群之上安装开源的KubeSphere
容器平台可视化运营集群环境。
所有机器处于同一内网网段,并且可以互相通信。
机器IP | 工作内容 |
---|---|
10.220.170.240 | NFS |
10.209.208.238 | master |
10.145.197.182 | nodes0 |
10.145.197.176 | nodes1 |
10.145.197.120 | nodes2 |
10.209.33.24 | nodes3 |
Docker版本: v19.03.4
k8s集群(kubeadm、kubelet 和 kubectl等)版本:v1.17.3
kubesphere版本:v3.0
在所有节点上执行
1 | # 为了方便本操作关闭了防火墙,也建议你这样操作 |
更换CentOS YUM源为阿里云yum源
1 | # 安装wget |
时间同步并确认
1 | timedatectl |
每台机器上也都要安装Docker
1 | # 安装 Docker CE |
需要将Docker的Cgroup Driver 修改为 systemd,不然在为Kubernetes 集群添加节点时会报如下错误:
1 | # 执行 kubeadm join 的 WARNING 信息 |
目前 Docker 的 Cgroup Driver 看起来应该是这样的:
1 | docker info|grep "Cgroup Driver" |
需要将这个值修改为 systemd 。同时将registry替换成国内的一些仓库地址,以免直接在官方仓库拉取镜像会很慢,操作如下。
1 | # Setup daemon. |
需要在每台机器上安装以下的软件包:
1 | # 配置K8S的yum源 |
安装指定版本 kubelet、 kubeadm 、kubectl, 这里选择当前较新的稳定版 Kubernetes 1.17.3,如果选择的版本不一样,在执行集群初始化的时候,注意 –kubernetes-version 的值。
1 | # 增加配置 |
在 Master上执行初始化,执行初始化使用 kubeadm init 命令。
1 | # 设置hosts |
下面有不带注释的初始化命令,建议先查看带注释的每个参数对应的意义,确保与你的当前配置的环境是一致的,然后再执行初始化操作,避免踩雷。
1 | # 初始化 Control-plane/Master 节点 |
不带注释的内容如下,如果初始化超时,可以修改DNS为8.8.8.8后重启网络服务再次尝试。
1 | kubeadm init \ |
接下来这个过程有点漫长(初始化会下载镜像、创建配置文件、启动容器等操作),泡杯茶,耐心等待。你也可以执行 tailf /var/log/messages 来实时查看系统日志,观察 Master 的初始化进展,期间碰到一些报错不要紧张,可能只是暂时的错误,等待最终反馈的结果即可。
如果初始化最终成功执行,你将看到如下信息:
1 | Your Kubernetes control-plane has initialized successfully! |
为普通用户添加 kubectl 运行权限,命令内容在初始化成功后的输出内容中可以看到。
1 | mkdir -p $HOME/.kube |
建议root用户也进行以上操作,作者使用的是root用户执行的初始化操作,然后在操作完成后查看集群状态的时候,出现如下错误:
1 | The connection to the server localhost:8080 was refused - did you specify the right host or port? |
这时候请备份好 kubeadm init 输出中的 kubeadm join 命令,因为将会需要这个命令来给集群添加节点。
集群必须安装Pod网络插件,以使Pod可以相互通信,只需在Master节点操作,其他新加入的节点会自动创建相关pod。必须在任何应用程序之前部署网络组件。另外,在安装网络之前,CoreDNS将不会启动,你可以通过命令 来查看CoreDNS 的状态
1 | # 查看 CoreDNS 的状态,并不是 Running 状态 |
kubeadm 支持多种网络插件,我们选择Calico 网络插件(kubeadm 仅支持基于容器网络接口(CNI)的网络(不支持kubenet),默认情况下,它给出的pod的IP段地址是 192.168.0.0/16 ,如果你的机器已经使用了此IP段,就需要修改这个配置项,将其值改为在初始化 Master 节点时使用 kubeadm init –pod-network-cidr=x.x.x.x/x 的IP地址段,即我们上面配置的 10.10.0.0/16 ,大概在625行左右,操作如下:
1 | # 获取配置文件 |
稍等片刻查询 pod 详情,你也可以使用 watch 命令来实时查看 pod 的状态,等待 Pod 网络组件部署成功后,就可以看到一些信息了,包括 Pod 的 IP 地址信息,这个过程时间可能会有点长。
1 | watch -n 2 kubectl get pods --all-namespaces -o wide |
请首先确认所有Worker节点满足第一部分的环境说明,并且已经安装了 Docker 和 kubeadm、kubelet 、kubectl,并且已经启动 kubelet。
1 | # 添加 Hosts 解析 |
将 Worker 节点添加到集群,这里注意,执行后可能会报错,有幸的话你会跳进这个坑,这是因为 Worker 节点加入集群的命令实际上在初始化 master 时已经有提示出来了,不过两小时后会删除上传的证书,所以如果你此时加入集群的时候提示证书相关的错误,请执行 kubeadm init phase upload-certs –upload-certs 重新加载证书。
1 | kubeadm join kuber4s.api:6443 --token 0y1dj2.ih27ainxwyib0911 \ |
执行加入操作,你可能会发现卡着不动,大概率是因为令牌ID对此集群无效或已过 2 小时的有效期(通过执行 kubeadm join –v=5 来获取详细的加入过程,看到了内容为 ”token id “0y1dj2” is invalid for this cluster or it has expired“ 的提示),接下来需要在 Master 上通过 kubeadm token create 来创建新的令牌。
1 | $ kubeadm token create --print-join-command |
在 Worker节点上重新执行加入集群命令
1 | kubeadm join kuber4s.api:6443 \ |
接下来在Master上查看 Worker 节点加入的状况,直到 Worker 节点的状态变为 Ready 便证明加入成功,这个过程可能会有点漫长,30 分钟以内都算正常的,主要看你网络的情况或者说拉取镜像的速度;另外不要一看到 /var/log/messages 里面报错就慌了,那也得看具体报什么错,看不懂就稍微等一下,一般在 Master 上能看到已经加入(虽然没有Ready)就没什么问题。
1 | watch kubectl get nodes -o wide |
kubesphere安装条件是必须k8s集群有StorageClass,这里用nfs。所以需要安装nfs服务来作为StorageClass。
1 | yum install -y nfs-utils |
1 | mkdir -p /data/k8s --创建一个存储目录 |
先启动rpcbind,再启动nfs
1 | systemctl start rpcbind |
Kubernetes 支持多种 StorageClass,这选择NFS 作为集群的 StorageClass。
下载所需文件,并进行内容调整
1 | mkdir nfsvolume && cd nfsvolume |
修改 deployment.yaml 中的两处 NFS 服务器 IP 和目录
1 | ... |
1 | kubectl create -f rbac.yaml |
在集群内所有机器上安装nfs-utils并启动。
1 | yum -y install nfs-utils |
查看storageclass
1 | $ kubectl get storageclass |
操作命令格式如下
1 | kubectl patch storageclass managed-nfs-storage -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}' |
请注意,最多只能有一个 StorageClass 能够被标记为默认。
验证标记是否成功
1 | $ kubectl get storageclass |
KubeSphere 是在 Kubernetes 之上构建的以应用为中心的容器平台,提供简单易用的操作界面以及向导式操作方式,在降低用户使用容器调度平台学习成本的同时,极大减轻开发、测试、运维的日常工作的复杂度,旨在解决 Kubernetes 本身存在的存储、网络、安全和易用性等痛点。除此之外,平台已经整合并优化了多个适用于容器场景的功能模块,以完整的解决方案帮助企业轻松应对敏捷开发与自动化运维、DevOps、微服务治理、灰度发布、多租户管理、工作负载和集群管理、监控告警、日志查询与收集、服务与网络、应用商店、镜像构建与镜像仓库管理和存储管理等多种场景。后续版本将提供和支持多集群管理、大数据、AI 等场景。
1 | k8s集群版本必须是1.15.x, 1.16.x, 1.17.x, or 1.18.x |
按照上面教程操作,完全符合要求。
1 | kubectl apply -f https://github.com/kubesphere/ks-installer/releases/download/v3.0.0/kubesphere-installer.yaml |
1 | kubectl logs -n kubesphere-system $(kubectl get pod -n kubesphere-system -l 'app in (ks-install, ks-installer)' -o jsonpath='{.items[0].metadata.name}') -f |
1 | kubectl get pods -A |
浏览器访问ip:30880 用户名:admin 默认密码:P@88w0rd
POD创建流程
Ingress与Service关系图
nfs异常排查
1 | kubectl get sc |
添加- –feature-gates=RemoveSelfLink=false
]]>如果你保持学习新技术的触角来持续拓展技术储备的广度的话,很可能在不同的地方听说过Istio
,可能还知道它和Service Mesh有着牵扯。本文可作为了解Istio
的入门介绍,介绍什么是Istio
,Istio
为什么最近这么火,以及Istio
解决了哪些问题,能给我们带来什么好处。
官方对 Istio 的介绍浓缩成了一句话:
1 | An open platform to connect, secure, control and observe services. |
翻译过来,就是”连接、安全加固、控制和观察服务的开放平台“。开放平台就是指它本身是开源的,服务对应的是微服务,也可以粗略地理解为单个应用。
中间的四个动词就是 Istio 的主要功能,官方也各有一句话的说明。这里再阐释一下:
虽然听起来非常高级,功能非常强大,但是一股脑出现这么多名词,还都是非常虚的概念,说了跟没说一样。要想理解上面这几句话的含义,我们还是从头说起,先聊聊 Service Mesh。
NOTE:其实 Istio 的源头是微服务,但这又是一个比较大的话题,目前可以参考网络上各种文章。如果有机会,我们再来聊聊微服务。
一般介绍 Service Mesh 的文章都会从网络层的又一个抽象说起,把 Service Mesh 看做建立在 TCP 层之上的微服务层。我这次换个思路,从 Service Mesh 的技术根基——网络代理来分析。
说起网络代理,我们会想到翻墙,如果对软件架构比较熟悉的会想到 Nginx 等反向代理软件。
其实网络代理的范围比较广,可以肯定的说,有网络访问的地方就会有代理的存在。
Wikipedia 对代理的定义如下:
1 | In computer networks, a proxy server is a server (a computer system or an application) that acts as an intermediary for requests from clients seeking resources from other servers. |
NOTE:代理可以是嵌套的,也就是说通信双方 A、B 中间可以多多层代理,而这些代理的存在有可能对 A、B 是透明的。
简单来说,网络代理可以简单类比成现实生活中的中介,本来需要通信的双方因为各种原因在中间再加上一道关卡。本来双方就能完成的通信,为何非要多此一举呢?
那是因为代理可以为整个通信带来更多的功能,比如:
不是要讲 Service Mesh 吗?为什么扯了一堆代理的事情?因为 Service Mesh 可以看做是传统代理的升级版,用来解决现在微服务框架中出现的问题,可以把 Service Mesh 看做是分布式的微服务代理。
在传统模式下,代理一般是集中式的单独的服务器,所有的请求都要先通过代理,然后再流入转发到实际的后端。
而在 Service Mesh 中,代理变成了分布式的,它常驻在了应用的身边(最常见的就是 Kubernetes Sidecar 模式,每一个应用的 Pod 中都运行着一个代理,负责流量相关的事情)。
这样的话,应用所有的流量都被代理接管,那么这个代理就能做到上面提到的所有可能的事情,从而带来无限的想象力。
此外,原来的代理都是基于网络流量的,一般都是工作在 IP 或者 TCP 层,很少关心具体的应用逻辑。
但是 Service Mesh 中,代理会知道整个集群的所有应用信息,并且额外添加了热更新、注入服务发现、降级熔断、认证授权、超时重试、日志监控等功能,让这些通用的功能不必每个应用都自己实现,放在代理中即可。
换句话说,Service Mesh 中的代理对微服务中的应用做了定制化的改进!
就这样,借着微服务和容器化的东风,传统的代理摇身一变,成了如今炙手可热的 Service Mesh。
应用微服务之后,每个单独的微服务都会有很多副本,而且可能会有多个版本,这么多微服务之间的相互调用和管理非常复杂,但是有了 Service Mesh,我们可以把这块内容统一在代理层。
有了看起来四通八达的分布式代理,我们还需要对这些代理进行统一的管理。
手动更新每个代理的配置,对代理进行升级或者维护是个不可持续的事情,在前面的基础上,在加上一个控制中心,一个完整的 Service Mesh 就成了。
管理员只需要根据控制中心的 API 来配置整个集群的应用流量、安全规则即可,代理会自动和控制中心打交道根据用户的期望改变自己的行为。
NOTE:所以你也可以理解 Service Mesh 中的代理会抢了 Nginx 的生意,这也是为了 Nginx 也要开始做 NginMesh 的原因。
了解了 Service Mesh 的概念,我们再来看 Istio ,也许就会清楚很多。首先来看 Istio 官方给出的架构图:
可以看到,Istio 就是我们上述提到的 Service Mesh 架构的一种实现,服务之间的通信(比如这里的 Service A 访问 Service B)会通过代理(默认是 Envoy)来进行。
而且中间的网络协议支持 HTTP/1.1,HTTP/2,gRPC 或者 TCP,可以说覆盖了主流的通信协议。
控制中心做了进一步的细分,分成了 Pilot、Mixer 和 Citadel,它们的各自功能如下:
代理会和控制中心通信,一方面可以获取需要的服务之间的信息,另一方面也可以汇报服务调用的 Metrics 数据。
知道了 Istio 的核心架构,再来看看它的功能描述就非常容易理解了:
虽然看起来非常炫酷,功能也很强大,但是一个架构和产品出来都是要解决具体的问题。所以这部分我们来看看微服务架构中的难题以及 Istio 给出的答案。
首先,原来的单个应用拆分成了许多分散的微服务,它们之间相互调用才能完成一个任务,而一旦某个过程出错(组件越多,出错的概率也就越大),就非常难以排查。
用户请求出现问题无外乎两个问题:错误和响应慢。如果请求错误,那么我们需要知道那个步骤出错了,这么多的微服务之间的调用怎么确定哪个有调用成功?哪个没有调用成功呢?
如果是请求响应太慢,我们就需要知道到底哪些地方比较慢?整个链路的调用各阶段耗时是多少?哪些调用是并发执行的,哪些是串行的?这些问题需要我们能非常清楚整个集群的调用以及流量情况。
此外,微服务拆分成这么多组件,如果单个组件出错的概率不变,那么整体有地方出错的概率就会增大。服务调用的时候如果没有错误处理机制,那么会导致非常多的问题。
比如如果应用没有配置超时参数,或者配置的超时参数不对,则会导致请求的调用链超时叠加,对于用户来说就是请求卡住了。
如果没有重试机制,那么因为各种原因导致的偶发故障也会导致直接返回错误给用户,造成不好的用户体验。
此外,如果某些节点异常(比如网络中断,或者负载很高),也会导致应用整体的响应时间变长,集群服务应该能自动避开这些节点上的应用。
最后,应用也是会出现 Bug 的,各种 Bug 会导致某些应用不可访问。这些问题需要每个应用能及时发现问题,并做好对应的处理措施。
应用数量的增多,对于日常的应用发布来说也是个难题。应用的发布需要非常谨慎,如果应用都是一次性升级的,出现错误会导致整个线上应用不可用,影响范围太大。
而且,很多情况我们需要同时存在不同的版本,使用 AB 测试验证哪个版本更好。
如果版本升级改动了 API,并且互相有依赖,那么我们还希望能自动地控制发布期间不同版本访问不同的地址。这些问题都需要智能的流量控制机制。
为了保证整个系统的安全性,每个应用都需要实现一套相似的认证、授权、HTTPS、限流等功能。
一方面大多数的程序员都对安全相关的功能并不擅长或者感兴趣,另外这些完全相似的内容每次都要实现一遍是非常冗余的。这个问题需要一个能自动管理安全相关内容的系统。
上面提到的这些问题是不是非常熟悉?它们就是 Istio 尝试解决的问题,如果把上面的问题和 Istio 提供的功能做个映射,你会发现它们非常匹配,毕竟 Istio 就是为了解决微服务的这些问题才出现的。
虽然 Istio 能解决那么多的问题,但是引入 Istio 并不是没有代价的。最大的问题是 Istio 的复杂性,强大的功能也意味着 Istio 的概念和组件非常多,要想理解和掌握 Istio ,并成功在生产环境中部署需要非常详细的规划。
一般情况下,集群管理团队需要对 Kubernetes 非常熟悉,了解常用的使用模式,然后采用逐步演进的方式把 Istio 的功能分批掌控下来。
第一步,自然是在测试环境搭建一套 Istio 的集群,理解所有的核心概念和组件。
了解 Istio 提供的接口和资源,知道它们的用处,思考如何应用到自己的场景中,然后是熟悉 Istio 的源代码,跟进社区的 Issues,了解目前还存在的 Issues 和 Bug,思考如何规避或者修复。
这一步是基础,需要积累到 Istio 安装部署、核心概念、功能和缺陷相关的知识,为后面做好准备。
第二步,可以考虑接入 Istio 的观察性功能,包括 Logging、Tracing、Metrics 数据。
应用部署到集群中,选择性地(一般是流量比较小,影响范围不大的应用)为一些应用开启 Istio 自动注入功能,接管应用的流量,并安装 Prometheus 和 Zipkin 等监控组件,收集系统所有的监控数据。
这一步可以试探性地了解 Istio 对应用的性能影响,同时建立服务的性能测试基准,发现服务的性能瓶颈,帮助快速定位应用可能出现的问题。
此时,这些功能可以是对应用开发者透明的,只需要集群管理员感知,这样可以减少可能带来的风险。
第三步,为应用配置 Time Out 超时参数、自动重试、熔断和降级等功能,增加服务的容错性。
这样可以避免某些应用错误进行这些配置导致问题的出现,这一步完成后需要通知所有的应用开发者删除掉在应用代码中对应的处理逻辑。这一步需要开发者和集群管理员同时参与。
第四步,和 Ingress、Helm、应用上架等相关组件和流程对接,使用 Istio 接管应用的升级发布流程。
让开发者可以配置应用灰度发布升级的策略,支持应用的蓝绿发布、金丝雀发布以及 AB 测试。
第五步,接入安全功能。配置应用的 TLS 互信,添加 RBAC 授权,设置应用的流量限制,提升整个集群的安全性。
因为安全的问题配置比较繁琐,而且优先级一般会比功能性相关的特性要低,所以这里放在了最后。
当然这个步骤只是一个参考,每个公司需要根据自己的情况、人力、时间和节奏来调整,找到适合自己的方案。
Istio 的架构在数据中心和集群管理中非常常见,每个 Agent 分布在各个节点上(可以是服务器、虚拟机、Pod、容器)负责接收指令并执行,以及汇报信息。
控制中心负责汇聚整个集群的信息,并提供 API 让用户对集群进行管理。
Kubernetes 也是类似的架构,SDN(Software Defined Network) 也是如此。
相信以后会有更多类似架构的出现,这是因为数据中心要管理的节点越来越多,我们需要把任务执行分布到各节点(Agent 负责的功能)。
同时也需要对整个集群进行管理和控制(Control Plane 的功能),完全去中心化的架构是无法满足后面这个要求的。
Istio 的出现为负责的微服务架构减轻了很多的负担,开发者不用关心服务调用的超时、重试、Rate Limit 的实现,服务之间的安全、授权也自动得到了保证。
集群管理员也能够很方便地发布应用(AB 测试和灰度发布),并且能清楚看到整个集群的运行情况。
但是这并不表明有了 Istio 就可以高枕无忧了,Istio 只是把原来分散在应用内部的复杂性统一抽象出来放到了统一的地方,并没有让原来的复杂消失不见。
因此我们需要维护 Istio 整个集群,而 Istio 的架构比较复杂,尤其是它一般还需要架在 Kubernetes 之上,这两个系统都比较复杂,而且它们的稳定性和性能会影响到整个集群。
因此再采用 Isito 之前,必须做好清楚的规划,权衡它带来的好处是否远大于额外维护它的花费,需要有相关的人才对整个网络、Kubernetes 和 Istio 都比较了解才行。
]]>你是否在为系统的数据库来一波大流量就几乎打满CPU,日常CPU居高不下烦恼?你是否在各种NoSql间纠结不定,到底该选用那种最好?今天的你就是昨天的我,这也是写这篇文章的初衷。
这篇文章是我好几个月来一直想写的一篇文章,也是一直想学习的一个内容,作为互联网从业人员,我们要知道关系型数据库(MySql、Oracle)无法满足我们对存储的所有要求,因此对底层存储的选型,对每种存储引擎的理解非常重要。同时也由于过去一段时间的工作经历,对这块有了一些更多的思考,想通过自己的总结把这块写出来分享给大家。
结构化数据、非结构化数据与半结构化数据
文章的开始,聊一下结构化数据、非结构化数据与半结构化数据,因为数据特点的不同,将在技术上直接影响存储引擎的选型。
首先是结构化数据,根据定义结构化数据指的是由二维表结构来逻辑表达和实现的数据,严格遵循数据格式与长度规范,也称作为行数据,特点为:数据以行为单位,一行数据表示一个实体的信息,每一行数据的属性是相同的。例如:
因此关系型数据库完美契合结构化数据的特点,关系型数据库也是关系型数据最主要的存储与管理引擎。
非结构化数据,指的是数据结构不规则或不完整,没有任何预定义的数据模型,不方便用二维逻辑表来表现的数据,例如办公文档(Word)、文本、图片、HTML、各类报表、视频音频等。
介于结构化与非结构化数据之间的数据就是半结构化数据了,它是结构化数据的一种形式,虽然不符合二维逻辑这种数据模型结构,但是包含相关标记,用来分割语义元素以及对记录和字段进行分层。常见的半结构化数据有XML和JSON,例如:
1 | <person> |
这种结构也被成为自描述的结构。
首先,我们看一下使用关系型数据库的方式,企业一个系统发展的几个阶段的架构演进(由于本文写的是Sql与NoSql,因此只以存储方式作为切入点,不会涉及类似MQ、ZK这些中间件内容):
阶段一:企业刚发展的阶段,最简单,一个应用服务器配一个关系型数据库,每次读写数据库。
阶段二:无论是使用MySQL还是Oracle还是别的关系型数据库,数据库通常不会先成为性能瓶颈,通常随着企业规模的扩大,一台应用服务器扛不住上游过来的流量且一台应用服务器会产生单点故障的问题,因此加应用服务器并且在流量入口使用Nginx做一层负载均衡,保证把流量均匀打到应用服务器上。
阶段三:随着企业规模的继续扩大,此时由于读写都在同一个数据库上,数据库性能出现一定的瓶颈,此时简单地做一层读写分离,每次写主库,读备库,主备库之间通过binlog同步数据,就能很大程度上解决这个阶段的数据库性能问题
阶段四:企业发展越来越好了,业务越来越大了,做了读写分离数据库压力还是越来越大,这时候怎么办呢,一台数据库扛不住,那我们就分几台吧,做分库分表,对表做垂直拆分,对库做水平拆分。以扩数据库为例,扩出两台数据库,以一定的单号(例如交易单号),以一定的规则(例如取模),交易单号对2取模为0的丢到数据库1去,交易单号对2取模为1的丢到数据库2去,通过这样的方式将写数据库的流量均分到两台数据库上。一般分库分表会使用Shard的方式,通过一个中间件,便于连接管理、数据监控且客户端无需感知数据库ip
上面的方式,看似可以解决问题(实际上确实也能解决很多问题),正常对关系型数据库做一下读写分离 + 分库分表,支撑个1W+的读写QPS还是问题不大的。但是受限于关系型数据库本身,这套架构方案依然有着明显的不足,下面对利用关系型数据库方式做存储的方案的优点先进行一下分析,后一部分再分析一下缺点,对某个技术的优缺点的充分理解是技术选型的前提。
因为行 + 列的二维表逻辑是非常贴近逻辑世界的一个概念,关系模型相对网状、层次等其他模型更加容易被理解
通用的SQL语言使得操作关系型数据库非常方便,支持join等复杂查询,Sql + 二维关系是关系型数据库最无可比拟的优点,这种易用性非常贴近开发者
支持ACID特性,可以维护数据之间的一致性,这是使用数据库非常重要的一个理由之一,例如同银行转账,张三转给李四100元钱,张三扣100元,李四加100元,而且必须同时成功或者同时失败,否则就会造成用户的资损
数据持久化到磁盘,没有丢失数据风险,支持海量数据存储
最常用的关系型数据库产品MySql、Oracle服务器性能卓越,服务稳定,通常很少出现宕机异常
紧接着的,我们看一下关系型数据库的缺点,也是比较明显的。
数据按行存储,即使只针对其中某一列进行运算,也会将整行数据从存储设备中读入内存,导致IO较高
为了提供丰富的查询能力,通常热点表都会有多个二级索引,一旦有了二级索引,数据的新增必然伴随着所有二级索引的新增,数据的更新也必然伴随着所有二级索引的更新,这不可避免地降低了关系型数据库的读写能力,且索引越多读写能力越差。有机会的话可以看一下自己公司的数据库,除了数据文件不可避免地占空间外,索引占的空间其实也并不少
数据一致性是关系型数据库的核心,但是同样为了维护数据一致性的代价也是非常大的。我们都知道SQL标准为事务定义了不同的隔离级别,从低到高依次是读未提交、读已提交、可重复度、串行化,事务隔离级别越低,可能出现的并发异常越多,但是通常而言能提供的并发能力越强。那么为了保证事务一致性,数据库就需要提供并发控制与故障恢复两种技术,前者用于减少并发异常,后者可以在系统异常的时候保证事务与数据库状态不会被破坏。对于并发控制,其核心思想就是加锁,无论是乐观锁还是悲观锁,只要提供的隔离级别越高,那么读写性能必然越差
前文提过,随着企业规模扩大,一种方式是对数据库做分库,做了分库之后,数据迁移(1个库的数据按照一定规则打到2个库中)、跨库join(订单数据里有用户数据,两条数据不在同一个库中)、分布式事务处理都是需要考虑的问题,尤其是分布式事务处理,业界当前都没有特别好的解决方案
由于数据库存储的是结构化数据,因此表结构schema是固定的,扩展不方便,如果需要修改表结构,需要执行DDL(data definition language)语句修改,修改期间会导致锁表,部分服务不可用
例如like “%中国真伟大%”,只能搜索到”2019年中国真伟大,爱祖国”,无法搜索到”中国真是太伟大了”这样的文本,即不具备分词能力,且like查询在”%中国真伟大”这样的搜索条件下,无法命中索引,将会导致查询效率大大降低
写了这么多,我的理解核心还是前三点,它反映出的一个问题是关系型数据库在高并发下的能力是有瓶颈的,尤其是写入/更新频繁的情况下,出现瓶颈的结果就是数据库CPU高、Sql执行慢、客户端报数据库连接池不够等错误,因此例如万人秒杀这种场景,我们绝对不可能通过数据库直接去扣减库存。
可能有朋友说,数据库在高并发下的能力有瓶颈,我公司有钱,加CPU、换固态硬盘、继续买服务器加数据库做分库不就好了,问题是这是一种性价比非常低的方式,花1000万达到的效果,换其他方式可能100万就达到了,不考虑人员、服务器投入产出比的Leader就是个不合格的Leader,且关系型数据库的方式,受限于它本身的特点,可能花了钱都未必能达到想要的效果。至于什么是花100万就能达到花1000万效果的方式呢?可以继续往下看,这就是我们要说的NoSql。
像上文分析的,数据库作为一种关系型数据的存储引擎,存储的是关系型数据,它有优点,同时也有明显的缺点,因此通常在企业规模不断扩大的情况下,不会一味指望通过增强数据库的能力来解决数据存储问题,而是会引入其他存储,也就是我们说的NoSql。
NoSql的全称为Not Only SQL,泛指非关系型数据库,是对关系型数据库的一种补充,特别注意补充这两个字,这意味着NoSql与关系型数据库并不是对立关系,二者各有优劣,取长补短,在合适的场景下选择合适的存储引擎才是正确的做法。
比较简单的NoSql就是缓存:
针对那些读远多于写的数据,引入一层缓存,每次读从缓存中读取,缓存中读取不到,再去数据库中取,取完之后再写入到缓存,对数据做好失效机制通常就没有大问题了。通常来说,缓存是性能优化的第一选择也是见效最明显的方案。
但是,缓存通常都是KV型存储且容量有限(基于内存),无法解决所有问题,于是再进一步的优化,我们继续引入其他NoSql:
数据库、缓存与其他NoSql并行工作,充分发挥每种NoSql的特点。当然NoSql在性能方面大大优于关系挺数据库的同时,往往也伴随着一些特性的缺失,比较常见的就是事务功能的缺失。
下面看一下常用的NoSql及他们的代表产品,并对每种NoSql的优缺点和适用场景做一下分析,便于熟悉每种NoSql的特点,方便技术选型。
KV型NoSql顾名思义就是以键值对形式存储的非关系型数据库,是最简单、最容易理解也是大家最熟悉的一种NoSql,因此比较快地带过。Redis、MemCache是其中的代表,Redis又是KV型NoSql中应用最广泛的NoSql,KV型数据库以Redis为例,最大的优点我总结下来就两点:
因此,KV型NoSql最大的优点就是高性能,利用Redis自带的BenchMark做基准测试,TPS可达到10万的级别,性能非常强劲。同样的Redis也有所有KV型NoSql都有的比较明显的缺点:
综上所述,KV型NoSql最合适的场景就是缓存的场景:
例如根据用户id查询用户信息,每次根据用户id去缓存中查询一把,查到数据直接返回,查不到去关系型数据库里面根据id查询一把数据写到缓存中去。
传统关系型数据库主要通过索引来达到快速查询的目的,但是在全文搜索的场景下,索引是无能为力的,like查询一来无法满足所有模糊匹配需求,二来使用限制太大且使用不当容易造成慢查询,搜索型NoSql的诞生正是为了解决关系型数据库全文搜索能力较弱的问题,ElasticSearch是搜索型NoSql的代表产品。
全文搜索的原理是倒排索引,我们看一下什么是倒排索引。要说倒排索引我们先看下什么是正排索引,传统的正排索引是文档–>关键字的映射,例如”Tom is my friend”这句话,会将其切分为”Tom”、”is”、”my”、”friend”四个单词,在搜索的时候对文档进行扫描,符合条件的查出来。这种方式原理非常简单,但是由于其检索效率太低,基本没什么实用价值。
倒排索引则完全相反,它是关键字–>文档的映射,我用张表格展示一下就比较清楚了:
意思是我现在这里有四个短句:
搜索引擎会根据一定的切分规则将这句话切成N个关键字,并以关键字的维度维护关键字在每个文本中的出现次数。这样下次搜索”Tom”的时候,由于Tom这个词语在”Tom is Tom”、”Tom is my friend”、”Tom is Betty’s husband”三句话中都有出现,因此这三条记录都会被检索出来,且由于”Tom is Tom”这句话中”Tom”出现了2次,因此这条记录对”Tom”这个单词的匹配度最高,最先展示。这就是搜索引擎倒排索引的基本原理,假设某个关键字在某个文档中出现,那么倒排索引中有两部分内容:
可以举一反三,我们搜索”Betty Tom”这两个词语也是一样,搜索引擎将”Betty Tom”切分为”Tom”、”Betty”两个单词,根据开发者指定的满足率,比如满足率=50%,那么只要记录中出现了两个单词之一的记录都会被检索出来,再按照匹配度进行展示。
搜索型NoSql以ElasticSearch为例,它的优点为:
同样,ElasticSearch也有比较明显的缺点:
性能全靠内存来顶,也是使用的时候最需要注意的点,非常吃硬件资源、吃内存,大数据量下64G + SSD基本是标配,算得上是数据库中的爱马仕了。为什么要专门提一下内存呢,因为内存这个东西是很值钱的,相同的配置多一倍内存,一个月差不多就要多花几百块钱,至于ElasticSearch内存用在什么地方,大概有如下这些:
读写之间有延迟,写入的数据差不多1s样子会被读取到,这也正常,写入的时候自动加入这么多索引肯定影响性能
数据结构灵活性不高,ElasticSearch这个东西,字段一旦建立就没法修改类型了,假如建立的数据表某个字段没有加全文索引,想加上,那么只能把整个表删了再重建
因此,搜索型NoSql最适用的场景就是有条件搜索尤其是全文搜索的场景,作为关系型数据库的一种替代方案。
另外,搜索型数据库还有一种特别重要的应用场景。我们可以想,一旦对数据库做了分库分表后,原来可以在单表中做的聚合操作、统计操作是否统统失效?例如我把订单表分16个库,1024张表,那么订单数据就散落在1024张表中,我想要统计昨天浙江省单笔成交金额最高的订单是哪笔如何做?我想要把昨天的所有订单按照时间排序分页展示如何做?这就是搜索型NoSql的另一大作用了,我们可以把分表之后的数据统一打在搜索型NoSql中,利用搜索型NoSql的搜索与聚合能力完成对全量数据的查询。
至于为什么把它放在KV型NoSql后面作为第二个写呢,因为通常搜索型NoSql也会作为一层前置缓存,来对关系型数据库进行保护。
列式NoSql,大数据时代最具代表性的技术之一了,以HBase为代表。
列式NoSql是基于列式存储的,那么什么是列式存储呢,列式NoSql和关系型数据库一样都有主键的概念,区别在于关系型数据库是按照行组织的数据:
看到每行有name、phone、address三个字段,这是行式存储的方式,且可以观察id = 2的这条数据,即使phone字段没有,它也是占空间的。
列式存储完全是另一种方式,它是按每一列进行组织的数据:
这么做有什么好处呢?大致有以下几点:
第二点说到了数据压缩,什么意思呢,以比较常见的字典表压缩方式举例:
自己看图理解一下,应该就懂了。
接着继续讲讲优缺点,列式NoSql,以HBase为代表的,优点为:
说了这么多HBase的优点,又到了说HBase缺点的时候了:
因此HBase比较适用于那种KV型的且未来无法预估数据增长量的场景,另外HBase使用还是需要一定的经验,主要体现在RowKey的设计上。
坦白讲,根据我的工作经历,文档型NoSql我只有比较浅的使用经验,因此这部分只能结合之前的使用与网上的文章大致给大家介绍一下。
什么是文档型NoSql呢,文档型NoSql指的是将半结构化数据存储为文档的一种NoSql,文档型NoSql通常以JSON或者XML格式存储数据,因此文档型NoSql是没有Schema的,由于没有Schema的特性,我们可以随意地存储与读取数据,因此文档型NoSql的出现是解决关系型数据库表结构扩展不方便的问题的。
MongoDB是文档型NoSql的代表产品,同时也是所有NoSql产品中的明星产品之一,因此这里以MongoDB为例。按我的理解,作为文档型NoSql,MongoDB是一款完全和关系型数据库对标的产品,就我们从存储上来看:
看到,关系型数据库是按部就班地每个字段一列存,在MongDB里面就是一个JSON字符串存储。关系型数据可以为name、phone建立索引,MongoDB使用createIndex命令一样可以为列建立索引,建立索引之后可以大大提升查询效率。其他方面而言,就大的基本概念,二者之间基本也是类似的:
因此,对于MongDB,我们只要理解成一个Free-Schema的关系型数据库就完事了,它的优缺点比较一目了然,优点:
缺点在于:
总而言之,MongDB的使用场景很大程度上可以对标关系型数据库,但是比较适合处理那些没有join、没有强一致性要求且表Schema会常变化的数据。
最后一部分,做一个总结,本文归根到底是两个话题:
首先是第一个话题,关系型数据库与非关系型数据库的选择,在我理解里面无非就是两点考虑:
第一点,不多解释应该都理解,非关系型数据库都是通过牺牲了ACID特性来获取更高的性能的,假设两张表之间有比较强的一致性需求,那么这类数据是不适合放在非关系型数据库中的。
第二点,核心数据不走非关系型数据库,例如用户表、订单表,但是这有一个前提,就是这一类核心数据会有多种查询模式,例如用户表有ABCD四个字段,可能根据AB查,可能根据AC查,可能根据D查,假设核心数据,但是就是个KV形式,比如用户的聊天记录,那么HBase一存就完事了。
这几年的工作经验来看,非核心数据尤其是日志、流水一类中间数据千万不要写在关系型数据库中,这一类数据通常有两个特点:
一旦使用关系型数据库作为存储引擎,将大大降低关系型数据库的能力,正常读写QPS不高的核心服务会受这一类数据读写的拖累。
接着是第二个问题,如果我们使用非关系型数据库作为存储引擎,那么如何选型?其实上面的文章基本都写了,这里只是做一个总结(所有的缺点都不会体现事务这个点,因为这是所有NoSql相比关系型数据库共有的一个问题):
但是这里特别说明,选型一定要结合实际情况而不是照本宣科,比如:
所以,如果不考虑实际情况,虽然合适有些存储引擎更加合适,但是强行使用反而适得其反,总而言之,适合自己的才是最好的。
]]>