博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
架构必备(转)
阅读量:5299 次
发布时间:2019-06-14

本文共 6700 字,大约阅读时间需要 22 分钟。

1. tcp-server状态上报

    get-tcp-ip接口怎么知道tcp-server集群中各台服务器是否可用呢,tcp-server主动上报是一个潜在方案,如果某一个tcp-server挂了,则会终止上报,对于停止上报状态的tcp-server,get-tcp-ip接口,将不返回给client相应的tcp-server的外网IP。

    该设计的存在的问题?

    诚然,状态上报解决了tcp-server高可用的问题,但这个设计犯了一个“反向依赖”的耦合小错误:使得tcp-server要依赖于一个与本身业务无关的web-server。

2. tcp-server状态拉取

    更优的方案是:web-server通过“拉”的方式获取各个tcp-server的状态,而不是tcp-server通过“推”的方式上报自己的状态。

    这样的话,每个tcp-server都独立与解耦,只需专注于资深的tcp业务功能即可。

     高可用、负载均衡、扩展性等任务由get-tcp-ip的web-server专注来执行。

3. 多说一句,将负载均衡实现在服务端,还有一个好处,可以实现异构tcp-server的负载均衡,以及过载保护:

    静态实施:web-server下的多个tcp-server的IP可以配置负载权重,根据tcp-server的机器配置分配负载(nginx也有类似的功能)

     动态实施:web-server可以根据“拉”回来的tcp-server的状态,动态分配负载,并在tcp-server性能极具下降时实施过载保护

4. 很多时候,业务需要跨公网调用一个第三方服务提供的接口,为了避免每个调用方都依赖于第三方服务,往往会抽象一个服务:

     解除调用方与第三方接口的耦合

     当第三方的接口变动时,只有服务需要修改,而不是所有调用方均修改

     问题:内部服务可能对上游业务提供了很多服务接口,当有一个接口跨公网第三方调用超时时,可能导致所有接口都不可用,即使大部分接口不依赖于跨公网第三方调用。

     为什么会出现这种情况呢?

     内部服务对业务方提供的N个接口,会共用服务容器内的工作线程(假设有100个工作线程)。

     假设这N个接口的某个接口跨公网依赖于第三方的接口,发生了网络抖动,或者接口超时(不妨设超时时间为5秒)。

     潜台词是,这个工作线程会被占用5秒钟,然后超时返回业务调用方。

     假设这个请求的吞吐量为20qps,言下之意,很短的时间内,所有的100个工作线程都会被卡在这个第三方超时等待上,而其他N-1个原本没有问题的接口,也得不到工作线程处理。

     潜在优化方案:

     增大工作线程数(不根本解决问题)

     降低超时时间(不根本解决问题)

     垂直拆分,N个接口拆分成若干个服务,使得在出问题时,被牵连的接口尽可能少(依旧不根本解决问题,难道一个服务只提供一个接口吗?)

5. 跨公网调用第三方,可能存在的问题:

    公网抖动,第三方服务不稳定,影响自身服务

     一个接口超时,占住工作线程,影响其他接口

     降低影响的优化方案:

     增大工作线程数

     降低超时时间

     服务垂直拆分

     业务需求决定技术方案,结合业务的解决方案:

     业务能接受旧数据:读取本地数据,异步代理定期更新数据

     有多个第三方服务提供商:多个第三方互备

     向第三方同步数据:本地写成功就算成功,异步向第三方同步数据

 

6. 架构设计时,能够巧用dns做一些什么事情:

     1). 反向代理水平扩展:在dns-server对于同一个域名可以配置多个nginx的外网ip,每次dns解析请求,轮询返回不同的ip,这样就能实现nginx的水平扩展,这个方法叫“dns轮询”。

     2). web-server负载均衡:

          既然“dns轮询”可以将同一个域名的流量均匀分配到不同的nginx,那么也可以利用它来做web-server的负载均衡:

          (1)架构中去掉nginx层

          (2)将多个web-server的内网ip直接改为外网ip

          (3)在dns-server将域名对应的外网ip进行轮询解析

          和nginx相比,dns来实施负载均衡有什么优缺点呢?

           优点:

           利用第三方dns实施,服务端架构不用动

           少了一层网络请求

           不足:

           dns只具备解析功能,不能保证对应外网ip的可用性(即使能够做80口的探测,实时性肯定也是比nginx差很多的),而nginx做反向代理时,与web-server之间有保活探测机制,当web-server挂掉时,能够自动迁移流量

           当web-server需要扩容时,通过dns扩容生效时间长,而nginx是服务端完全自己可控的部分,web-server扩容更实时更方便

           因为上面两个原因,架构上很少取消反向代理层,而直接使用dns来实施负载均衡。

      3).用户就近访问:

           http请求的第一个步骤域名到外网ip的转换,发生在整个服务端外部,服务端不可控,那么如果要实施“根据客户端ip来分配最近的服务器机房访问”,就只能在dns-server上做了:

           (1)电信用户想要访问某一个服务器资源

           (2)浏览器向dns-server发起服务器域名解析请求

           (3)dns-server识别出访问者是电信用户

           (4)dns-server将电信机房的nginx外网ip返回给访问者

           (5)访问者就近访问

           根据用户ip来返回最近的服务器ip,称为“智能dns”,cdn以及多机房多活中最常用。 

           总结,架构设计中,dns有它独特的功能和作用:

           1).dns轮询,水平扩展反向代理层

           2).去掉反向代理层,利用dns实施负载均衡

           3).智能dns,根据用户ip来就近访问服务器

 

7. 负载均衡:

     1).做两台nginx组成一个集群,分别部署上keepalived,设置成相同的虚IP,保证nginx的高可用;当一台nginx挂了,keepalived能够探测到,并将流量自动迁移到另一台nginx上,整个过程对调用方透明

          优点:1)解决了高可用的问题

          缺点:1)资源利用率只有50%

                    2)nginx仍然是接入单点,如果接入吞吐量超过的nginx的性能上限怎么办,例如qps达到了50000咧

    2).nginx毕竟是软件,性能比tomcat好,但总有个上限,超出了上限,还是扛不住。lvs就不一样了,它实施在操作系统层面;f5的性能又更好了,它实施在硬件层面;它们性能比nginx好很多,例如每秒可以抗10w,这样可以利用他们来扩容

         99.9999%的公司到这一步基本就能解决接入层高可用、扩展性、负载均衡的问题。

         这就完美了嘛?还有潜在问题么?

         好吧,不管是使用lvs还是f5,这些都是scale up的方案,根本上,lvs/f5还是会有性能上限,假设每秒能处理10w的请求,一天也只能处理80亿的请求(10w秒吞吐量*8w秒),那万一系统的日PV超过80亿怎么办呢?(好吧,没几个公司要考虑这个问题)

     3).如之前文章所述,水平扩展,才是解决性能问题的根本方案,能够通过加机器扩充性能的方案才具备最好的扩展性。

          facebook,google,baidu的PV是不是超过80亿呢,它们的域名只对应一个ip么,终点又是起点,还是得通过DNS轮询来进行扩容:

          此时:

           1)通过DNS轮询来线性扩展入口lvs层的性能

           2)通过keepalived来保证高可用

           3)通过lvs来扩展多个nginx

           4)通过nginx来做负载均衡,业务七层路由 

 

8. 信息安全:

     1).黑客定理一:网络上传递的数据是不安全的,属于黑客公共场所,能被截取

     2).黑客定理二:客户端的代码是不安全的,属于黑客本地范畴,能被逆向工程,任何客户端与服务端提前约定好的算法与密钥都是不安全的

     3).黑客定理三:用户客户端内存是安全的,属于黑客远端范畴,不能被破解。(使用“具备用户特性的东西(如密码散列)”作为加密密钥,一人一密,是安全的。只是,当“具备用户特性的东西”泄漏,就有潜在风险。)

     4).对于不同加密方法明:

         明文消息传递如同裸奔,不安全

         客户端和服务端提前约定加密算法和密钥,不安全(好多公司都是这么实现的=_=)

         服务端随机生成密钥,发送给客户端,不安全

         一人一密,客户端使用“具备用户特性的东西”作为加密密钥,弱安全

         一次一密,三次握手建立安全信道,安全

    5). 特点:每次通信前,进行密钥协商,一次一密

         密钥协商过程,如下图所述,需要随机生成三次密钥,两次非对称加密密钥(公钥,私钥),一次对称加密密钥,简称安全信道建立的“三次握手”,在客户端发起安全信道建立请求后:

         服务端随机生成公私钥对(公钥pk1,私钥pk2),并将公钥pk1传给客户端  (注意:此时黑客能截获pk1)  

         客户端随机生成公私钥对(公钥pk11,私钥pk22),并将公钥pk22,通过pk1加密,传给服务端 (注意:此时黑客能截获密文,也知道是通过pk1加密的,但由于黑客不知道私钥pk2,是无法解密的) 

         服务端收到密文,用私钥pk2解密,得到pk11 

         服务端随机生成对称加密密钥key=X,用pk11加密,传给客户端(注意:同理,黑客由密文无法解密出key)

         客户端收到密文,用私钥pk22解密,可到key=X

         至此,安全信道建立完毕,后续通讯用key=X加密,以保证信息的安全性

 

9. 通过上面的分析,Worker线程在执行的过程中,有一部计算时间需要占用CPU,另一部分等待时间不需要占用CPU,通过量化分析,例如打日志进行统计,可以统计出整个Worker线程执行过程中这两部分时间的比例,例如:

     执行计算,占用CPU的时间(粉色时间轴)是100ms

     等待时间,不占用CPU的时间(橙色时间轴)也是100ms

     得到的结果是,这个线程计算和等待的时间是1:1,即有50%的时间在计算(占用CPU),50%的时间在等待(不占用CPU):

     假设此时是单核,则设置为2个工作线程就可以把CPU充分利用起来,让CPU跑到100%

     假设此时是N核,则设置为2N个工作现场就可以把CPU充分利用起来,让CPU跑到N*100%

      结论:

      N核服务器,通过执行业务的单线程分析出本地计算时间为x,等待时间为y,则工作线程数(线程池线程数)设置为 N*(x+y)/x,能让CPU的利用率最大化。

      经验:

      一般来说,非CPU密集型的业务(加解密、压缩解压缩、搜索排序等业务是CPU密集型的业务),瓶颈都在后端数据库访问或者RPC调用,本地CPU计算的时间很少,所以设置几十或者几百个工作线程是能够提升吞吐量的。 

 

10. 总结

      1).线程数不是越多越好

      2).sleep()不占用CPU

      3).单核设置多线程不但能使得代码清晰,还能提高吞吐量

      4).站点和服务最常用的线程模型是“IO线程与工作现场通过任务队列解耦”,此时设置多工作线程可以提升吞吐量

      5).N核服务器,通过日志分析出任务执行过程中,本地计算时间为x,等待时间为y,则工作线程数(线程池线程数)设置为 N*(x+y)/x,能让CPU的利用率最大化

   

11. 不止sleep()函数,在进行一些阻塞调用时,例如网络编程中的:阻塞accept(),等待客户端连接; 阻塞recv(),等待下游回包; 都不占用CPU资源。

 

12. 业务有定时任务或者定时超时的需求,当任务量很大时,可能需要维护大量的timer,或者进行低效的扫描。

      1).“轮询扫描法”

           a.用一个Map<uid, last_packet_time>来记录每一个uid最近一次请求时间last_packet_time

           b.当某个用户uid有请求包来到,实时更新这个Map

           c.启动一个timer,当Map中不为空时,轮询扫描这个Map,看每个uid的last_packet_time是否超过30s,如果超过则进行超时处理

              问题:只启动一个timer,但需要轮询,效率较低

      2).“多timer触发法”

           a.用一个Map<uid, last_packet_time>来记录每一个uid最近一次请求时间last_packet_time

           b.当某个用户uid有请求包来到,实时更新这个Map,并同时对这个uid请求包启动一个timer,30s之后触发

           c.每个uid请求包对应的timer触发后,看Map中,查看这个uid的last_packet_time是否超过30s,如果超过则进行超时处理

               问题:不需要轮询,但每个请求包要启动一个timer,比较耗资源

       3).环形队列法:

            三个重要的数据结构:

            1)30s超时,就创建一个index从0到30的环形队列(本质是个数组)

            2)环上每一个slot是一个Set<uid>,任务集合

            3)同时还有一个Map<uid, index>,记录uid落在环上的哪个slot里

             同时:

             1)启动一个timer,每隔1s,在上述环形队列中移动一格,0->1->2->3…->29->30->0…

             2)有一个Current Index指针来标识刚检测过的slot

             当有某用户uid有请求包到达时:

              1)从Map结构中,查找出这个uid存储在哪一个slot里 

              2)从这个slot的Set结构中,删除这个uid

              3)将uid重新加入到新的slot中,具体是哪一个slot呢 => Current Index指针所指向的上一个slot,因为这个slot,会被timer在30s之后扫描到

              4)更新Map,这个uid对应slot的index值

              哪些元素会被超时掉呢?

              Current Index每秒种移动一个slot,这个slot对应的Set<uid>中所有uid都应该被集体超时!如果最近30s有请求包来到,一定被放到Current Index的前一个slot了,Current Index所在的slot对应Set中所有元素,都是最近30s没有请求包来到的。

               所以,当没有超时时,Current Index扫到的每一个slot的Set中应该都没有元素。

              优势:

              (1)只需要1个timer

              (2)timer每1s只需要一次触发,消耗CPU很低

              (3)批量超时,Current Index扫到的slot,Set中所有元素都应该被超时掉

                这个环形队列法是一个通用的方法,Set和Map中可以是任何task,本文的uid是一个最简单的举例。

                 HashedWheelTimer也是类似的原理,有兴趣的同学可以百度一下这个数据结构,Netty中的一个工具类

 

13. URI设计原则

       1). URI的末尾不要添加“/”

             多一个斜杠,语义完全不同,究竟是目录,还是资源,还是不确定而多做一次301跳转?

             负面case:http://api.canvas.com/shapes/ 

             正面case:http://api.canvas.com/shapes

       2).使用“-”提高URI的可读性

           目的是使得URI便于理解,用“-”来连接单词

            正面case:http://api.example.com/blogs/my-first-post

        3).禁止在URL中使用“_”

            目的是提高可读性,“_”可能被文本查看器中的下划线特效遮蔽

             负面case:http://api.example.com/blogs/my_first_post

             别争,看到效果就明白了

        4).禁止使用大写字母

             RFC 3986中规定URI区分大小写,但别用大写字母来为难程序员了,既不美观,又麻烦

             负面case:http://api.example.com/My-Folder/My-Doc 

             正面case:http://api.example.com/my-folder/my-doc 

        5).不要在URI中包含扩展名

            应鼓励REST API客户端使用HTTP提供的格式选择机制Accept request header

            正面case:http://58.com/bj/ershou/310976

            一个case:http://58.com/bj/ershou/310976x.shtml

        6).建议URI中的名称使用复数

            额,楼主不知道为何会有这么奇怪的建议

            正面case:http://api.college.com/students/3248234/courses

           负面case:http://api.college.com/student/3248234/course

 

转载于:https://www.cnblogs.com/Jtianlin/p/8894018.html

你可能感兴趣的文章
php server port,$_SERVER[‘SERVER_PORT’]关于php5.2一个bug
查看>>
php 类 init,PHP内核探索:类的定义
查看>>
java的二叉树树一层层输出,Java构造二叉树、树形结构先序遍历、中序遍历、后序遍历...
查看>>
meep php,麻省理工时域差分软件 MEEP windows 下编译开发(一)——准备工作
查看>>
matlab的清除0,matlab中的平均值clear %清除变量dx=0.01*2*pi; %间隔x=0:dx:2*pi; %自变量向量y=...
查看>>
php 循环套 重复,php 循环套循环 出现重复数据
查看>>
mysql distince,MySQL学习(未完待续)
查看>>
php libevent 定时器,PHP 使用pcntl和libevent实现Timer功能
查看>>
对数字进行 混淆 php,解密混淆的PHP程序
查看>>
zencart不支持php7的原因,Zen Cart1.3.8产品页报错提示:Deprecated: Function ereg_replace() is deprecated...
查看>>
php仿阿里巴巴,php实现的仿阿里巴巴实现同类产品翻页
查看>>
matlab fis编辑器在哪,基本FIS编辑器
查看>>
linux的串口子系统,TTY子系统
查看>>
修改linux远程22端口,linux修改ssh远程端口22
查看>>
Linux系统的创始者,组图:Linux之父的办公室首度曝光
查看>>
关于linux的环境变量设置,linux环境变量设置
查看>>
pLC中C语言的运算符是什么,c语言中的“?:”是什么运算符
查看>>
android 耳机检测,检测耳机是否已插入Android设备。
查看>>
android开发常用app有哪些,android 开发之app都可以进行哪些优化
查看>>
android按钮显示文字,android 按钮的文字显示不全
查看>>