精华内容
下载资源
问答
  • 高并发技术

    万次阅读 多人点赞 2019-10-21 16:45:04
    高并发技术 第一章 预备知识一 理解大数据二 网工基础知识OSI七层参考模型应用层表示层会话层传输层网络层链路层物理层功能分层总结第二章 LVS技术一 LVS介绍二 LVS调度算法LVS命令监控多个端口号管理服务集群中的...

    第一章 预备知识

    一 理解大数据

    我国是人口大国同时也是数据大国, 由数据的量(数以亿计)变产生了质变 , 我们步入了大数据时代.
    而大数据也带来的高并发的问题. 解决高并发问题是大数据时代的永恒主题.

    我们假设已经解决高并发的问题, 我们可以通过对数以亿计的数据做日志分析 ,
    从中分析用户行为 ,分析在哪个渠道的用户最具购买力 , 哪个渠道最容易接纳我们的产品.
    进而对用户行为进行建模 ,分析用户喜好 ,根据这些喜好可以推荐给用户一些个性化服务.
    即: 高并发>日志>分析行为>画像>推荐>服务
    这便是大数据时代下企业发展之路 ,因此 ,解决高并发问题便是关键.

    通过相应技术, 解决高并发问题 ,为企业节省更多资金 ,有益企业良性发展.
    这便是大数据技术发展的不竭动力之源.

    二 网工基础知识

    OSI七层参考模型

    OSI是Open System Interconnection的缩写,意为开放式系统互联。国际标准化组织(ISO)制定了OSI模型,该模型定义了不同计算机互联的标准,是设计和描述计算机网络通信的基本框架。OSI七层模型如下:

    所在层所在层名称
    第七层应用层 ( nginx 软件 )
    第六层表示层
    第五层会话层
    第四层传输层 /运输层( lvs 内核 )
    第三层网络层
    第二层链路层 / 数据链路层
    第一层物理层

    应用层

    网络应用层是通信用户之间的窗口,为用户提供网络管理、文件传输、事务处理等服务
    这一层设计的主要问题是分布数据库、分布计算技术、网络操作系统和分布操作系统、
    远程文件传输、电子邮件、终端电话及远程作业登录与控制等。
    应用层为操作系统或网络应用程序提供访问网络服务的接口

    应用层协议的代表包括
    Telnet(远程登录协议)、
    FTP(文件传输协议)、
    HTTP(超文本传输协议)、
    SNMP(简单网络管理协议)、
    DNS(域名解析协议)等。

    表示层

    表示层向上对应用层提供服务,向下接收来自会话层的服务。
    表示层要完成某些特定的功能,主要有不同数据编码格式的转换,提供数据压缩、解压缩服务,
    对数据进行加密、解密。例如图像格式的显示,就是由位于表示层的协议来支持。
    表示层为应用层提供服务包括语法选择、语法转换等。语法选择是提供一种初始语法和以后修改这种选择的手段。
    语法转换涉及代码转换和字符集的转换、数据格式的修改以及对数据结构操作的适配。

    会话层

    提供包括 访问验证 和 会话管理 在内的 建立和维护 应用之间通信的 机制(如服务器验证用户登录)。
    会话层提供的服务可使应用建立和维持会话,并能使会话获得同步。
    会话层使用校验点可使通信会话在通信失效时从校验点继续恢复通信。
    会话层同样要担负应用进程服务要求,而运输层不能完成的那部分工作,给运输层功能差距以弥补。
    主要的功能是对话管理,数据流同步和重新同步。

    传输层

    传输层建立在网络层和会话层之间,实质上它是网络体系结构中高低层之间衔接的一个接口层。
    用一个寻址机制来标识一个特定的应用程序(端口号)。
    传输层不仅是一个单独的结构层,它还是整个分层体系协议的核心,没有传输层整个分层协议就没有意义。
    传输层的数据单元是由数据组织成的数据段(segment)这个层负责获取全部信息,
    传输层最重要的两个协议:TCP , UDP协议

    传输层主要负责的行为
    三次握手>>(传输数据)>>四次分手

    小技巧
    利用 netstat -natp可查看虚拟机建立的tcp连接

    在这里插入图片描述

    网络层

    网络层也称通信子网层,是高层协议之间的界面层,用于控制通信子网的操作,是通信子网与资源子网的接口。
    如果你在谈论一个IP地址,那么你是在处理第3层的问题,这是“数据包”问题,而不是第2层的“帧”。
    IP是第3层问题的一部分,此外还有一些路由协议和地址解析协议(ARP)。
    有关路由的一切事情都在第3层处理。地址解析和路由是3层的重要目的 ,是路由器所属层
    网络层还可以实现拥塞控制、网际互连、信息包顺序控制及网络记账等功能。

    网络层协议的代表包括
    IP(网际协议:目前应用最广的网络互联协议, 消除物理网络差异性 ,使网络互连成为可能)
    OSPF(开放式最短路径优先, 属于内部网关协议)等。

    小技巧
    通过 route -n 查看虚拟机内核的IP路由表信息
    在这里插入图片描述

    链路层

    在物理层提供比特流服务的基础上,将比特信息封装成数据帧Frame,
    起到在物理层上建立、撤销、标识逻辑链接和链路复用以及差错校验等功能。
    数据链路层在不可靠的物理介质上提供可靠的传输 ,是交换机所在的层
    该层的作用包括:物理地址寻址、数据的成帧、流量控制、数据的检错、重发等。
    在这一层,数据的单位称为帧(frame)。

    数据链路层协议的代表包括:
    ARP协议(地址解析协议:根据IP地址获取物理地址的一个TCP/IP协议)、
    SDLC、HDLC、PPP、STP、帧中继等。

    小技巧
    通过arp -a 查询虚拟机ARP缓存中IP地址和MAC地址的对应关系
    在这里插入图片描述

    物理层

    物理层是OSI分层结构体系中最重要、最基础的一层,
    建立在传输媒介基础上,起建立、维护和取消物理连接作用,实现设备之间的物理接口。
    物理层只接收和发送一串比特(bit)流,不考虑信息的意义和信息结构。

    功能分层

    层与层依赖

    1. 能够申请到端口号
    2. 路由表有下一跳条目
    3. ARP能请求到下一跳MAC
    4. 三次握手
    5. 传输数据
    6. 四次分手

    在这里插入图片描述

    总结

    整个互联网建立在下一跳的模式下
    IP是逻辑上的两个端点
    MAC是物理上连接的两个节点

    端点间TCP传输过程中
    确认机制
    状态机制
    不可分割

    解析数据包需要成本
    交换机:二层,只关心MAC地址 学习机制
    路由器:三层,只关心IP和路由表
    LVS服务器:四层,只关心PORT,状态
    nginx:七层,关心socket对应关系

    第二章 LVS技术

    一 LVS介绍

    LVS是Linux Virtual Server的简写,即Linux虚拟服务器,是一个虚拟的服务器集群系统

    相关概念

    1. ipvs : 嵌入到linux的内核
    2. ipvsadm:管理应用程序
    3. VIP: 虚拟服务器地址
    4. DIP: 转发的网络地址
      和RIP通信:ARP协议,获取Real Server的RIP:MAC地址
      转发Client的数据包到RIP上(隐藏的VIP)
    5. RIP: 后端真实主机(后端服务器)
    6. CIP: 客户端IP地址

    在这里插入图片描述

    工作模式

    1. LVS -D_NAT 地址转换

    NAT(Network Address Translation,网络地址转换):
    当在专用网内部的一些主机本来已经分配到了本地IP地址(即仅在本专用网内使用的专用地址),
    但现在又想和因特网上的主机通信时,可使用NAT方法

    这种方法需要在专用网连接到因特网的路由器上安装NAT软件。
    装有NAT软件的路由器叫做NAT路由器,它至少有一个有效的外部全球IP地址
    所有使用本地地址的主机在和外界通信时,都要在NAT路由器上将本地地址转换成全球IP地址,才能连接网络。
    私有ip不能出现再公网上,所以需要net转换,
    nat交换机将源id端口转换为net交换机分配到的公网ip,并记录下对于的内网主机地址,
    收到请求后根据目标地址转换为内网地址

    在这里插入图片描述

    2. LVS -DR MAC欺骗 直接路由

    这种方式不会修改源ip地址和目标ip地址,会在第二层数据链路层时修改目标MAC地址,
    将其修改为server到的MAC地址。成为MAC欺骗。
    又因为MAC基于下一跳的机制,所以server和负载均衡器必须位于同一个局域网。
    server端使用隐藏的vip发送socket,这样源ip对应请求的目标ip,同时降低负载均衡器的IO.

    在这里插入图片描述

    3. LVS -TUN 隧道

    使用ip隧道技术,将请求封装成ipTUN,server收到ipTUN后解包并处理请求。
    响应机制使用隐藏的VIP

    在这里插入图片描述

    二 LVS调度算法

    静态调度算法

    简称全称
    rr轮询调度(Round-Robin Scheduling)
    wrr加权轮询调度(Weighted Round-Robin Scheduling)
    dh目标地址散列调度(Destination Hashing Scheduling)
    sh源地址散列调度(Source Hashing Scheduling)

    动态调度算法

    简称全称
    lc最小连接调度(Least-Connection Scheduling)
    wlc加权最小连接调度(Weighted Least-Connection Scheduling)
    sed最短的期望的延迟调度(Shortest Expected Delay’)
    nq最少队列调度(Never Queue )
    lblc基于局部性的最少链接(Locality-Based Least Connections Scheduling)
    lblcr带复制的基于局部性最少链接(Locality-Based Least Connections with Replication Scheduling)

    默认方法:wlc
    点击了解LVS三种工作模式与10种调度算法详细

    LVS命令

    监控多个端口号

    # ipvs内核模块
    yum install ipvsadm -y
    
    # 管理集群服务
    #添加:
    -A -t|u|f service-address [-s scheduler]
    	-t: TCP协议的集群 
    	-u: UDP协议的集群
    	service-address:     IP:PORT
    	-f: FWM: 防火墙标记 
    	service-address: Mark Number
    #修改:-E
    #删除:
    -D -t|u|f service-address
    
    eg: 
    ipvsadm -A -t 192.168.9.100:80 -s rr
    
    

    管理服务集群中的Real Serever(RS)

    #添加:
    -a -t|u|f service-address -r server-address [-g|i|m] [-w weight]
    	-t|u|f service-address:事先定义好的某集群服务
    	-r server-address: 某RS的地址,在NAT模型中,可使用IP:PORT实现端口映射;
    		[-g|i|m]: LVS类型	
    		-g: DR
    		-i: TUN
    		-m: NAT
    		[-w weight]: 定义服务器权重
    #修改:-e
    #删除:
    -d -t|u|f service-address -r server-address
    #eg:
     ipvsadm -a -t 172.16.100.1:80 -r 192.168.10.8 –g
     ipvsadm -a -t 172.16.100.1:80 -r 192.168.10.9 -g
    
    #查看
    -L|l
    -n: 数字格式显示主机地址和端口
    	--stats:统计数据
    	--rate: 速率
    	--timeout: 显示tcp、tcpfin和udp的会话超时时长
    -:c 显示当前的ipvs连接状况
    
    #删除所有集群服务
    -C:清空ipvs规则
    -S: 保存规则
    -R : 载入此前的规则
    #eg:
     ipvsadm -S > /path/to/somefile
     ipvsadm -R < /path/form/somefile
    

    三. LVS-DR实现

    LVS-DR实验拓扑图

    在这里插入图片描述

    实现步骤

    vip :虚拟ip
    RS:real服务器

    1. 准备3台虚拟机
      node1作为lvs服务器
      node2,node3作为Real Server

    2. 先配置3台虚拟机的网络
      eth0,配置在一个网段
      DIP,RIP在一个网段

    3. 配置lvs的VIP(node1主机)

      # 临时配置 ,重启后配置消失
      # 设置vip 需要根据自己所在网段配置, 例如我的网段 192.168.179.0,因此我随便设置了一个192.168.179.100
      ifconfig eth0:2 192.168.179.100/24
      
      # 设置转发级别, 	/proc不允许使用vim进行修改 ,因为使用vi命令会在目录下创建交换文件(临时配置)
      echo "1" > /proc/sys/net/ipv4/ip_forward
      
    4. 调整RS的响应。通告级别(每一台RS都配, node2, node3)

      # node2
      echo 1  > /proc/sys/net/ipv4/conf/eth0/arp_ignore
      echo 2  > /proc/sys/net/ipv4/conf/eth0/arp_announce
      # node3
      echo 1  > /proc/sys/net/ipv4/conf/all/arp_ignore
      echo 2  > /proc/sys/net/ipv4/conf/all/arp_announce
      
    5. 配置RS的VIP(每一台RS都配, node2, node3)

      ifconfig lo:8 192.168.179.100 netmask 255.255.255.255
      

      在这里插入图片描述

    6. 启动RS上的httpd(node2, node3)

      # 下载httpd服务
      yum install -y httpd 
      
      # 进入相关目录
      cd /var/www/html
      
      # 添加主页内容
      vi index.html  
      ------------------index.html------------
      from ooxxip(各自ip)
      ------------------index.html------------
      
      # 启动服务
      service httpd start
      
      # 关闭防火墙(node1,node2,node3主机)
      service iptables stop
      

      客户端验证:
      VIP:80不能显示(node1)
      访问的是node的vip: 192.168.179.100 ,而不是真实 ip:192.168.179.140

      RIP:80 能显示(node2,node3)
      在这里插入图片描述
      在这里插入图片描述

    7. LVS——ipvsadm(node1)

      # 下载对lvs命令的支持
      yum install -y ipvsadm 
      
      # 设置监控的包
      ipvsadm -A -t 192.168.179.100:80 -s rr
      # 添加real server(noed2, node3),-g直接路由,
      ipvsadm -a -t 192.168.179.100:80 -r 192.168.179.141 -g 
      ipvsadm -a -t 192.168.179.100:80 -r 192.168.179.142 -g
      # 查看配置是否成功(图1)
      ipvsadm -ln
      
      # 浏览器刷新: 访问vip(node1),然后F5刷新,测试配置的轮询算法是否生效,并多次重复刷新
      访问的是node的vip: 192.168.179.100
      而不是真实 ip:192.168.179.140
      
      # 查看tcp协议连接的有哪些进程
      netstat -natp
      # 验证lVS偷窥/负载记录(图2)
      ipvsadm -lnc
      

      图1
      在这里插入图片描述
      图2
      在这里插入图片描述

    LVS简单总结
    负载均衡
    四层(传输层)
    不会握手——高速转发
    丰富的调度算法

    缺点:
    后端:镜像服务器 ,没有健康检查机制
    自身:单点故障(如果lvs出现故障,会导致服务不可用; 如果RS出现故障会出现数据倾斜)

    没有解决的问题:
    后端服务器如果臃肿,由计算和io瓶颈,lvs是无能为力的
    这里就引出下面了Keepalived技术

    第三章 Keepalived

    keepalived是集群管理中保证集群高可用的服务软件

    一 高可用 High Available

    1. 需要心跳机制探测后端RS是否提供服务。
      a) 探测down,需要从lvs中删除该RS
      b) 探测发送从down到up,需要从lvs中再次添加RS。
    2. Lvs DR,需要主备(HA)
      Keepalived 原理:
      VRRP协议(虚拟路由冗余协议) - Virtual Router Redundancy Protocol
      IP漂移

    Keepalived下的lvs架构图

    添加了用于备用的lvs服务器 , keepalived通过心跳检测的形式检测lvs和rs ,
    如果主lvs出现问题则 ,立即为备份主机分配vip ,并令其生效
    在这里插入图片描述

    二 模拟实验配置

    node1 主lvs
    node4 备lvs
    node2,node3 real server(RS)
    每个节点node对应一台物理主机
    进行实验时,防火墙必须关闭

    # 1.清除上个实验lvs的配置(node1)
    ipvsadm -C
    
    # 2.查看相关配置是否清除完毕
    ipvsadm -in
    
    # 3. 关闭上个实验node1中vip的设置
    ifconfig 
    ifconfig eth0:2 down
    
    # 4.打开node4, 可以不用安装ipvsadm -lnc ,安装 Keepalived ,keepalived(ipvsadm,自身高可用)
    # 配置RS的步骤还和上次实验一致
    yum install -y  keepalived ipvsadm 
    yum install -y ipvsadm   # 这里安装 ipvsadm不是用来配置而是用来查看内核相关接口情况
    
    # 5.在node1中安装Keepalived 
    yum install -y  keepalived
    
    # 6. 修改配置文件(建议备份一下)
    cd /etc/keepalived
    # 帮助手册
    man keepalived.conf
    cp keepalived.conf  keepalived.conf.bak
    vim keepalived.conf    
    # 修改内容在下一个代码块
    
    
    # 7. 远程复制(从 node1复制到node4 ,图1)
    scp ./keepalived.conf root@192.168.179.143:`pwd`
    # 修改node4的角色状态并降低权重
    vrrp_instance VI_1 {
        state BACKUP        #MASTER 主和从 
        priority 50      #优先级 主的优先级要高
    }
    
    
    # 8. 启动Keepalived(node1,node4)
    service keepalived start
    # 查看日志
    tail /var/log/messages 
    
    
    # 9. 测试
    # a.查看通过keepalived配置的lvs是否生效(如图2)
    ipvsadm -ln   # 对主备都进行查看
    # 访问 node1的vip,刷新浏览器,看轮询算法是否生效(图3)
    
    # b.模拟主lvs宕机 ,将node1网卡关闭 ,使node1与外界失联, 访问浏览器,查看轮询算法是否有异常(无异常:模拟用户对异常的无感知)
    ifconfig eth0 down
    # 模拟运维修复主lvs异常 ,在虚拟机中将网卡启动 ,查看虚拟接口 eth0:3是否从备机自动分配到了主机
    ifconfig eth0 up
    
    # c.关闭启动一个real server(node2/node3) ,查看keepalived是否将宕掉的real剔除(keepalived不仅对准备做健康检查还会对realserver做健康检查)
    service httpd stop  # 测试完毕后重启服务
    ipvsadm -ln        # 对主备都进行查看
    
    # d.模拟keepalived软件异常退出(在node1中执行)
    ps -ef | grep keep   # 定位keepalived的所有端口号
    kill  -9 端口号   # 所有都结束
    #这样做的结果是备机node4也会拥有接口eth0:3, 而导致主备都会拥有同一个vip的情况 
    #我们都知道在互联网中ip地址必须唯一,所以这样做是非常危险的,同时这也是keepalived的一个bug
    
    
    
    

    步骤5修改的内容代码

    # 主要是对端口号,virtual_ipaddress,persistence_timeout,lb_kind DR,心跳检查返回的状态的膝盖
    # 这里没出现的建议删除
    
    global_defs {
       notification_email {
         root@localhost  #发送提醒邮件的目标地址可有多个
         goldbin@126.com
      }
       notification_email_from test@localhost              #发送邮件的from地址,可以随意写,邮件地址不存在都无所谓
       smtp_server 127.0.0.1             #邮件服务的地址,一般写本地
       smtp_connect_timeout 30
       router_id LVS_DEVEL
    }
    
    
    vrrp_instance VI_1 {
        state MASTER        #MASTER 主和从 
        interface eth0        #VIP需要绑定的网卡名称
        virtual_router_id 51
        priority 100       #优先级 主的优先级要高
        advert_int 1
        authentication {
            auth_type PASS
            auth_pass 1111
        }
        virtual_ipaddress {  
            192.168.179.100/24 dev eth0 label eth0:3         #设置VIP
        }
    }
    
    
    virtual_server 192.168.179.100 80 {       #设置虚拟lvs服务,VIP PORT
        delay_loop 6
        lb_algo rr   #调度算法wrr
        lb_kind DR  #lvs的模式
        nat_mask 255.255.255.0
       # persistence_timeout 50 同一个IP地址在50秒内lvs转发给同一个后端服务器
        persistence_timeout 0
        protocol TCP
      
        real_server 192.168.179.141 80 {       #设置真实服务器的心跳机制 RID PORT
            weight 1      #权重
            HTTP_GET {      #心跳检测的方式
                url {
                  path /      #心跳检查的地址
                  status_code 200      #心跳检查返回的状态
                }
                connect_timeout 3       #超时时间
                nb_get_retry 3      #重复检查3次
                delay_before_retry 3      #每隔1秒钟再次检查
            }
        }
        real_server 192.168.179.142 80 {      #第二个真实服务器设置
          weight 1      #权重
            HTTP_GET {      #心跳检测的方式
                url {
                  path /      #心跳检查的地址
                  status_code 200      #心跳检查返回的状态
                }
                connect_timeout 2       #超时时间
                nb_get_retry 3      #重复检查3次
                delay_before_retry 3      #每隔1秒钟再次检查
            }
       }
    
    }
    

    图1
    在这里插入图片描述
    图2
    在这里插入图片描述

    图3
    在这里插入图片描述
    在这里插入图片描述

    第四章 Nginx和 Tengine

    一 介绍

    Nginx

    Nginx (“engine x”) 是一个高性能的 HTTP 和 反向代理服务器,也是一个 IMAP/POP3/SMTP 代理服务器。
    其将源代码以类BSD许可证的形式发布,因它的稳定性、丰富的功能集、
    示例配置文件和低系统资源的消耗而闻名 .官方测试nginx能够支撑5万并发链接,
    并且cpu、内存等资源消耗却非常低,运行非常稳定. 其特点是占有内存少,并发能力强,
    事实上nginx的并发能力确实在同类型的网页服务器中表现较好,
    中国大陆使用nginx网站用户有:新浪、网易、腾讯等。

    Tengine

    Tengine 是nginx的加强版,封装版 淘宝开源 ,官网http://tengine.taobao.org/
    动态模块加载(DSO)支持。加入一个模块不再需要重新编译整个Tengine;
    支持SO_REUSEPORT选项,建连性能提升为官方nginx的三倍;
    支持SPDY v3协议,自动检测同一端口的SPDY请求和HTTP请求;
    流式上传到HTTP后端服务器或FastCGI服务器,大量减少机器的I/O压力;
    更加强大的负载均衡能力,包括一致性hash模块、会话保持模块,还可以对后端的服务器进行主动健康检查,根据服务器状态自动上线下线,以及动态解析upstream中出现的域名;
    输入过滤器机制支持。通过使用这种机制Web应用防火墙的编写更为方便;
    支持设置proxy、memcached、fastcgi、scgi、uwsgi在后端失败时的重试次数
    动态脚本语言Lua支持。扩展功能非常高效简单;
    支持管道(pipe)和syslog(本地和远端)形式的日志以及日志抽样;
    支持按指定关键字(域名,url等)收集Tengine运行状态;
    组合多个CSS、JavaScript文件的访问请求变成一个请求;
    自动去除空白字符和注释从而减小页面的体积

    常用高并发模型设计

    利用lvs来管理Nginx ,每台Nginx可支撑五万链接 ,因此通过这样的架构可以轻松实现对百万链接支撑的请求

    在这里插入图片描述

    二 Nginx和apache(httpd)的优缺点

    1. nginx相对于apache的优点:
      轻量级,同样起web 服务,比apache 占用更少的内存及资源
      抗并发,nginx 处理请求是异步非阻塞的,而apache 则是阻塞型的,在高并发下nginx 能保持低资源低消耗
      高性能, 高度模块化的设计,编写模块相对简单 社区活跃,各种高性能模块出品迅速

    2. apache 相对于nginx 的优点:
      rewrite ,比nginx 的rewrite 强大
      模块多,基本想到的都可以找到
      少bug ,nginx 的bug 相对较多

    3. Nginx 配置简洁, Apache 复杂

    4. ,最核心的区别在于apache是同步多进程模型,一个连接对应一个进程;nginx是异步的,多个连接(万级别)可以对应一个进程

    三 安装Tengine并制作Nginx脚本

    Tengine集成了nginx ,所以我们只需要安装Tengine即可

    # 1、安装依赖 gcc openssl-devel pcre-devel zlib-devel
    yum install gcc openssl-devel pcre-devel zlib-devel -y
    
    # 2.上传Tengine,解压,复制
    # 要注意我们所复制后的目录以及复制后的文件名
    tar tengine-2.1.0.tar.gz -rf
    cp tengine-2.1.0 /usr/local/ -rf
    
    # 3. 在复制后的tengine-2.1.0目录下运行
    ./configure \
      --prefix=/usr/local/tengine-2.1.0
    
    
    # 4 .编译安装
    make && make install
    
    # 5. 上传脚本使nginx可以像服务一样启动 ,内容在下一个代码块
    cd /etc/init.d/
    chmod +x nginx 
    
    # 6、添加该文件到系统服务中去
    	chkconfig --add nginx
    	查看是否添加成功
    	chkconfig --list nginx
    
    # 7. 运行nginx服务
    service nginx  start
    
    # ps:如果出现没有相关文件的错误, 请使用touch目录创建
    # 在浏览器输入配置Tengine的ip即可访问,如下图
    

    脚本内容

    注意这里配置的路径要与我们make install所在的目录一致
    nginx="/usr/local/tengine-2.1.0/sbin/nginx"
    NGINX_CONF_FILE="/usr/local/tengine-2.1.0/conf/nginx.conf"

    #!/bin/sh
    #
    # nginx - this script starts and stops the nginx daemon
    #
    # chkconfig:   - 85 15 
    # description:  Nginx is an HTTP(S) server, HTTP(S) reverse \
    #               proxy and IMAP/POP3 proxy server
    # processname: nginx
    # config:      /etc/nginx/nginx.conf
    # config:      /etc/sysconfig/nginx
    # pidfile:     /var/run/nginx.pid
     
    # Source function library.
    . /etc/rc.d/init.d/functions
     
    # Source networking configuration.
    . /etc/sysconfig/network
     
    # Check that networking is up.
    [ "$NETWORKING" = "no" ] && exit 0
     
    nginx="/usr/local/tengine-2.1.0/sbin/nginx"
    prog=$(basename $nginx)
     
    NGINX_CONF_FILE="/usr/local/tengine-2.1.0/conf/nginx.conf"
     
    [ -f /etc/sysconfig/nginx ] && . /etc/sysconfig/nginx
     
    lockfile=/var/lock/subsys/nginx
     
    make_dirs() {
       # make required directories
       user=`nginx -V 2>&1 | grep "configure arguments:" | sed 's/[^*]*--user=\([^ ]*\).*/\1/g' -`
       options=`$nginx -V 2>&1 | grep 'configure arguments:'`
       for opt in $options; do
           if [ `echo $opt | grep '.*-temp-path'` ]; then
               value=`echo $opt | cut -d "=" -f 2`
               if [ ! -d "$value" ]; then
                   # echo "creating" $value
                   mkdir -p $value && chown -R $user $value
               fi
           fi
       done
    }
     
    start() {
        [ -x $nginx ] || exit 5
        [ -f $NGINX_CONF_FILE ] || exit 6
        make_dirs
        echo -n $"Starting $prog: "
        daemon $nginx -c $NGINX_CONF_FILE
        retval=$?
        echo
        [ $retval -eq 0 ] && touch $lockfile
        return $retval
    }
     
    stop() {
        echo -n $"Stopping $prog: "
        killproc $prog -QUIT
        retval=$?
        echo
        [ $retval -eq 0 ] && rm -f $lockfile
        return $retval
    }
     
    restart() {
        configtest || return $?
        stop
        sleep 1
        start
    }
     
    reload() {
        configtest || return $?
        echo -n $"Reloading $prog: "
        killproc $nginx -HUP
        RETVAL=$?
        echo
    }
     
    force_reload() {
        restart
    }
     
    configtest() {
      $nginx -t -c $NGINX_CONF_FILE
    }
     
    rh_status() {
        status $prog
    }
     
    rh_status_q() {
        rh_status >/dev/null 2>&1
    }
     
    case "$1" in
        start)
            rh_status_q && exit 0
            $1
            ;;
        stop)
            rh_status_q || exit 0
            $1
            ;;
        restart|configtest)
            $1
            ;;
        reload)
            rh_status_q || exit 7
            $1
            ;;
        force-reload)
            force_reload
            ;;
        status)
            rh_status
            ;;
        condrestart|try-restart)
            rh_status_q || exit 0
                ;;
        *)
            echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload|configtest}"
            exit 2
    esac
    

    Tengine配置成功图
    在这里插入图片描述

    四 配置文件nginx.conf

    位于Nginx编译安装后指定的目录
    cd /usr/local/tengine-2.1.0/conf/nginx.conf

    配置文件解读

    #1.定义Nginx运行的用户和用户组
    user root root;
    
    #nginx进程数,建议设置为等于CPU总核心数。
    worker_processes 1;
    
    #全局错误日志定义类型,[ debug | info | notice | warn | error | crit ]
    error_log /var/log/nginx/error.log info;
    
    #进程文件
    pid /var/run/nginx.pid;
    
    #一个nginx进程打开的最多文件描述符数目,理论值应该是最多打开文件数(系统的值ulimit -n)与nginx进程数相除,但是nginx分配请求并不均匀,所以建议与ulimit -n的值保持一致。
    worker_rlimit_nofile 65535;
    #工作模式与连接数上限
    events {
        #参考事件模型,use [ kqueue | rtsig | epoll | /dev/poll | select | poll ]; epoll模型是Linux 2.6以上版本内核中的高性能网络I/O模型,如果跑在FreeBSD上面,就用kqueue模型。
        use epoll;
        #单个进程最大连接数(总的最大连接数=连接数*进程数)
        worker_connections 1024;
    }
    
    
    #2. event下的一些配置及其意义
     #单个后台worker process进程的最大并发链接数    
        #worker_connections  1024;
        # 并发总数是 worker_processes 和 worker_connections 的乘积
        # 即 max_clients = worker_processes * worker_connections
        # 在设置了反向代理的情况下,max_clients = worker_processes * worker_connections / 4  为什么
        # 为什么上面反向代理要除以4,应该说是一个经验值
        # 根据以上条件,正常情况下的Nginx Server可以应付的最大连接数为:4 * 8000 = 32000
        # worker_connections 值的设置跟物理内存大小有关
        # 因为并发受IO约束,max_clients的值须小于系统可以打开的最大文件数
    	# 而系统可以打开的最大文件数和内存大小成正比,一般1GB内存的机器上可以打开的文件数大约是10万左右
        # 我们来看看360M内存的VPS可以打开的文件句柄数是多少:
        # $ cat /proc/sys/fs/file-max
        # 输出 34336
        # 32000 < 34336,即并发连接总数小于系统可以打开的文件句柄总数,这样就在操作系统可以承受的范围之内
        # 所以,worker_connections 的值需根据 worker_processes 进程数目和系统可以打开的最大文件总数进行适当地进行设置
        # 使得并发总数小于操作系统可以打开的最大文件数目
        # 其实质也就是根据主机的物理CPU和内存进行配置
        # 当然,理论上的并发总数可能会和实际有所偏差,因为主机还有其他的工作进程需要消耗系统资源。
        # ulimit -SHn 65535
    
    #3. 设定http服务器
    http {
        include mime.types; #文件扩展名与文件类型映射表
        default_type application/octet-stream; #默认文件类型
        #charset utf-8; #默认编码
        server_names_hash_bucket_size 128; #服务器名字的hash表大小
        client_header_buffer_size 32k; #上传文件大小限制
        large_client_header_buffers 4 64k; #设定请求缓
        client_max_body_size 8m; #设定请求缓
        sendfile on; #开启高效文件传输模式,sendfile指令指定nginx是否调用sendfile函数来输出文件,对于普通应用设为 on,如果用来进行下载等应用磁盘IO重负载应用,可设置为off,以平衡磁盘与网络I/O处理速度,降低系统的负载。注意:如果图片显示不正常把这个改成off。
        autoindex on; #开启目录列表访问,合适下载服务器,默认关闭。
        tcp_nopush on; #防止网络阻塞
        tcp_nodelay on; #防止网络阻塞
        keepalive_timeout 120; #长连接超时时间,单位是秒
        
        #5. gzip模块设置
        gzip on; #开启gzip压缩输出
        gzip_min_length 1k; #最小压缩文件大小
        gzip_buffers 4 16k; #压缩缓冲区
        gzip_http_version 1.0; #压缩版本(默认1.1,前端如果是squid2.5请使用1.0)
        gzip_comp_level 2; #压缩等级
        gzip_types text/plain application/x-javascript text/css application/xml;
        #压缩类型,默认就已经包含text/html,所以下面就不用再写了,写上去也不会有问题,但是会有一个warn。
        gzip_vary on;
        #limit_zone crawler $binary_remote_addr 10m; #开启限制IP连接数的时候需要使用
        
        #6. 虚拟主机的配置
        #nginx支持三种类型的虚拟主机配置,
        #1、基于ip的虚拟主机, (一块主机绑定多个ip地址)
        #2、基于域名的虚拟主机(servername)
        #3、基于端口的虚拟主机(listen如果不写ip端口模式)
        server {
            #监听端口
            listen 80;
            #域名可以有多个,用空格隔开
            server_name www.ha97.com ha97.com;
    
        #7. location 映射(ngx_http_core_module)
        	#location [ = | ~ | ~* | ^~ ] uri { ... }
        	#location URI {}:
        	#	对当前路径及子路径下的所有对象都生效;
        	#location = URI {}: 注意URL最好为具体路径。
            #   精确匹配指定的路径,不包括子路径,因此,只对当前资源生效;
        	#location ~ URI {}:
        	#location ~* URI {}:
        	#	模式匹配URI,此处的URI可使用正则表达式,~区分字符大小写,~*不区分字符大小写;
        	#location ^~ URI {}:
        	#	不使用正则表达式
        	#优先级:= > ^~ > ~|~* >  /|/dir/
            #=前缀的指令严格匹配这个查询。如果找到,停止搜索。
            # 特别注意: 所有剩下的常规字符串,最长的匹配。如果这个匹配使用^〜前缀,搜索停止。 
            #正则表达式,在配置文件中定义的顺序。
            #如果第3条规则产生匹配的话,结果被使用。否则,如同从第2条规则被使用
            #先普通
                #顺序无关
                #最大前缀
                #匹配规则简单
                #打断:
                    #^~
                    #完全匹配
            #再正则
                #不完全匹配
                #正则特殊性:一条URI可以和多条location匹配上
                #有顺序的
                #先匹配,先应用,即时退出匹配
            #IP访问控制
            #location  {
            	  # deny  IP /IP段   禁止访问
            	   #deny  192.168.1.109;
            	   #allow 192.168.1.0/24;192.168.0.0/16;192.0.0.0/8   允许访问
            	#}
            #规则:按照顺序依次检测,直到匹配到第一条规则
           # 用户认证访问
            #模块ngx_http_auth_basic_module 允许使用“HTTP基本认证”协议验证用户名和密码来限制对资源的访问。
            #	location / {
            #		auth_basic           "closed site";
            #		auth_basic_user_file /var/users;
            #    }
            #Apache发行包中的htpasswd命令来创建user_file 文件
            
            #要通过yum –y install httpd
            #htpasswd -c -m /var/users username
            #注:需要安装httpd才可以使用上面命令
            
    
            location ~ .*\.(php|php5)?$
                index index.html index.htm index.jsp;
                root /data/www/ha97;
            {
            fastcgi_pass 127.0.0.1:9000;
            fastcgi_index index.jsp;
            include fastcgi.conf;
        }
    }    
    

    ps :
    通过淘宝Tengine 查看Tengine技术以及Nginx中文文档
    Nginx中文地址

    技术延伸

    通过对Tengine中Nginx的配置文件修改达到下面的效果

    利用Nginx配置虚拟Server

    # 1.修改配置文件
    # 修改配置文件中的server, 修改了server_name
    # 创建了一个server,配置server_name ,listen 以及location
    
     	server {
            listen 80;
            server_name www.chy.com;
            location / {
                root   /mnt;
                autoindex on;    #这里配置了自动索引
            }
    
             }
        server {
            listen       80;
            server_name  www.timepause.com;
    
    
            #charset koi8-r;
    
            #access_log  logs/host.access.log  main;
    
            location / {
                root   html;
                index  index.html index.htm;
            }
    
    
    
    # 2. 重新加载nginx ( 需要将Nginx封装成一个服务,本章第三节支持 )
    service nginx reload
    
    # 3. 在本地hosts文件中配置地址映射,将上面配置的两个域名映射到有nginx的虚拟机中
    # hosts 文件位于 C:\Windows\System32\drivers\etc下
    192.168.179.140  www.chy.com www.timepause.com
    
    # 4.测试访问
    www.timepause.com   如图1
    www.chy.com              如图2
    
    # 5. 如果虚拟机添加了光盘文件.可以挂载,以列表的形式进行显示
    mount /dev/cdrom /mnt/
    # 再次访问  www.chy.com (这样可以做yum的本地安装库, 如图3)
    #思路 :找一台物理机+nginx可以作为公司的本地安装源
    

    图1
    在这里插入图片描述
    图2
    在这里插入图片描述
    如3
    在这里插入图片描述

    利用Nginx对服务器做反向代理

    在配置文件中的server配置中的location中添加如下配置

    location / {
               root   html;
                index  index.html index.htm;
            }
             location /chy.doc {
                 proxy_pass http://192.168.179.141/;
             }
            location ~* \.go$  {
                proxy_pass http://192.168.179.142;
            }
    
    

    通过上面配置并重载服务, 即可实现对指定uri的静态访问拦截 ,与动态拦截(图1)
    静态拦截如图2,图3. 可以拦截server_name后面为 /chy.doc的请求,
    但会在url中将 chy.doc 去掉, 因此我们实际访问的都是Index.html页面

    如访问 server_name/chy.doc/index.html 通过Nginx处理后实际访问的是 192.168.179.141/index.html
    访问 server_bane/chy.doc 通过Nginx处理后实际访问的是 192.168.179.141/ ,这里默认的也是访问index.html

    正则表达式动态拦截, 可以拦截 .go 结尾的uri, 并且以 ip/uri 的路径将其转发(不会去掉该uri ,图4),
    如访问 server_name/index.go 通过Nginx处理后实际访问的是 192.168.179.142/index.go
    但是要求我们在 192.168.179.192这台主机需要有 index.go页面处理

    图1
    在这里插入图片描述

    图2
    在这里插入图片描述

    图3
    在这里插入图片描述
    图4
    在这里插入图片描述

    利用Nginx做反向代理负载均衡

    修改Tengine 中的nginx中的配置文件nginx.config

    # 1. 修改超时时间,如果不为0 ,会等到超时时间结束才会被负载到另一个服务器, 
    keepalive_timeout  0;
    
    # 2. 创建 upstream配置, 名称任意 ,配置用于负载的服务器, 一般位于server配置的上方
    upstream testbalance {
            server 192.168.179.141;
            server 192.168.179.142;
        }
    
    # 3. 配置反向代理
    # 重载服务后, 我们只需要访问 server_name+用于反向代理的localtion.即http://www.timepause.com/test
    #就会被随机负载到 .141和 .142的两台服务器上了( 图1,图2 )
      location /test {                    							  # location后的拦截路径名任意,但是需要有"/"
                 proxy_pass http://testbalance/;            # 这里最后也要有 "/;"
             }
             
    ----------------------------------------------------------
        server {
            listen       80;
            server_name  www.timepause.com;
    
            #charset koi8-r;
    
            #access_log  logs/host.access.log  main;
    
            location / {
               root   html;
                index  index.html index.htm;
            }
             location /test {
                 proxy_pass http://testbalance/;
             }
    }
    ----------------------------------------------------------
    

    图1
    在这里插入图片描述
    图2
    在这里插入图片描述

    利用Nginx 对指定域名(百度)进行代理

    在 nginx.conf中的server配置中添加如下代码 ,修改后需要重载nginx服务

    location /baidu {
                 proxy_pass https://www.baidu.com/;
             }
    
    # 重载nginx服务,访问 (图1)
    service nginx reload 
    

    注意 :

    1. 如果遇到图3问题需要配置默认网关route add default gw 网关地址要根据自己所在网段进行配置

    2. 我们需要注意的是避免域名直接的跳转 ,如果我们将反向代理写成了这个 http://www.baidu.com/ ,
      由于现在的网址使用的协议大多都是https(安全性考虑) ,通过Nginx代理后的目录就是( 图2 ),
      导致Nginx不能对当前域名进行管理, 因此在今后的项目代码中尽量不要出现客户端的跳转
      以后在企业开发时一定多多注意类似问题

    图1
    在这里插入图片描述
    图2
    在这里插入图片描述

    图3
    在这里插入图片描述

    小技巧 :利用 !命令的前几个字符匹配命令并执行

    举例 ,其他命令也同本例

    # 利用 ifconfig 命令查看网络信息,图1
    ifconfig 
    
    # 利用 !if 可快速执行上一个,命令,图2
    !if
    
    #这个命令的原理是会调用 history 命令来查看我们的历史命令,
    #通过对我们在!后书写的字符进行匹配 ,如果匹配到就进行执行该命令
    

    图1
    在这里插入图片描述

    图2
    在这里插入图片描述

    nginx如何识别我们的域名

    我们发送这样的域名请求时 ,虽然域名被本地hosts文件映射到了对应的虚拟机ip地址,因此我们实际访问的其实是对应的IP地址
    但我们仍可以根据域名访问到对应的地址 , 这是因为在我们发送域名请求时, 域名信息也会被封装到请求头的Host中发送(图1)
    Nginx扫描请求头中的信息(域名/ip/端口) ,然后与Nginx的配置文件的server配置(图2)进行比对 ,如果符合就跳转到对应的页面

    在这里插入图片描述
    图2
    在这里插入图片描述

    利用Nginx的access.log监听用户的浏览信息

    该日志文件位于Nginx目录下的: logs/access.log
    利用 tail -f access.log 可以通过Nginx持续监听用户在被Nginx代理的网站的浏览记录
    例如下图中我访问了chy.com 的 /EFI 目录 ,而日志中就会显示我的访问信息
    个人感觉这个功能可能在数据收集中可能会有意想不到的作用

    在这里插入图片描述

    拓展: 指定 access.log 日志输出格式

    通过对 nginx.conf 文件的修改 ,获取指定格式的日志格式文件, 方便我们进行日志分析

    • Nginx中文文档
      http://tengine.taobao.org/nginx_docs/cn/docs/

    • 修改日志格式参数的参考(下面两个地方)
      1.日志模块格式配置所在地址
      http://tengine.taobao.org/nginx_docs/cn/docs/http/ngx_http_log_module.html
      2.内嵌变量介绍
      http://tengine.taobao.org/nginx_docs/cn/docs/http/ngx_http_core_module.html#variables
      注意: 可以通过将这些字段复制, 然后通过ctrl+f 在上面两个页面中查找

    具体步骤

    1. 创建 my_log 用于格式化日志输出格式(图1)

      # 注意: 这里的^A 是通过 ctrl+v 和 ctrl+A 实现的 , 方面我们以后进行日志的分割
      '$remote_addr^A$msec$http_host^A$request_uri';
      
    2. 创建一个location ,用于捕获浏览器请求为 xx/log.gif 的请求 (图2)
      注意: /opt/data/access.log 会在我们重载nginx 服务后自动创建

      location =/log.gif {
              default_type image/gif;
              access_log /opt/data/access.log my_log;
              }
      
      
    3. 重载nginx服务 ,访问

      # 重载
      service nginx reload
      # 打开日志记录文件 ,阻塞式访问 ( 图4 )
      tail -f /opt/data/access.log
      
      # 浏览器访问(图3 )
      http://node2/log.gif
      http://node2/log.gif?name=002&age=15  #看一看到带参访问的参数也会被显示到日志文件 
      
      # 打开
      ctrl+f5 快速清除缓存
      
    4. 额外补充: 我们可以看到直接访问nginx页面会出现 404请求资源不存在, 我们可以随便找一张图片修改其名称为 log.gif
      放到nginx的html目录下( /usr/local/tengine-2.1.0/html ),然后再次访问即可看到图片啦( 图5 )~~~

    图1
    在这里插入图片描述
    图2
    在这里插入图片描述
    图3
    在这里插入图片描述
    图4
    在这里插入图片描述
    图5
    在这里插入图片描述

    五 session一致性问题的解决

    情景模拟

    三台虚拟机node1,node2,node3
    node2,3安装jdk与tomcat(目的是运行tomcat)
    node1 安装Nginx, memcached

    # 1. 修改主页,便于识别,查看session,访问页面,如图1,图2
    # node2中的tomcat的index页面, 清除所有内容,编写如下代码 ,用于分辨主机以及对应的session信息(  位于/webapps/ROOT/index.jsp )
    -----------------------------------
    from 192.168.179.141<br>
    <%=session.getId()%>
    
    # node3中的tomcat的index页面,清除所有内容,编写如下代码(  /webapps/ROOT/index.jsp )
    -----------------------------------
    from 192.168.179.142<br>
    <%=session.getId()%>
    
    # 2 利用Nginx代理上面两个服务器(给我们的感觉是代理了两个tomcat主页 ) ,修改nginx.conf (配置负载均衡upstream 以及location  )
    ----------------------------配置的内容-----------------
     upstream test.tomcat.session {
            server 192.168.179.141:8080;
            server 192.168.179.142:8080;
            }
    
       server {
            listen       80;
            server_name  www.timepause.com;
            
            location /tomcat {
              proxy_pass http://test.tomcat.session/;
            }
    
    }
    
    # 3. 重载nginx 服务,访问 浏览器 ,可以看到一直刷新可以分别访问到这两个tomcat主页(图3,图4 )
    http://www.timepause.com/tomcat
    
    # 4.观察session变化我们可以看到,每次刷新session都会改变,这里就说明出现了session不一致的情况,下面我们将解决这个问题
    

    图1
    在这里插入图片描述
    图2
    在这里插入图片描述

    图3
    在这里插入图片描述
    图4
    在这里插入图片描述

    解决方案——安装memcached

    安装memcached可以为我们解决seession的问题

    # 1、安装memcached(node1)
      yum -y install memcached
      
    # 2、启动memcached(node1)
    	memcached -d -m 128m -p 11211 -l 192.168.179.140 -u root -P /tmp/
    	-d:后台启动服务
    	-m:缓存大小
    	-p:端口
    	-l:IP
    	-P:服务器启动后的系统进程ID,存储的文件
    	-u:服务器启动是以哪个用户名作为管理用户
    
    	
    # 3. 修改node2,node3中tomcat中conf  ( context.xml ), 注意memcachedNodes的配置
    ------------------------------------------------------------------------
    <Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager" 
    	memcachedNodes="n1:192.168.179.140:11211" 
        sticky="false" 
        lockingMode="auto"
        sessionBackupAsync="false"
    	requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$"
        sessionBackupTimeout="1000" transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory" />
    
    
    
    # 4、拷贝jar到tomcat的lib下 ,分享在博客末尾
    [asm-3.2.jar]
    [kryo-1.04.jar]
    [kryo-serializers-0.11.jar]
    [memcached-session-manager-1.7.0.jar]
    [memcached-session-manager-tc7-1.8.1.jar]
    [minlog-1.2.jar]
    [msm-kryo-serializer-1.7.0.jar]
    [reflectasm-1.01.jar]
    [spymemcached-2.7.3.jar]
    
    
    5. 重启tomcat ,访问测试
    由图1,图2可以看到运行memcached 就消除了一致性问题 ,而且他们的时间也被一致化了
    

    注意: 时间一致性
    不仅是Nginx代理的tomcat需要时间一致, 集群中时间也必须一致 ,
    查看虚拟机当前时间命令 date , 通过date -s 设置时间 .
    可以通过xshell的全部会话功能( 图3 )来同步时间

    [root@node4 ~]# date -s "2011-11-11 11:11:11"
    Fri Nov 11 11:11:11 CST 2011
    

    图1

    在这里插入图片描述
    图2
    在这里插入图片描述
    图3
    在这里插入图片描述

    时间一致性的另一种解决方案(安装ntpdate服务)

    1. 在Xshell右下方选择全部会话功能, 安装ntp服务
      yum -y install ntpdate
    2. 启动ntp服务(-u指定的是网络上的ntp服务器,可以自行搜索)
      ntpdate -u ntp.api.bz

    链接:https://pan.baidu.com/s/11KOnUQcDOEfSkmYcZdU9jw
    提取码:nc38
    复制这段内容后打开百度网盘手机App,操作更方便哦

    展开全文
  • 这几天区块链技术火的有点不正常,这种不正常的热度说白了就是有人在炒作趁热割韭菜。 区块链技术据说将来可以代替绝大多数的证券、银行等部门的服务器,在交易、数据安全、知识产权保护等方面发挥巨大的作用,是...

         这几天区块链技术火的有点不正常,这种不正常的热度说白了就是有人在炒作趁热割韭菜。

         区块链技术据说将来可以代替绝大多数的证券、银行等部门的服务器,在交易、数据安全、知识产权保护等方面发挥巨大的作用,是面向未来的技术。

        对于这样前途无量的技术,自己有一个问题没有想明白,想求助大神们解答。

        区块链是一种去中心化的分布式的记账数据库,以集群中的所有个体节点共同记录相互印证来保证数据的防篡改和安全性。但是它也有一个天生的致命弱点——51%攻击,也就是说当一个节点的算力超过集群整体的51%的时候,它所记录的东西就会被当做是正确的,其他的节点都会以它为准进行更新。

        那么,问题就来了:

        (1)如果要保持集群的安全性,就要使集群的节点数量尽可能多;

        (2)N个节点都要同步 ,那么每一个节点都要同步N-1次,当节点多的时候这个量将会非常可观。比如,对于证券交易,几千万的账户需要同时更新,那么每一台电脑几乎都要当做服务器与N-1个其他节点相互通信,但这样的性能单机是无论如何也无法达到的,就算是阿里的云服务器集群也只是刚刚能够扛下双十一的峰值。如果要同步N-1个节点的数据那么时间延迟可能就会很大。

        问题就是,区块链在安全性与性能之间怎样取舍? 或者说在集群节点足够多的时候,怎样保证同步的实时性?

     

     

     

        据说,一些加密货币因为节点太多,交易确认过程将近一天。那么这样的话,如果进一步增大交易网络节点个数,这个交易还能及时完成吗?

     

     

     

     

     

     

     

     

    展开全文
  • PHP解决高并发问题

    万次阅读 多人点赞 2020-05-27 12:10:04
    我们通常衡量一个Web系统的吞吐率的指标是QPS(Query Per Second,每秒处理请求数),解决每秒数万次的高并发场景,这个指标非常关键。举个例子,我们假设处理一个业务请求平均响应时间为100ms,同时,系统内有20台...

    举个例子,高速路口,1秒钟来5部车,每秒通过5部车,高速路口运作正常。突然,这个路口1秒钟只能通过4部车,车流量仍然依旧,结果必定出现大塞车。(5条车道忽然变成4条车道的感觉)

    同理,某一个秒内,20*500个可用连接进程都在满负荷工作中,却仍然有1万个新来请求,没有连接进程可用,系统陷入到异常状态也是预期之内。

    14834077821.jpg

    其实在正常的非高并发的业务场景中,也有类似的情况出现,某个业务请求接口出现问题,响应时间极慢,将整个Web请求响应时间拉得很长,逐渐将Web服务器的可用连接数占满,其他正常的业务请求,无连接进程可用。

    更可怕的问题是,是用户的行为特点,系统越是不可用,用户的点击越频繁,恶性循环最终导致“雪崩”(其中一台Web机器挂了,导致流量分散到其他正常工作的机器上,再导致正常的机器也挂,然后恶性循环),将整个Web系统拖垮。

    重启与过载保护

    如果系统发生“雪崩”,贸然重启服务,是无法解决问题的。最常见的现象是,启动起来后,立刻挂掉。这个时候,最好在入口层将流量拒绝,然后再将重启。如果是redis/memcache这种服务也挂了,重启的时候需要注意“预热”,并且很可能需要比较长的时间。

    秒杀和抢购的场景,流量往往是超乎我们系统的准备和想象的。这个时候,过载保护是必要的。如果检测到系统满负载状态,拒绝请求也是一种保护措施。在前端设置过滤是最简单的方式,但是,这种做法是被用户“千夫所指”的行为。更合适一点的是,将过载保护设置在CGI入口层,快速将客户的直接请求返回

    高并发下的数据安全

    我们知道在多线程写入同一个文件的时候,会存现“线程安全”的问题(多个线程同时运行同一段代码,如果每次运行结果和单线程运行的结果是一样的,结果和预期相同,就是线程安全的)。如果是MySQL数据库,可以使用它自带的锁机制很好的解决问题,但是,在大规模并发的场景中,是不推荐使用MySQL的。秒杀和抢购的场景中,还有另外一个问题,就是“超发”,如果在这方面控制不慎,会产生发送过多的情况。我们也曾经听说过,某些电商搞抢购活动,买家成功拍下后,商家却不承认订单有效,拒绝发货。这里的问题,也许并不一定是商家奸诈,而是系统技术层面存在超发风险导致的。

    1. 超发的原因

    假设某个抢购场景中,我们一共只有100个商品,在最后一刻,我们已经消耗了99个商品,仅剩最后一个。这个时候,系统发来多个并发请求,这批请求读取到的商品余量都是99个,然后都通过了这一个余量判断,最终导致超发。(同文章前面说的场景)

    14834077822.jpg

    在上面的这个图中,就导致了并发用户B也“抢购成功”,多让一个人获得了商品。这种场景,在高并发的情况下非常容易出现。

    优化方案1:将库存字段number字段设为unsigned,当库存为0时,因为字段不能为负数,将会返回false

     <?php
     //优化方案1:将库存字段number字段设为unsigned,当库存为0时,因为字段不能为负数,将会返回false
     include('./mysql.php');
     $username = 'wang'.rand(0,1000);
     //生成唯一订单
     function build_order_no(){
       return date('ymd').substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
     }
     //记录日志
     function insertLog($event,$type=0,$username){
         global $conn;
         $sql="insert into ih_log(event,type,usernma)
         values('$event','$type','$username')";
         return mysqli_query($conn,$sql);
     }
     function insertOrder($order_sn,$user_id,$goods_id,$sku_id,$price,$username,$number)
     {
           global $conn;
           $sql="insert into ih_order(order_sn,user_id,goods_id,sku_id,price,username,number)
           values('$order_sn','$user_id','$goods_id','$sku_id','$price','$username','$number')";
          return  mysqli_query($conn,$sql);
     }
     //模拟下单操作
     //库存是否大于0
     $sql="select number from ih_store where goods_id='$goods_id' and sku_id='$sku_id' ";
     $rs=mysqli_query($conn,$sql);
     $row = $rs->fetch_assoc();
       if($row['number']>0){//高并发下会导致超卖
           if($row['number']<$number){
             return insertLog('库存不够',3,$username);
           }
           $order_sn=build_order_no();
           //库存减少
           $sql="update ih_store set number=number-{$number} where sku_id='$sku_id' and number>0";
           $store_rs=mysqli_query($conn,$sql);
           if($store_rs){
               //生成订单
               insertOrder($order_sn,$user_id,$goods_id,$sku_id,$price,$username,$number);
               insertLog('库存减少成功',1,$username);
           }else{
               insertLog('库存减少失败',2,$username);
           }
       }else{
           insertLog('库存不够',3,$username);
       }
     ?>
    
    1. 悲观锁思路

    解决线程安全的思路很多,可以从“悲观锁”的方向开始讨论。

    悲观锁,也就是在修改数据的时候,采用锁定状态,排斥外部请求的修改。遇到加锁的状态,就必须等待。

    14834077833.jpg

    虽然上述的方案的确解决了线程安全的问题,但是,别忘记,我们的场景是“高并发”。也就是说,会很多这样的修改请求,每个请求都需要等待“锁”,某些线程可能永远都没有机会抢到这个“锁”,这种请求就会死在那里。同时,这种请求会很多,瞬间增大系统的平均响应时间,结果是可用连接数被耗尽,系统陷入异常。

    优化方案2:使用MySQL的事务,锁住操作的行

     <?php
     //优化方案2:使用MySQL的事务,锁住操作的行
     include('./mysql.php');
     //生成唯一订单号
     function build_order_no(){
       return date('ymd').substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
     }
     //记录日志
     function insertLog($event,$type=0){
         global $conn;
         $sql="insert into ih_log(event,type)
         values('$event','$type')";
         mysqli_query($conn,$sql);
     }
     //模拟下单操作
     //库存是否大于0
     mysqli_query($conn,"BEGIN");  //开始事务
     $sql="select number from ih_store where goods_id='$goods_id' and sku_id='$sku_id' FOR UPDATE";//此时这条记录被锁住,其它事务必须等待此次事务提交后才能执行
     $rs=mysqli_query($conn,$sql);
     $row=$rs->fetch_assoc();
     if($row['number']>0){
         //生成订单
         $order_sn=build_order_no();
         $sql="insert into ih_order(order_sn,user_id,goods_id,sku_id,price)
         values('$order_sn','$user_id','$goods_id','$sku_id','$price')";
         $order_rs=mysqli_query($conn,$sql);
         //库存减少
         $sql="update ih_store set number=number-{$number} where sku_id='$sku_id'";
         $store_rs=mysqli_query($conn,$sql);
         if($store_rs){
           echo '库存减少成功';
             insertLog('库存减少成功');
             mysqli_query($conn,"COMMIT");//事务提交即解锁
         }else{
           echo '库存减少失败';
             insertLog('库存减少失败');
         }
     }else{
       echo '库存不够';
         insertLog('库存不够');
         mysqli_query($conn,"ROLLBACK");
     }
     ?>
    
    1. FIFO队列思路

    那好,那么我们稍微修改一下上面的场景,我们直接将请求放入队列中的,采用FIFO(First Input First Output,先进先出),这样的话,我们就不会导致某些请求永远获取不到锁。看到这里,是不是有点强行将多线程变成单线程的感觉哈。

    14834077834.jpg

    然后,我们现在解决了锁的问题,全部请求采用“先进先出”的队列方式来处理。那么新的问题来了,高并发的场景下,因为请求很多,很可能一瞬间将队列内存“撑爆”,然后系统又陷入到了异常状态。或者设计一个极大的内存队列,也是一种方案,但是,系统处理完一个队列内请求的速度根本无法和疯狂涌入队列中的数目相比。也就是说,队列内的请求会越积累越多,最终Web系统平均响应时候还是会大幅下降,系统还是陷入异常。

    1. 文件锁的思路
      对于日IP不高或者说并发数不是很大的应用,一般不用考虑这些!用一般的文件操作方法完全没有问题。但如果并发高,在我们对文件进行读写操作时,很有可能多个进程对进一文件进行操作,如果这时不对文件的访问进行相应的独占,就容易造成数据丢失

    优化方案4:使用非阻塞的文件排他锁

     <?php
     //优化方案4:使用非阻塞的文件排他锁
     include ('./mysql.php');
     //生成唯一订单号
     function build_order_no(){
       return date('ymd').substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
     }
     //记录日志
     function insertLog($event,$type=0){
         global $conn;
         $sql="insert into ih_log(event,type)
         values('$event','$type')";
         mysqli_query($conn,$sql);
     }
     $fp = fopen("lock.txt", "w+");
     if(!flock($fp,LOCK_EX | LOCK_NB)){
         echo "系统繁忙,请稍后再试";
         return;
     }
     //下单
     $sql="select number from ih_store where goods_id='$goods_id' and sku_id='$sku_id'";
     $rs =  mysqli_query($conn,$sql);
     $row = $rs->fetch_assoc();
     if($row['number']>0){//库存是否大于0
         //模拟下单操作
         $order_sn=build_order_no();
         $sql="insert into ih_order(order_sn,user_id,goods_id,sku_id,price)
         values('$order_sn','$user_id','$goods_id','$sku_id','$price')";
         $order_rs =  mysqli_query($conn,$sql);
         //库存减少
         $sql="update ih_store set number=number-{$number} where sku_id='$sku_id'";
         $store_rs =  mysqli_query($conn,$sql);
         if($store_rs){
           echo '库存减少成功';
             insertLog('库存减少成功');
             flock($fp,LOCK_UN);//释放锁
         }else{
           echo '库存减少失败';
             insertLog('库存减少失败');
         }
     }else{
       echo '库存不够';
         insertLog('库存不够');
     }
     fclose($fp);
      ?>
    
     <?php
     //优化方案4:使用非阻塞的文件排他锁
     include ('./mysql.php');
     //生成唯一订单号
     function build_order_no(){
       return date('ymd').substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
     }
     //记录日志
     function insertLog($event,$type=0){
         global $conn;
         $sql="insert into ih_log(event,type)
         values('$event','$type')";
         mysqli_query($conn,$sql);
     }
     $fp = fopen("lock.txt", "w+");
     if(!flock($fp,LOCK_EX | LOCK_NB)){
         echo "系统繁忙,请稍后再试";
         return;
     }
     //下单
     $sql="select number from ih_store where goods_id='$goods_id' and sku_id='$sku_id'";
     $rs =  mysqli_query($conn,$sql);
     $row = $rs->fetch_assoc();
     if($row['number']>0){//库存是否大于0
         //模拟下单操作
         $order_sn=build_order_no();
         $sql="insert into ih_order(order_sn,user_id,goods_id,sku_id,price)
         values('$order_sn','$user_id','$goods_id','$sku_id','$price')";
         $order_rs =  mysqli_query($conn,$sql);
         //库存减少
         $sql="update ih_store set number=number-{$number} where sku_id='$sku_id'";
         $store_rs =  mysqli_query($conn,$sql);
         if($store_rs){
           echo '库存减少成功';
             insertLog('库存减少成功');
             flock($fp,LOCK_UN);//释放锁
         }else{
           echo '库存减少失败';
             insertLog('库存减少失败');
         }
     }else{
       echo '库存不够';
         insertLog('库存不够');
     }
     fclose($fp);
      ?>
    
    1. 乐观锁思路

    这个时候,我们就可以讨论一下“乐观锁”的思路了。乐观锁,是相对于“悲观锁”采用更为宽松的加锁机制,大都是采用带版本号(Version)更新。实现就是,这个数据所有请求都有资格去修改,但会获得一个该数据的版本号,只有版本号符合的才能更新成功,其他的返回抢购失败。这样的话,我们就不需要考虑队列的问题,不过,它会增大CPU的计算开销。但是,综合来说,这是一个比较好的解决方案。

    在这里插入图片描述
    有很多软件和服务都“乐观锁”功能的支持,例如Redis中的watch就是其中之一。通过这个实现,我们保证了数据的安全。

    优化方案5:Redis中的watch

     <?php
     $redis = new redis();
      $result = $redis->connect('127.0.0.1', 6379);
      echo $mywatchkey = $redis->get("mywatchkey");
     /*
       //插入抢购数据
      if($mywatchkey>0)
      {
          $redis->watch("mywatchkey");
       //启动一个新的事务。
         $redis->multi();
        $redis->set("mywatchkey",$mywatchkey-1);
        $result = $redis->exec();
        if($result) {
           $redis->hSet("watchkeylist","user_".mt_rand(1,99999),time());
           $watchkeylist = $redis->hGetAll("watchkeylist");
             echo "抢购成功!<br/>";
             $re = $mywatchkey - 1;  
             echo "剩余数量:".$re."<br/>";
             echo "用户列表:<pre>";
             print_r($watchkeylist);
        }else{
           echo "手气不好,再抢购!";exit;
        } 
      }else{
          // $redis->hSet("watchkeylist","user_".mt_rand(1,99999),"12");
          //  $watchkeylist = $redis->hGetAll("watchkeylist");
             echo "fail!<br/>";   
             echo ".no result<br/>";
             echo "用户列表:<pre>";
           //  var_dump($watchkeylist); 
      }*/
     $rob_total = 100;   //抢购数量
     if($mywatchkey<=$rob_total){
         $redis->watch("mywatchkey");
         $redis->multi(); //在当前连接上启动一个新的事务。
         //插入抢购数据
         $redis->set("mywatchkey",$mywatchkey+1);
         $rob_result = $redis->exec();
         if($rob_result){
              $redis->hSet("watchkeylist","user_".mt_rand(1, 9999),$mywatchkey);
             $mywatchlist = $redis->hGetAll("watchkeylist");
             echo "抢购成功!<br/>";
           
             echo "剩余数量:".($rob_total-$mywatchkey-1)."<br/>";
             echo "用户列表:<pre>";
             var_dump($mywatchlist);
         }else{
               $redis->hSet("watchkeylist","user_".mt_rand(1, 9999),'meiqiangdao');
             echo "手气不好,再抢购!";exit;
         }
     }
     ?>
    
    展开全文
  • 亿级流量网站架构核心技术高并发系统,亿级流量网站架构核心技术高并发系统
  • Java高并发解决方案

    万次阅读 多人点赞 2019-03-03 22:23:50
    电商的秒杀和抢购,对我们来说,都不是一个陌生的东西。然而,从技术的角度来说,这对于Web系统是一个巨大的考验。...在过去的工作中,我曾经面对过5w每秒的高并发秒杀功能,在这个过程中,整个W...

    电商的秒杀和抢购,对我们来说,都不是一个陌生的东西。然而,从技术的角度来说,这对于Web系统是一个巨大的考验。当一个Web系统,在一秒钟内收到数以万计甚至更多请求时,系统的优化和稳定至关重要。这次我们会关注秒杀和抢购的技术实现和优化,同时,从技术层面揭开,为什么我们总是不容易抢到火车票的原因?

    一、大规模并发带来的挑战

    在过去的工作中,我曾经面对过5w每秒的高并发秒杀功能,在这个过程中,整个Web系统遇到了很多的问题和挑战。如果Web系统不做针对性的优化,会轻而易举地陷入到异常状态。我们现在一起来讨论下,优化的思路和方法哈。

    1.请求接口的合理设计

    一个秒杀或者抢购页面,通常分为2个部分,一个是静态的HTML等内容,另一个就是参与秒杀的Web后台请求接口。

    通常静态HTML等内容,是通过CDN的部署,一般压力不大,核心瓶颈实际上在后台请求接口上。这个后端接口,必须能够支持高并发请求,同时,非常重要的一点,必须尽可能“快”,在最短的时间里返回用户的请求结果。为了实现尽可能快这一点,接口的后端存储使用内存级别的操作会更好一点。仍然直接面向MySQL之类的存储是不合适的,如果有这种复杂业务的需求,都建议采用异步写入。

    当然,也有一些秒杀和抢购采用“滞后反馈”,就是说秒杀当下不知道结果,一段时间后才可以从页面中看到用户是否秒杀成功。但是,这种属于“偷懒”行为,同时给用户的体验也不好,容易被用户认为是“暗箱操作”。

    2.高并发的挑战:一定要“快”

    我们通常衡量一个Web系统的吞吐率的指标是QPS(Query Per Second,每秒处理请求数),解决每秒数万次的高并发场景,这个指标非常关键。举个例子,我们假设处理一个业务请求平均响应时间为100ms,同时,系统内有20台Apache的Web服务器,配置MaxClients为500个(表示Apache的最大连接数目)。

    那么,我们的Web系统的理论峰值QPS为(理想化的计算方式):

    20*500/0.1 = 100000 (10万QPS)

    咦?我们的系统似乎很强大,1秒钟可以处理完10万的请求,5w/s的秒杀似乎是“纸老虎”哈。实际情况,当然没有这么理想。在高并发的实际场景下,机器都处于高负载的状态,在这个时候平均响应时间会被大大增加。

    就Web服务器而言,Apache打开了越多的连接进程,CPU需要处理的上下文切换也越多,额外增加了CPU的消耗,然后就直接导致平均响应时间增加。因此上述的MaxClient数目,要根据CPU、内存等硬件因素综合考虑,绝对不是越多越好。可以通过Apache自带的abench来测试一下,取一个合适的值。然后,我们选择内存操作级别的存储的Redis,在高并发的状态下,存储的响应时间至关重要。网络带宽虽然也是一个因素,不过,这种请求数据包一般比较小,一般很少成为请求的瓶颈。负载均衡成为系统瓶颈的情况比较少,在这里不做讨论哈。

    那么问题来了,假设我们的系统,在5w/s的高并发状态下,平均响应时间从100ms变为250ms(实际情况,甚至更多):

    20*500/0.25 = 40000 (4万QPS)

    于是,我们的系统剩下了4w的QPS,面对5w每秒的请求,中间相差了1w。

    然后,这才是真正的恶梦开始。举个例子,高速路口,1秒钟来5部车,每秒通过5部车,高速路口运作正常。突然,这个路口1秒钟只能通过4部车,车流量仍然依旧,结果必定出现大塞车。(5条车道忽然变成4条车道的感觉)

    同理,某一个秒内,20*500个可用连接进程都在满负荷工作中,却仍然有1万个新来请求,没有连接进程可用,系统陷入到异常状态也是预期之内。

    其实在正常的非高并发的业务场景中,也有类似的情况出现,某个业务请求接口出现问题,响应时间极慢,将整个Web请求响应时间拉得很长,逐渐将Web服务器的可用连接数占满,其他正常的业务请求,无连接进程可用。

    更可怕的问题是,是用户的行为特点,系统越是不可用,用户的点击越频繁,恶性循环最终导致“雪崩”(其中一台Web机器挂了,导致流量分散到其他正常工作的机器上,再导致正常的机器也挂,然后恶性循环),将整个Web系统拖垮。

    3.重启与过载保护

    如果系统发生“雪崩”,贸然重启服务,是无法解决问题的。最常见的现象是,启动起来后,立刻挂掉。这个时候,最好在入口层将流量拒绝,然后再将重启。如果是redis/memcache这种服务也挂了,重启的时候需要注意“预热”,并且很可能需要比较长的时间。

    秒杀和抢购的场景,流量往往是超乎我们系统的准备和想象的。这个时候,过载保护是必要的。如果检测到系统满负载状态,拒绝请求也是一种保护措施。在前端设置过滤是最简单的方式,但是,这种做法是被用户“千夫所指”的行为。更合适一点的是,将过载保护设置在CGI入口层,快速将客户的直接请求返回。

    二、作弊的手段:进攻与防守

    秒杀和抢购收到了“海量”的请求,实际上里面的水分是很大的。不少用户,为了“抢“到商品,会使用“刷票工具”等类型的辅助工具,帮助他们发送尽可能多的请求到服务器。还有一部分高级用户,制作强大的自动请求脚本。这种做法的理由也很简单,就是在参与秒杀和抢购的请求中,自己的请求数目占比越多,成功的概率越高。

    这些都是属于“作弊的手段”,不过,有“进攻”就有“防守”,这是一场没有硝烟的战斗哈。

    1.同一个账号,一次性发出多个请求

    部分用户通过浏览器的插件或者其他工具,在秒杀开始的时间里,以自己的账号,一次发送上百甚至更多的请求。实际上,这样的用户破坏了秒杀和抢购的公平性。

    这种请求在某些没有做数据安全处理的系统里,也可能造成另外一种破坏,导致某些判断条件被绕过。例如一个简单的领取逻辑,先判断用户是否有参与记录,如果没有则领取成功,最后写入到参与记录中。这是个非常简单的逻辑,但是,在高并发的场景下,存在深深的漏洞。多个并发请求通过负载均衡服务器,分配到内网的多台Web服务器,它们首先向存储发送查询请求,然后,在某个请求成功写入参与记录的时间差内,其他的请求获查询到的结果都是“没有参与记录”。这里,就存在逻辑判断被绕过的风险。

    应对方案:

    在程序入口处,一个账号只允许接受1个请求,其他请求过滤。不仅解决了同一个账号,发送N个请求的问题,还保证了后续的逻辑流程的安全。实现方案,可以通过Redis这种内存缓存服务,写入一个标志位(只允许1个请求写成功,结合watch的乐观锁的特性),成功写入的则可以继续参加。

    或者,自己实现一个服务,将同一个账号的请求放入一个队列中,处理完一个,再处理下一个。

    2.多个账号,一次性发送多个请求

    很多公司的账号注册功能,在发展早期几乎是没有限制的,很容易就可以注册很多个账号。因此,也导致了出现了一些特殊的工作室,通过编写自动注册脚本,积累了一大批“僵尸账号”,数量庞大,几万甚至几十万的账号不等,专门做各种刷的行为(这就是微博中的“僵尸粉“的来源)。举个例子,例如微博中有转发抽奖的活动,如果我们使用几万个“僵尸号”去混进去转发,这样就可以大大提升我们中奖的概率。

    这种账号,使用在秒杀和抢购里,也是同一个道理。例如,iPhone官网的抢购,火车票黄牛党。

    应对方案:

    这种场景,可以通过检测指定机器IP请求频率就可以解决,如果发现某个IP请求频率很高,可以给它弹出一个验证码或者直接禁止它的请求:

    弹出验证码,最核心的追求,就是分辨出真实用户。因此,大家可能经常发现,网站弹出的验证码,有些是“鬼神乱舞”的样子,有时让我们根本无法看清。他们这样做的原因,其实也是为了让验证码的图片不被轻易识别,因为强大的“自动脚本”可以通过图片识别里面的字符,然后让脚本自动填写验证码。实际上,有一些非常创新的验证码,效果会比较好,例如给你一个简单问题让你回答,或者让你完成某些简单操作(例如百度贴吧的验证码)。
    直接禁止IP,实际上是有些粗暴的,因为有些真实用户的网络场景恰好是同一出口IP的,可能会有“误伤“。但是这一个做法简单高效,根据实际场景使用可以获得很好的效果。

    3.多个账号,不同IP发送不同请求

    所谓道高一尺,魔高一丈。有进攻,就会有防守,永不休止。这些“工作室”,发现你对单机IP请求频率有控制之后,他们也针对这种场景,想出了他们的“新进攻方案”,就是不断改变IP。

    有同学会好奇,这些随机IP服务怎么来的。有一些是某些机构自己占据一批独立IP,然后做成一个随机代理IP的服务,有偿提供给这些“工作室”使用。还有一些更为黑暗一点的,就是通过木马黑掉普通用户的电脑,这个木马也不破坏用户电脑的正常运作,只做一件事情,就是转发IP包,普通用户的电脑被变成了IP代理出口。通过这种做法,黑客就拿到了大量的独立IP,然后搭建为随机IP服务,就是为了挣钱。

    应对方案:

    说实话,这种场景下的请求,和真实用户的行为,已经基本相同了,想做分辨很困难。再做进一步的限制很容易“误伤“真实用户,这个时候,通常只能通过设置业务门槛高来限制这种请求了,或者通过账号行为的”数据挖掘“来提前清理掉它们。

    僵尸账号也还是有一些共同特征的,例如账号很可能属于同一个号码段甚至是连号的,活跃度不高,等级低,资料不全等等。根据这些特点,适当设置参与门槛,例如限制参与秒杀的账号等级。通过这些业务手段,也是可以过滤掉一些僵尸号。

    4.火车票的抢购

    看到这里,同学们是否明白你为什么抢不到火车票?如果你只是老老实实地去抢票,真的很难。通过多账号的方式,火车票的黄牛将很多车票的名额占据,部分强大的黄牛,在处理验证码方面,更是“技高一筹“。

    高级的黄牛刷票时,在识别验证码的时候使用真实的人,中间搭建一个展示验证码图片的中转软件服务,真人浏览图片并填写下真实验证码,返回给中转软件。对于这种方式,验证码的保护限制作用被废除了,目前也没有很好的解决方案。

    因为火车票是根据身份证实名制的,这里还有一个火车票的转让操作方式。大致的操作方式,是先用买家的身份证开启一个抢票工具,持续发送请求,黄牛账号选择退票,然后黄牛买家成功通过自己的身份证购票成功。当一列车厢没有票了的时候,是没有很多人盯着看的,况且黄牛们的抢票工具也很强大,即使让我们看见有退票,我们也不一定能抢得过他们哈。

    最终,黄牛顺利将火车票转移到买家的身份证下。

    解决方案:

    并没有很好的解决方案,唯一可以动心思的也许是对账号数据进行“数据挖掘”,这些黄牛账号也是有一些共同特征的,例如经常抢票和退票,节假日异常活跃等等。将它们分析出来,再做进一步处理和甄别。

    三、高并发下的数据安全

    我们知道在多线程写入同一个文件的时候,会存现“线程安全”的问题(多个线程同时运行同一段代码,如果每次运行结果和单线程运行的结果是一样的,结果和预期相同,就是线程安全的)。如果是MySQL数据库,可以使用它自带的锁机制很好的解决问题,但是,在大规模并发的场景中,是不推荐使用MySQL的。秒杀和抢购的场景中,还有另外一个问题,就是“超发”,如果在这方面控制不慎,会产生发送过多的情况。我们也曾经听说过,某些电商搞抢购活动,买家成功拍下后,商家却不承认订单有效,拒绝发货。这里的问题,也许并不一定是商家奸诈,而是系统技术层面存在超发风险导致的。

    1.超发的原因

    假设某个抢购场景中,我们一共只有100个商品,在最后一刻,我们已经消耗了99个商品,仅剩最后一个。这个时候,系统发来多个并发请求,这批请求读取到的商品余量都是99个,然后都通过了这一个余量判断,最终导致超发。(同文章前面说的场景)

    在上面的这个图中,就导致了并发用户B也“抢购成功”,多让一个人获得了商品。这种场景,在高并发的情况下非常容易出现。

    2.悲观锁思路

    解决线程安全的思路很多,可以从“悲观锁”的方向开始讨论。

    悲观锁,也就是在修改数据的时候,采用锁定状态,排斥外部请求的修改。遇到加锁的状态,就必须等待。

    虽然上述的方案的确解决了线程安全的问题,但是,别忘记,我们的场景是“高并发”。也就是说,会很多这样的修改请求,每个请求都需要等待“锁”,某些线程可能永远都没有机会抢到这个“锁”,这种请求就会死在那里。同时,这种请求会很多,瞬间增大系统的平均响应时间,结果是可用连接数被耗尽,系统陷入异常。

    3.FIFO队列思路

    那好,那么我们稍微修改一下上面的场景,我们直接将请求放入队列中的,采用FIFO(First Input First Output,先进先出),这样的话,我们就不会导致某些请求永远获取不到锁。看到这里,是不是有点强行将多线程变成单线程的感觉哈。

    然后,我们现在解决了锁的问题,全部请求采用“先进先出”的队列方式来处理。那么新的问题来了,高并发的场景下,因为请求很多,很可能一瞬间将队列内存“撑爆”,然后系统又陷入到了异常状态。或者设计一个极大的内存队列,也是一种方案,但是,系统处理完一个队列内请求的速度根本无法和疯狂涌入队列中的数目相比。也就是说,队列内的请求会越积累越多,最终Web系统平均响应时候还是会大幅下降,系统还是陷入异常。

    4.乐观锁思路

    这个时候,我们就可以讨论一下“乐观锁”的思路了。乐观锁,是相对于“悲观锁”采用更为宽松的加锁机制,大都是采用带版本号(Version)更新。实现就是,这个数据所有请求都有资格去修改,但会获得一个该数据的版本号,只有版本号符合的才能更新成功,其他的返回抢购失败。这样的话,我们就不需要考虑队列的问题,不过,它会增大CPU的计算开销。但是,综合来说,这是一个比较好的解决方案。

    5.缓存服务器

    Redis分布式要保证数据都能能够平均的缓存到每一台机器,首先想到的做法是对数据进行分片,因为Redis是key-value存储的,首先想到的是Hash分片,可能的做法是对key进行哈希运算,得到一个long值对分布式的数量取模会得到一个一个对应数据库的一个映射,没有读取就可以定位到这台数据库

    有很多软件和服务都“乐观锁”功能的支持,例如Redis中的watch就是其中之一。通过这个实现,我们保证了数据的安全。

    四、小结

    互联网正在高速发展,使用互联网服务的用户越多,高并发的场景也变得越来越多。电商秒杀和抢购,是两个比较典型的互联网高并发场景。虽然我们解决问题的具体技术方案可能千差万别,但是遇到的挑战却是相似的,因此解决问题的思路也异曲同工。

    个人整理并发解决方案。

    a.应用层面:读写分离、缓存、队列、集群、令牌、系统拆分、隔离、系统升级(可水平扩容方向)。
    b.时间换空间:降低单次请求时间,这样在单位时间内系统并发就会提升。
    c.空间换时间:拉长整体处理业务时间,换取后台系统容量空间。

     

    喜欢本文的朋友们,欢迎关注微信公众号“Java面试达人”,收看更多精彩内容

    也可关注微博 “程序员阿九”,收看更多精彩内容

     

    展开全文
  • 高并发情况下你还在用Random生成随机数?

    万次阅读 多人点赞 2021-04-05 23:42:13
    为了改进这个问题,增强随机数生成器在高并发环境中的性能,于是乎,就有了ThreadLocalRandom——一个性能强悍的高并发随机数生成器。 ThreadLocalRandom继承自Random,根据里氏代换原则,这说明ThreadLocalRandom...
  • 多线程和高并发介绍

    万次阅读 2021-06-06 05:00:46
    多线程和高并发介绍 文章目录多线程和高并发介绍前言一、什么是多线程?1.多线程介绍2.多线程实现原理?3.白话文解释多线程4.多线程存在的问题二、什么是高并发?1.高并发介绍2.如何提升系统的并发能力三、多线程和...
  • 分布式、多线程、高并发都不懂,拿什么去跳槽

    万次阅读 多人点赞 2019-10-09 01:03:16
    当提起这三个词的时候,是不是很多人都认为分布式=高并发=多线程?当面试官问到高并发系统可以采用哪些手段来解决,或者被问到分布式系统如何解决一致性的问题,是不是一脸懵逼?确...
  • 9种高性能高可用高并发技术架构

    千次阅读 2019-11-06 15:58:37
    面临难题:对于每天上亿流量,拥有上亿页面的大型电商网站来说,能够支撑高并发访问,同时能够秒级让最新模板生效的商品详情页系统的架构是如何设计的? 解决方案:异步多级缓存架构+nginx本地化缓存+动态模板渲染的...
  • 面试高并发,凉了!!(全程高能,建议收藏)

    千次阅读 多人点赞 2021-05-14 07:50:56
    此时,小菜突然想起来自己的好朋友——大冰,一个工作经验丰富的大神级别的人物,小菜决定要跟他学习高并发编程的知识!努力提升自己的编程技能!从此,小菜踏上了高并发学习的道路。 总结 并发编程一直是让人很...
  • 我是全网最硬核的高并发编程作者,CSDN最值得关注博主,全网最硬核的基于可视化的多数据源数据异构中间件作者,也许现在就是,也是不久的将来就是,大家同意吗?
  • 大型网站分布式架构高并发高可用可扩展技术
  • 4.1 高并发分布式技术专题 - 分布式开发技术 4.1.1 RPC 4.1.2 分布式系统指挥官Zookeeper 4.1.3 Dubbo框架 4.2 高并发分布式技术专题 - 高并发开发技术 4.2.1 Java多线程并发编程 4.2.2 NIO与...
  • 面试题:高并发场景下,如何保证缓存与数据库一致性? 问题分析 我们日常开发中,对于缓存用的最多的场景就像下图一样,可能仅仅是对数据进行缓存,减轻数据库压力,缩短接口响应时间。 这种方案在不需要考虑...
  • 互联网高并发技术架构:生产环境、交易域、核心域、运营系统、对账中心、数据库集群、渠道域、渠道资源等
  • 高并发高性能服务器是如何实现的

    千次阅读 2020-11-09 08:15:00
    总结 高并发技术从最开始的多进程一路演进到当前的事件驱动,计算机技术就像生物一样也在不断演变进化,但不管怎样,了解历史才能更深刻的理解当下。希望这篇文章能对大家理解高并发服务器有所帮助。 码农的荒岛...
  • 高并发与高可用知识总结

    万次阅读 2019-09-02 15:48:33
    究竟啥才是互联网架构“高并发” 一、什么是高并发 高并发(High Concurrency)是互联网分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计保证系统能够同时并行处理很多请求。 高并发相关常用的一些...
  • 高并发的解决方案

    万次阅读 多人点赞 2018-01-06 22:27:06
    刚开始的时候应用和静态资源是保存在一起的,当并发量达到一定程度的时候就需要将静态资源保存到专门的服务器中,静态资源主要包括图片、视频、js、css和一些资源文件等,这些文件因为没有状态所以分离比较简单,...
  • Python的高并发技术

    千次阅读 2019-05-25 23:31:00
    目前大多数编程语言都直接支持并发,而且其标准库通常还提供了一些封装程度较的功能。并发可以用多种方式来实现,这些方式最重要的区别在于如何访问"共享数据":是通过"共享内存"等方式直接访问,还是通过"进程间...
  • 高并发解决方案之一 ——负载均衡

    万次阅读 多人点赞 2018-04-15 21:52:15
    由于Nginx 超越 Apache 的高性能和稳定性,使得国内使用 Nginx 作为 Web 服务器的网站也越来越多,其中包括新浪博客、新浪播客、网易新闻、腾讯网、搜狐博客等门户网站频道等,在3w以上的高并发环境下,ngnix处理...
  • 谈谈电商秒杀高并发的处理

    万次阅读 多人点赞 2018-04-22 23:53:41
    众所周知现在连市场卖菜的大妈都快知道高并发了,哈哈,那么我们生活中是否接触过高并发呢。当然了哈哈,比如你给你女朋友抢秒杀的化妆品什么的了。秒杀最棘手的问题就是解决并发带来的问题。下面我们一起聊聊喽。 ...
  • 亿级流量 网站架构 核心技术 搭建 高可用 高并发 完整版 亿级流量 网站架构 核心技术 搭建 高可用 高并发 完整版
  • 高并发与多线程的关系与区别

    万次阅读 多人点赞 2019-03-09 21:30:41
    一、什么是高并发 高并发(High Concurrency)是一种系统运行过程中遇到的一种“短时间内遇到大量操作请求”的情况,主要发生在web系统集中大量访问收到大量请求(例如:12306的抢票情况;天猫双十一活动)。该...
  • Web开发中,什么级别才算是高并发

    万次阅读 2017-08-25 10:19:06
    大家心里仔细想想,当你们听到高并发网站时,心里对这个网站是个什么概念?首先想到的是淘宝吗?带着问题,我们一起思考技术 写这个话题是因为我对搜索引擎给我的答案很不满意,然后决定把思考的一些东西分享出来...
  • 一、高并发、多线程 1.高并发 高并发是请求,指的是多个客户端同一时刻向服务端发送请求, 它是一种现象。 比如,在双11凌晨12:00分同时有10万个下单请求。 高并发标准: 高并发用户数 TPS(Transactions Per ...
  • JavaWeb 并发编程 与 高并发解决方案

    万次阅读 多人点赞 2018-09-12 03:41:00
    在这里写写我学习到和自己所理解的 Java高并发编程和高并发解决方案。现在在各大互联网公司中,随着日益增长的互联网服务需求,高并发处理已经是一个非常常见的问题,在这篇文章里面我们重点讨论两个方面的问题,一...
  • 大数据和高并发解决方案

    千次阅读 2019-04-29 15:59:24
    下边整理的是一些针对海量数据和高并发情况下的解决方案,技术水平有限,欢迎留言指导。 二、针对海量数据和高并发的主要解决方案 海量数据的解决方案: 使用缓存; 页面静态化技术; 数据库优化; ...
  • 到底多大才算高并发

    万次阅读 多人点赞 2019-05-11 13:22:52
    高并发(High Concurrency)是使用技术手段使系统可以并行处理很多请求。 关键指标: -响应时间(Response Time) -吞吐量(Throughput) -每秒查询率QPS(Query Per Second) -每秒事务处理量TPS(Transaction Per ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 421,938
精华内容 168,775
关键字:

技术高并发