关于network:在MMORPG中其他玩家和怪物的同步做法

1)在MMORPG中,其余玩家和怪物的同步做法2)AssetBundle通过Offset加密/解密问题3)加载预制体API区别4)对于MaterialPropertyBlock的应用问题 这是第340篇UWA技术常识分享的推送,精选了UWA社区的热门话题,涵盖了UWA问答、社区帖子等技术知识点,助力大家更全面地把握和学习。 UWA社区主页:community.uwa4d.comUWA QQ群:465082844 NetworkQ:对于在MMORPG中,其余玩家和怪物的同步做法: 目前的做法是怪物和玩家都有一个状态机,状态的扭转是依据服务器下发的状态进行状态设置的,然而目前有个问题就是如果呈现本地卡顿或者网络稳定等其余起因导致客户端之前的动作还没有完结,服务器又下发了新的状态,这个时候如果间接设置新状态就会体现异样也会呈现地位不同步的状况(例如客户端还没有挪动完结,因为卡顿或者提早起因又收到了攻打状态,如果这个时候间接攻打就体现出又挪动又攻打的状况)。 因为之前没有这方面的教训,在网上查了其他人的做法,说是要做对其余角色的状态预测,如果呈现同步异样,用插值让异样的对象迅速复原到失常的状态。 那么:1. 其余玩家和同步是用状态机加预测去做的吗?2. 怪物同步的个别做法大略是怎么样的?和其余玩家相似吗?3. 在做同步的时候有哪些要留神的点呢? A: 其余玩家和怪物的同步个别会采纳状态同步和状态预测相结合的形式。状态同步是指服务器将所有角色的状态同步给客户端,客户端依据这些状态来更新角色的状态机。状态预测是指客户端依据本人的状态机来预测其余角色的状态,并依据这些预测后果来更新本人的视图。预测的后果须要通过与服务器的同步来验证,如果预测的后果和服务器的后果不统一,就须要进行状态修改。怪物的同步和其余玩家的同步相似,都须要采纳状态同步和状态预测相结合的形式。不同的是,怪物通常由服务器管制,因而服务器能够更准确地管制怪物的状态和行为,这样能够缩小客户端的预测量和同步量,从而进步同步的效率。在做同步的时候,须要留神以下几个点:状态同步的频率要足够高,确保角色的状态可能及时同步到客户端,缩小状态预测的误差。状态预测要尽量精确,采纳适合的算法和参数来进步预测的准确率。当客户端呈现卡顿或者提早时,须要采纳插值和修改等形式来让角色迅速复原到失常的状态,避免出现地位不同步的状况。须要对角色的挪动和攻打等行为进行校验,防止舞弊和外挂的呈现。对于客户端和服务器之间的通信,须要采纳牢靠的协定和加密形式来保障通信的平安和稳固。感激NG週@UWA问答社区提供了答复 AssetBundleQ:打包AssetBundle想用偏移量简略做一下加密: App版本应用:AssetBundle.LoadFromFile(filePath, 0, offset) 然而WebGL版本应用:UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(urlPath);AssetBundle ab = DownloadHandlerAssetBundle.GetContent(request); 未找到相干接口,请问大家有没有什么思路? A:在WebGL平台上应用UnityWebRequest发送网络申请获取AssetBundle文件,获取到后在内存中进行解密,而后将解密后的AssetBundle内容存储到内存中。 在内存中加载AssetBundle,能够应用AssetBundle.LoadFromMemoryAsync(byte[] buffer)办法或者AssetBundle.LoadFromMemory(byte[] buffer)办法。 感激NG週@UWA问答社区提供了答复 LoadingQ:请问PrefabUtility.LoadPrefabContents和AssetDatabase.LoadAssetAtPath这两个加载预制体的API具体有什么区别呢? 都试过测试感觉差不多,速度上没多大区别:PrefabUtility.LoadPrefabContents之后须要PrefabUtility.SavaAsPrefabAsset保留和PrefabUtility.UnloadPrefabContents开释;AssetDatabase.LoadAssetAtPath之后,应用EditorUtility.SetDirty标记,而后AssetDatabase.SaveAssets保留。 针对以上问题,有教训的敌人欢送转至社区交换分享:https://answer.uwa4d.com/question/648047ab47691b75be04aa96 RenderingQ:3D麻将我的项目,一个牌面一个材质球。我应用了一个不带材质球的麻将预制体,而后在创立麻将牌的时候实例化预制体,依据牌面值查找并赋值对应材质球, GetComponent<MeshRender>().shareMaterial = XXXXX。 材质球应用的Shader是URP里的Complex Lit。 有个需要是点击手中麻将牌时,批改曾经打出的牌(雷同牌面的)在桌面的牌的色彩,但不能影响手中的牌的色彩。于是我用MaterialPropertyBlock的SetPropertyBlock办法批改已打出的牌的色彩值,然而不失效。 已打出牌的材质球的Inspector下方有提醒“MaterialPropertyBlock is used to Modify these values”。网上也有一些解答,但还是没搞懂。请问正确流程和做法应该怎么才行呢? 针对以上问题,有教训的敌人欢送转至社区交换分享:https://answer.uwa4d.com/question/647e96efe63c097f73e2f2d9 封面图来源于网络 明天的分享就到这里。生有涯而知无涯,在漫漫的开发周期中,咱们遇到的问题只是冰山一角,UWA社区愿伴你同行,一起摸索分享。欢送更多的开发者退出UWA社区。 UWA官网:www.uwa4d.comUWA社区:community.uwa4d.comUWA学堂:edu.uwa4d.com官网技术QQ群:465082844

July 3, 2023 · 1 min · jiezi

关于network:使用Streaming-Mipmap后纹理内存没有下降的疑问

1)应用Streaming Mipmap后纹理内存没有降落的疑难2)TCP网络传输大端/小端疑难3)Texture Compression,Default和Override有什么关系4)如何疾速革除Log 这是第299篇UWA技术常识分享的推送。明天咱们持续为大家精选了若干和开发、优化相干的问题,倡议浏览工夫10分钟,认真读完必有播种。 UWA 问答社区:answer.uwa4d.comUWA QQ群2:793972859(原群已满员) TextureQ:为什么我在我的项目中应用了Streaming Mipmap然而在GOT报告中看纹理内存没有降落?是没有正确失效还是统计有问题? A:之前做过相干测试,发现GOT Online是能够统计到被Streaming Mipmap影响的纹理的正确内存的,所以我揣测你遇到的状况大概率还是没有正确失效导致的。以下是对如何让一张纹理利用Streaming Mipmap的简略流程总结,其中会注明须要特地留神的一些条件(有些被官网文档收录,有些则文档中没有,但试验证实为必要): 在Project Settings-Quality中开启Texture Streaming选项。然而试验发现Editor中开启该选项而真机上却会生效的景象,导致所有纹理的Streaming Mipmap设置全副生效。所以为了确保失效,首先应该在代码中调用QualitySettings.streamingMipmapsActive API全局地开启这个选项,能力确保Streaming Mipmap可用。调整1中设置的参数。比拟重要的参数是Memory Budget参数和Max Level Reduction参数。Memory Budget示意纹理资源的估算,默认值是512MB,但依据UWA的大量我的项目数据来看,中低端机上个别为200MB左右。它的数值代表的是所有纹理资源的估算——即,它既包含了非流式的纹理、又包含了咱们想要采纳流式的纹理——但这个“估算”并不代表纹理资源可占用的下限,只是Unity判断对于一个开启Streaming Mipmap的纹理到底采纳它的哪些Mipmap通道的参考值,非流式纹理可能轻易冲破这个估算。Max Level Reduction则是代表着Unity通过流式存储最高能取到哪一级的Mipmap通道,这个参数的优先级比Memory Budget要高,也就会造成理论内存超过预算的状况。(比方该参数为2时,则最多剔除Mipmap0和1通道,即使抛弃当前还远超出预算值也不会进一步剔除。) 也就是说,如果Memory Budget值设置的远高于我的项目中纹理理论占用的内存,则Texture Streaming可能齐全不起效,所有开启Streaming Mipmap的纹理仍将保留它们的全副Mipmap通道。 设置开启Streaming Mipmap的纹理。1、2中提到的设置只对开启了Streaming Mipmap的纹理起效,而这只是官网文档的说法,从实际操作来看,Texture Streaming只对同时满足以下三个条件的纹理失效: 1)开启了Streaming Mipmap且开启了Generate Mipmap的纹理(这一点官网文档中没有提及,事实上开启Generate Mipmap才会生成Mipmap通道供Streaming Mipmap剔除); 2)被即时加载的纹理(如一开始就曾经在场景中被依赖的纹理,即使开启了Streaming Mipmap其内存也不会发生变化,通过AssetBundle加载和Res.Load()加载的纹理则能够),也即官网文档中这句话的理论含意: 如果是进行Android开发,还须要关上Build Setting,并将Compression Method设置为LZ4或LZ4HC。Unity须要应用其中一种压缩办法进行异步纹理加载,这是纹理串流零碎所必须的操作。 3)Gfx局部内存(这里指的是纹理资源开启Read/Write选项时,复制到CPU端的那一部分内存是不受Streaming Mipmap影响的); Streaming Mipmap剔除Mipmap通道的法则。它的机制其实和Texture Quality是相似的。咱们晓得,开启Mipmap的纹理之所以会变成原来的4/3倍,实际上是它各个通道所占用的内存之和。举例而言,一个具备11个Mipmap通道的原大小为1MB的纹理(10241024分辨率、ASTC44格局),其内存占用为1+1/4+1/16+…的11项等比数列之和,即约4/3。等比数列的各项就对应了Mipmap0、Mipmap1、Mipmap2…等各个Mipmap通道。那么,当Max Level Reduction参数设置为2时,其实际意义就是保留Mipmap2和后续所有更小的通道,而剔除Mipmap0和Mipmap1通道,此时的内存大小为4/3MB-1MB-1/4MB=85.33KB。这和我在Profiler或GOT Online中看到的数据基本一致。对于采纳Streaming Mipmap计划的倡议。根据上述试验和剖析不难看出,Streaming Mipmap是的确具备一些长处的,对于对内存比拟敏感,尤其是纹理内存占用很大的我的项目,采纳Streaming Mipmap计划是十分正当和举荐的选项。与此同时,它的理论应用要求对我的项目中纹理资源的内存占用有相当的理解和布局——相干设置在Quality中,天经地义地应该思考到不同设施Lod分级时不同的设置。在中低端机中,设置尽量低的Memory Budget和尽量高的Max Level Reduction;在高端机上则恰恰相反,在内存可承受范畴内尽量开启最好的画面体现。除此之外,对于哪些纹理要开启Streaming Mipmap,个别是场景中3D物体的纹理,而UI模块采纳的纹理则尽量敞开。因为Mipmap的意义次要在于适应纹理在间隔镜头远近时的体现须要、防止失真等,而UI齐全不须要这些,开启只会白白浪费内存和计算工夫。感激Faust@UWA问答社区提供了答复 NetworkQ:网络协议规定对立应用大端数据传输,然而测试用小端数据发送,而后服务器收到间接返回给客户端,客户端能够正确解析。所以就有个疑难:数据传输不须要大小端转换?还是底层帮忙转了? 测试例子如下:音讯头部固定4个字节,值为body的长度。服务器没做任何解决收到音讯间接返回。客户端收到音讯先解析头部长度4个字节,拿到body长度再获取body具体数据。没有大小端转换然而解析都正确: { // 测试发送整数 var bodyMsg = "测试发送数据"; var bodyBytes = Encoding.UTF8.GetBytes(bodyMsg); // 发送头部长度,固定4个字节 int headLen = bodyBytes.Length; var headBytes = BitConverter.GetBytes(headLen); // 发送头部长度音讯 _sendSocketAsyncEventArgs.SetBuffer(headBytes, 0, 4); _socket.SendAsync(_sendSocketAsyncEventArgs); // 发送body音讯 _sendSocketAsyncEventArgs.SetBuffer(bodyBytes, 0, bodyBytes.Length); _socket.SendAsync(_sendSocketAsyncEventArgs);}A:测试发送的数据只有在多字节数据处理时才须要思考字节序,如果是Protobuf,是不必思考字节序的。 ...

June 1, 2022 · 1 min · jiezi

TCP与SOCKET

三次握手 四次挥手 SOCKET 在内核中,会维护两个队列: 半连接队列(syn队列),存放处于SYN-RCVD状态的socket全连接队列(accept队列),存放处于ESTABLISHED状态的socketaccept函数会从全连接队列里取出一个连接,如果没有则阻塞。这里取出的是一个新的连接,叫已连接socket,不同于一开始创建的监听socket。Socket在Linux中以文件的形式存在,有自己的文件描述符,读写就如同一个文件流。 socket结构里有一个发送队列和一个接收队列,里面保存着缓存sk_buff,里面可以看到完整的包结构。 数据接收流程网卡接收到数据后写入内存,并向CPU发起中断,操作系统执行中断程序唤醒对应socket等待队列里的进程。如何同时监听多个socket: select将进程放入所有需要监听的socket的等待队列里,如果有一个socket接收到了数据就唤醒进程,然后遍历socket列表查看是哪个socket接收到了数据。 epollint epfd = epoll_create(...)epoll_ctl(epfd, ...)while(1){ int n = epoll_wait(...) for (接收到数据的socket){ //处理 }}利用epoll_ctl将需要监视的socket添加到epoll_create创建的对象(eventpoll)中,然后利用epoll_wait进行阻塞,将进程放入eventpoll的等待队列中。当有socket接收到数据,中断程序将其被eventpoll的就绪列表所(rdllist)引用,然后唤醒进程,进程只需要遍历就绪列表就可以了。socket在eventpoll中利用红黑树存储,而就绪列表利用双向链表存储。

June 16, 2019 · 1 min · jiezi

FTP简介

文件传输协议(File Transfer Protocol),是用于在网络上进行文件传输的一套标准协议,使用客户/服务器模式。采用两个TCP连接来传输文件: 控制连接:服务器监听21端口,客户端发起连接,并通过该连接发送命令,接收服务器应答。如:list:获取文件目录,reter:取一个文件,store:存一个文件数据连接:每当一个文件在客户端与服务器之间传输时,就建立一个全新的数据连接站在服务器的角度来说,有两种工作模式: 主动模式PORT 优点:服务器端只需开放21端口,利于安全管理缺点:如果客户端开了防火墙或者处于内网(NAT网关之后),服务器对客户端发起的连接可能会失败被动模式 优点:对客户端环境没有要求缺点:有些客户端不支持该模式,需要允许从任意远程终端到服务器高位端口的连接,可以指定FTP服务器使用的端口范围

May 25, 2019 · 1 min · jiezi

Linux-硬件信息获取

在 linux 上可以通过 dmidecode 或是 lshw 来获取硬件信息,能够方便的查看系统配置。但它们的输出信息过多,解析起来有些麻烦,另外 lshw 对 usb 接口的网卡支持不好,显示的信息不够,所以在此整理下通过读文件或是一些简单命令来获取硬件信息的方法。 DMI一般情况下内核默认加载了 dmi sysfs ,路径是 /sys/class/dmi 。里面包含了 bios , board , product 等信息。 Bios 通过命令 ls -l /sys/class/dmi/id/bios_* 可以看到支持的 bios 字段,如下: $ ls -l /sys/class/dmi/id/bios_*-r--r--r-- 1 root root 4.0K 5月 8 17:18 /sys/class/dmi/id/bios_date-r--r--r-- 1 root root 4.0K 5月 8 17:18 /sys/class/dmi/id/bios_vendor-r--r--r-- 1 root root 4.0K 5月 8 17:18 /sys/class/dmi/id/bios_version直接读文件即可获取对应值。 Board 通过命令 ls -l /sys/class/dmi/id/board_* 可以看到支持的 board 字段,如下: ...

May 9, 2019 · 6 min · jiezi

详解云计算网络底层技术——Linux network namespace 原理与实践

本文首发于我的公众号 CloudDeveloper(ID: cloud_dev),专注于干货分享,号内有大量书籍和视频资源,后台回复「1024」即可领取,欢迎大家关注,二维码文末可以扫。本文通过 IP 命令操作来简单介绍 network namespace 的基本概念和用法。深入了解可以看看我之前写的两篇文章 Docker 基础技术之 Linux namespace 详解 和 Docker 基础技术之 Linux namespace 源码分析。和 network namespace 相关的操作的子命令是 ip netns 。1. ip netns add xx 创建一个 namespace# ip netns add net1# ip netns lsnet12. ip netns exec xx yy 在新 namespace xx 中执行 yy 命令# ip netns exec net1 ip addr 1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00# ip netns exec net1 bash // 在 net1 中打开一个shell终端# ip addr // 在net1中的shell终端1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00# exit // 退出net1上面 bash 不好区分是当前是在哪个 shell,可以采用下面的方法解决:# ip netns exec net1 /bin/bash –rcfile <(echo “PS1="namespace net1> "")namespace net1> ping www.baidu.com每个 namespace 在创建的时候会自动创建一个回环接口 lo ,默认不启用,可以通过 ip link set lo up 启用。3. network namespace 之间的通信新创建的 namespace 默认不能和主机网络,以及其他 namespace 通信。可以使用 Linux 提供的 veth pair 来完成通信。下面显示两个 namespace 之间通信的网络拓扑:3.1 ip link add type veth 创建 veth pair# ip link add type veth# ip link3: veth0@veth1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/ether 1a:53:39:5a:26:12 brd ff:ff:ff:ff:ff:ff4: veth1@veth0: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/ether 46:df:46:1f:bf:d6 brd ff:ff:ff:ff:ff:ff使用命令 ip link add xxx type veth peer name yyy 指定 veth pair 的名字。3.2 ip link set xx netns yy 将 veth xx 加入到 namespace yy 中# ip link set veth0 netns net0# ip link set veth1 netns net1## ip netns exec net0 ip addr1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:0010: veth0@if11: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000 link/ether 1a:53:39:5a:26:12 brd ff:ff:ff:ff:ff:ff link-netnsid 13.3 给 veth pair 配上 ip 地址# ip netns exec net0 ip link set veth0 up# ip netns exec net0 ip addr1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever10: veth0@if11: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state LOWERLAYERDOWN group default qlen 1000 link/ether 1a:53:39:5a:26:12 brd ff:ff:ff:ff:ff:ff link-netnsid 1# ip netns exec net0 ip addr add 10.1.1.1/24 dev veth0# ip netns exec net0 ip route10.1.1.0/24 dev veth0 proto kernel scope link src 10.1.1.1 linkdown## ip netns exec net1 ip link set veth1 up# ip netns exec net1 ip addr add 10.1.1.2/24 dev veth1可以看到,在配完 ip 之后,还自动生成了对应的路由表信息。3.4. ping 测试两个 namespace 的连通性# ip netns exec net0 ping 10.1.1.2PING 10.1.1.2 (10.1.1.2) 56(84) bytes of data.64 bytes from 10.1.1.2: icmp_seq=1 ttl=64 time=0.069 ms64 bytes from 10.1.1.2: icmp_seq=2 ttl=64 time=0.054 ms64 bytes from 10.1.1.2: icmp_seq=3 ttl=64 time=0.053 ms64 bytes from 10.1.1.2: icmp_seq=4 ttl=64 time=0.053 msDone!4. 多个不同 namespace 之间的通信2 个 namespace 之间通信可以借助 veth pair ,多个 namespace 之间的通信则可以使用 bridge 来转接,不然每两个 namespace 都去配 veth pair 将会是一件麻烦的事。下面就看看如何使用 bridge 来转接。拓扑图如下:4.1 使用 ip link 和 brctl 创建 bridge通常 Linux 中和 bridge 有关的操作是使用命令 brctl (yum install -y bridge-utils ) 。但为了前后照应,这里都用 ip 相关的命令来操作。// 建立一个 bridge# ip link add br0 type bridge# ip link set dev br0 up9: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default qlen 1000 link/ether 42:55:ed:eb:a0:07 brd ff:ff:ff:ff:ff:ff inet6 fe80::4055:edff:feeb:a007/64 scope link valid_lft forever preferred_lft forever4.2 创建 veth pair//(1)创建 3 个 veth pair# ip link add type veth# ip link add type veth# ip link add type veth4.3 将 veth pair 的一头挂到 namespace 中,一头挂到 bridge 上,并设 IP 地址// (1)配置第 1 个 net0# ip link set dev veth1 netns net0# ip netns exec net0 ip link set dev veth1 name eth0# ip netns exec net0 ip addr add 10.0.1.1/24 dev eth0# ip netns exec net0 ip link set dev eth0 up## ip link set dev veth0 master br0# ip link set dev veth0 up// (2)配置第 2 个 net1# ip link set dev veth3 netns net1# ip netns exec net1 ip link set dev veth3 name eth0# ip netns exec net1 ip addr add 10.0.1.2/24 dev eth0# ip netns exec net1 ip link set dev eth0 up## ip link set dev veth2 master br0# ip link set dev veth2 up// (3)配置第 3 个 net2# ip link set dev veth5 netns net2# ip netns exec net2 ip link set dev veth5 name eth0# ip netns exec net2 ip addr add 10.0.1.3/24 dev eth0# ip netns exec net2 ip link set dev eth0 up# # ip link set dev veth4 master br0# ip link set dev veth4 up这样之后,竟然通不了,经查阅 参见 ,是因为原因是因为系统为bridge开启了iptables功能,导致所有经过br0的数据包都要受iptables里面规则的限制,而docker为了安全性(我的系统安装了 docker),将iptables里面filter表的FORWARD链的默认策略设置成了drop,于是所有不符合docker规则的数据包都不会被forward,导致你这种情况ping不通。解决办法有两个,二选一:关闭系统bridge的iptables功能,这样数据包转发就不受iptables影响了:echo 0 > /proc/sys/net/bridge/bridge-nf-call-iptables为br0添加一条iptables规则,让经过br0的包能被forward:iptables -A FORWARD -i br0 -j ACCEPT第一种方法不确定会不会影响docker,建议用第二种方法。我采用以下方法解决:iptables -A FORWARD -i br0 -j ACCEPT结果:# ip netns exec net0 ping -c 2 10.0.1.2PING 10.0.1.2 (10.0.1.2) 56(84) bytes of data.64 bytes from 10.0.1.2: icmp_seq=1 ttl=64 time=0.071 ms64 bytes from 10.0.1.2: icmp_seq=2 ttl=64 time=0.072 ms— 10.0.1.2 ping statistics —2 packets transmitted, 2 received, 0% packet loss, time 999msrtt min/avg/max/mdev = 0.071/0.071/0.072/0.008 ms# ip netns exec net0 ping -c 2 10.0.1.3PING 10.0.1.3 (10.0.1.3) 56(84) bytes of data.64 bytes from 10.0.1.3: icmp_seq=1 ttl=64 time=0.071 ms64 bytes from 10.0.1.3: icmp_seq=2 ttl=64 time=0.087 ms— 10.0.1.3 ping statistics —2 packets transmitted, 2 received, 0% packet loss, time 1000msrtt min/avg/max/mdev = 0.071/0.079/0.087/0.008 msDone!我的公众号 CloudDeveloper(ID: cloud_dev),号内有大量书籍和视频资源,后台回复「1024」即可领取,分享的内容包括但不限于云计算虚拟化、容器、OpenStack、K8S、雾计算、网络、工具、SDN、OVS、DPDK、Linux、Go、Python、C/C++编程技术等内容,欢迎大家关注。 ...

March 5, 2019 · 4 min · jiezi

Linux 网络工具详解之 ip tuntap 和 tunctl 创建 tap/tun 设备

本文首发于我的公众号 CloudDeveloper(ID: cloud_dev),专注于干货分享,号内有大量书籍和视频资源,后台回复「1024」即可领取,欢迎大家关注,二维码文末可以扫。在前面一篇文章中,我们已经介绍了 tap/tun 的基本原理,本文将介绍如何使用工具 tunctl 和 ip tuntap 来创建并使用 tap/tun 设备。tunctl安装首先在 centos 的环境中安装 tunctl。[root@localhost ~]# vim /etc/yum.repos.d/nux-misc.repo[nux-misc]name=Nux Miscbaseurl=http://li.nux.ro/download/nux/misc/el7/x86_64/enabled=0gpgcheck=1gpgkey=http://li.nux.ro/download/nux/RPM-GPG-KEY-nux.roubuntu 是 apt-get install uml-utilities。man tunctl 查看 tunctl 手册,用法如下:Synopsistunctl [ OPTIONS ] [ -u owner ] [-g group] [ -t device-name ]-u 参数指定用户名,表明这个接口只受该用户控制,这个接口发生的事不会影响到系统的接口。-g 指定一组用户-t 指定要创建的 tap/tun 设备名。[OPTIONS] 部分:-b 简单打印创建的接口名字-n 创建 tun 设备-p 创建 tap 设备,默认创建该设备-f tun-clone-device 指定 tun 设备对应的文件名,默认是 /dev/net/tun,有些系统是 /dev/misc/net/tun。-d interfacename 删除指定接口使用常见用法:默认创建 tap 接口:tunctl以上等价于 tunctl -p为用户 user 创建一个 tap 接口:# tunctl -u user创建 tun 接口:tunctl -n为接口配置 IP 并启用:# ifconfig tap0 192.168.0.254 up为接口添加路由:# route add -host 192.168.0.1 dev tap0删除接口:# tunctl -d tap0ip tuntap安装命令行输入 ip help 查看 ip 命令是否支持 tuntap 工具,支持的话就会显示 tuntap 选项:[root@localhost ~]# ip helpUsage: ip [ OPTIONS ] OBJECT { COMMAND | help } ip [ -force ] -batch filenamewhere OBJECT := { link | addr | addrlabel | route | rule | neigh | ntable | tunnel | tuntap | maddr | mroute | mrule | monitor | xfrm | netns | l2tp | tcp_metrics | token }不支持就请升级或下载最新的 iproute2 工具包,或者使用上面介绍的 tunctl 工具。使用输入 ip tuntap help 查看详细使用命令:[root@localhost ~]# ip tuntap helpUsage: ip tuntap { add | del } [ dev PHYS_DEV ] [ mode { tun | tap } ] [ user USER ] [ group GROUP ] [ one_queue ] [ pi ] [ vnet_hdr ] [ multi_queue ]Where: USER := { STRING | NUMBER } GROUP := { STRING | NUMBER }常见用法:创建 tap/tun 设备:ip tuntap add dev tap0 mod tap # 创建 tap ip tuntap add dev tun0 mod tun # 创建 tun删除 tap/tun 设备:ip tuntap del dev tap0 mod tap # 删除 tap ip tuntap del dev tun0 mod tun # 删除 tunPS: user 和 group 参数和 tunctl 的 -u、 -g 参数是一样的。以上两个工具,我们更推荐使用 ip tuntap,一个是因为 iproute2 更全更新,已经逐步在替代老旧的一些工具,另一个是因为 tunctl 在某些 Debian 类的系统上支持不全。总结tunctl 和 ip tuntap 的常见使用方式。更推荐使用 ip tuntap 工具。我的公众号 CloudDeveloper(ID: cloud_dev),号内有大量书籍和视频资源,后台回复「1024」即可领取,分享的内容包括但不限于云计算虚拟化、容器、OpenStack、K8S、雾计算、网络、工具、SDN、OVS、DPDK、Linux、Go、Python、C/C++编程技术等内容,欢迎大家关注。 ...

March 1, 2019 · 2 min · jiezi

用 tap/tun 做虚拟机的网卡

本文首发于我的公众号 CloudDeveloper(ID: cloud_dev),专注于干货分享,号内有大量书籍和视频资源,后台回复「1024」即可领取,欢迎大家关注,二维码文末可以扫。在云计算时代,虚拟机和容器已经成为标配。它们背后的网络管理都离不开一样东西,就是虚拟网络设备,或者叫虚拟网卡,tap/tun 就是在云计算时代非常重要的虚拟网络网卡。tap/tun 是什么tap/tun 是 Linux 内核 2.4.x 版本之后实现的虚拟网络设备,不同于物理网卡靠硬件网路板卡实现,tap/tun 虚拟网卡完全由软件来实现,功能和硬件实现完全没有差别,它们都属于网络设备,都可以配置 IP,都归 Linux 网络设备管理模块统一管理。作为网络设备,tap/tun 也需要配套相应的驱动程序才能工作。tap/tun 驱动程序包括两个部分,一个是字符设备驱动,一个是网卡驱动。这两部分驱动程序分工不太一样,字符驱动负责数据包在内核空间和用户空间的传送,网卡驱动负责数据包在 TCP/IP 网络协议栈上的传输和处理。用户空间与内核空间的数据传输在 Linux 中,用户空间和内核空间的数据传输有多种方式,字符设备就是其中的一种。tap/tun 通过驱动程序和一个与之关联的字符设备,来实现用户空间和内核空间的通信接口。在 Linux 内核 2.6.x 之后的版本中,tap/tun 对应的字符设备文件分别为:tap:/dev/tap0tun:/dev/net/tun设备文件即充当了用户空间和内核空间通信的接口。当应用程序打开设备文件时,驱动程序就会创建并注册相应的虚拟设备接口,一般以 tunX 或 tapX 命名。当应用程序关闭文件时,驱动也会自动删除 tunX 和 tapX 设备,还会删除已经建立起来的路由等信息。tap/tun 设备文件就像一个管道,一端连接着用户空间,一端连接着内核空间。当用户程序向文件 /dev/net/tun 或 /dev/tap0 写数据时,内核就可以从对应的 tunX 或 tapX 接口读到数据,反之,内核可以通过相反的方式向用户程序发送数据。tap/tun 和网络协议栈的数据传输tap/tun 通过实现相应的网卡驱动程序来和网络协议栈通信。一般的流程和物理网卡和协议栈的交互流程是一样的,不同的是物理网卡一端是连接物理网络,而 tap/tun 虚拟网卡一般连接到用户空间。如下图的示意图,我们有两个应用程序 A、B,物理网卡 eth0 和虚拟网卡 tun0 分别配置 IP:10.1.1.11 和 192.168.1.11,程序 A 希望构造数据包发往 192.168.1.0/24 网段的主机 192.168.1.1。基于上图,我们看看数据包的流程:应用程序 A 构造数据包,目的 IP 是 192.168.1.1,通过 socket A 将这个数据包发给协议栈。协议栈根据数据包的目的 IP 地址,匹配路由规则,发现要从 tun0 出去。tun0 发现自己的另一端被应用程序 B 打开了,于是将数据发给程序 B.程序 B 收到数据后,做一些跟业务相关的操作,然后构造一个新的数据包,源 IP 是 eth0 的 IP,目的 IP 是 10.1.1.0/24 的网关 10.1.1.1,封装原来的数据的数据包,重新发给协议栈。协议栈再根据本地路由,将这个数据包从 eth0 发出。后续步骤,当 10.1.1.1 收到数据包后,会进行解封装,读取里面的原始数据包,继而转发给本地的主机 192.168.1.1。当接收回包时,也遵循同样的流程。在这个流程中,应用程序 B 的作用其实是利用 tun0 对数据包做了一层隧道封装。其实 tun 设备的最大用途就是用于隧道通信的。tap/tun 的区别看到这里,你可能还不大明白 tap/tun 的区别。tap 和 tun 虽然都是虚拟网络设备,但它们的工作层次还不太一样。tap 是一个二层设备(或者以太网设备),只能处理二层的以太网帧;tun 是一个点对点的三层设备(或网络层设备),只能处理三层的 IP 数据包。tap/tun 的应用从上面的数据流程中可以看到,tun 设备充当了一层隧道,所以,tap/tun 最常见的应用也就是用于隧道通信,比如 VPN,包括 tunnel 和应用层的 IPsec 等,其中比较有名的两个开源项目是 openvpn 和 VTun。总结tun/tap 虚拟网卡,对应于物理网卡,如 eth0。tun/tap 驱动包括字符设备驱动和网卡驱动。tun/tap 常用于隧道通信。我的公众号 CloudDeveloper(ID: cloud_dev),号内有大量书籍和视频资源,后台回复「1024」即可领取,分享的内容包括但不限于云计算虚拟化、容器、OpenStack、K8S、雾计算、网络、工具、SDN、OVS、DPDK、Linux、Go、Python、C/C++编程技术等内容,欢迎大家关注。 ...

February 28, 2019 · 1 min · jiezi

解决 docker 容器无法通过 IP 访问宿主机问题

问题起源在使用 docker 的过程中我不幸需要在 docker 容器中访问宿主机的 80 端口, 而这个 80 端口是另外一个容器 8080 端口映射出去的. 当我在容器里通过 docker 的网桥 172.17.0.1 访问宿主机时, 居然发现:curl: (7) Failed to connect to 172.17.0.1 port 80: No route to host查找问题原因可以确定的是容器与宿主机是有网络连接的, 因为可以在容器内部通过 172.17.0.1 Ping 通宿主机:root@930d07576eef:/# ping 172.17.0.1PING 172.17.0.1 (172.17.0.1) 56(84) bytes of data.64 bytes from 172.17.0.1: icmp_seq=1 ttl=64 time=0.130 ms也可以在容器内部访问其它内网和外网.iptables 显示也允许 docker 容器访问:# iptables –list | grep DOCKERDOCKER-ISOLATION all – anywhere anywhere DOCKER all – anywhere anywhere Chain DOCKER (1 references)Chain DOCKER-ISOLATION (1 references)之后在查找一些资料后发现这个问题: NO ROUTE TO HOST network request from container to host-ip:port published from other container.解释正如 Docker Community Forms 所言, 这是一个已知的 Bug, 宿主机的 80 端口允许其它计算机访问, 但是不允许来自本机的 Docker 容器访问. 必须通过设置 firewalld 规则允许本机的 Docker 容器访问.gypark 指出可以通过在 /etc/firewalld/zones/public.xml 中添加防火墙规则避免这个问题:<rule family=“ipv4”> <source address=“172.17.0.0/16” /> <accept /></rule>注意这里的 172.17.0.0/16 可以匹配 172.17.xx.xx IP 段的所有 IP.之后重启下防火墙:systemctl restart firewalld之后就可以在 docker 容器内部访问宿主机 80 端口.其它问题实际上当我又用 vmware 新开了一台虚拟机希望能重现这个问题的时候, 发现在新的虚拟机上居然没有类似的问题. 也就是说容器可以直接通过172.17.0.1访问宿主机 80 端口, 查看防火墙配置也没看到有172.17.xx.xx的白名单.猜测是由于在新的虚拟机安装的 docker 是 Docker version 1.12.5, build 047e51b/1.12.5, 也就是 Red Hat 从 docker 开源版本迁出开发的版本, 而之前的是 Docker version 17.06.2-ce, build cec0b72 属于 Docker-CE, 可能是 docker 版本有差异, Red Hat 顺便把那个 Known Bug 修复了. ...

January 9, 2019 · 1 min · jiezi

深度 | 线下场景的客流数字化探索与应用

阿里妹导读:数字化的时代,无论是商场里的大小专柜,还是小区门口的便利店,大多仍处于“数据荒漠”中。店家不知道店内多少商品被人浏览,多少衣服被试穿了,作为顾客的我们也不知道哪些商品是最受同龄人喜爱的畅销好物。在新零售场景中,线下的行为数据是潜藏的宝矿。如何进行数字化升级,更好辅佐商家和消费者,成为摆在我们眼前的重要课题。下面,搜索事业部的算法专家京五将为大家详细介绍阿里在线下场景的客流数字化探索与应用。在互联网时代,数据是所有应用的基础,淘宝的商家可以基于商品历史的点击成交量来判断店内各个商品的情况,并做出相应的运营行为,淘宝的买家会根据商品历史的成交数据,评论数据等,来辅助自己判断是否进行购买,同时我们平台也会基于用户和商品的历史数据,来训练模型,预测各个商品的点击率,预测各个用户的偏好,使展示的结果更符合用户的需求。可以看出,数据对于各个不同的角色都有很重要的作用。在互联网中,获取数据相对容易,反观线下零售场景,大部分数据都是缺失的,商家并不知道店内多少商品被浏览了,多少商品被试穿了,买家也不知道各件商品的历史数据。因此,我们的客流数字化相关的探索,就是要将线下的用户和商品的行为数据收集起来,让线下的行为也能有迹可循,为商业决策和市场运营提供准确有效的数据支撑,将传统零售中的导购经验逐渐数字化成可量化和统计的数字指标,能够辅助商家运营,同时帮助用户进行决策。基于这些数据,也能够让算法在线下发挥更大的作用。整体方案整体方案如下图所示,方案涉及场外的选品策略指导,线下引流,进店的人群画像,顾客轨迹跟踪,人货交互数据沉淀,试衣镜互动/推荐,以及离店后的线上二次触达。从场外到场内再到线上,构成了整体全流程的产品方案。客流数字化探索在门店客流数字化的探索中,硬件部署上,我们使用了门店已有的监控摄像头和RFID标签,并结合视觉及射频相关技术,通过在门店部署GPU终端进行计算。技术方案上,我们基于人脸识别技术,识别进店用户的性别,年龄,新老客等基础属性,并通过行人检测跟踪与跨摄像头的行人重识别技术跟踪用户在门店内的动线变化,同时得到整体门店各个区域的热力图分布,此外,还通过摄像头与RFID 多传感器融合的技术识别用户在门店内的行为,包括翻动,试穿等,精确定位门店内各个商品的浏览与试穿频次以及用户在线下的偏好。下面会主要介绍其中的行人检测,行人重识别和动作识别这3个技术方向相关的优化。行人检测在新零售的客流数字化场景中,我们需要通过监控摄像头对门店客流的进店频次、性别、动作、行为轨迹、停留时间等全面的记录和分析。要达到我们的目标,首先需要能够检测并识别出摄像头中的行人。虽然目前YOLO等目标检测算法可以做到近乎实时的计算性能,但其评估环境都是Titan X、M40等高性能GPU,且只能支持单路输入。无论从硬件成本或是计算能力方面考虑,这些算法都无法直接应用到真实场景中。当然YOLO官方也提供了像YOLOv3-Tiny这种轻量级的模型方案,但模型性能衰减过大,在COCO上mAP下降超过40%。同时现有目标检测方案的泛化能力还比较弱,不同场景的差异对模型性能会造成较大的影响。门店场景下的视角、光线、遮挡、相似物体干扰等情况与开源数据集差异较大,直接使用基于VOC、COCO数据集训练的模型对该场景进行检查,效果非常不理想。我们分别针对模型的性能和在实际数据集的效果两方面做了相应的优化。网络结构精简与优化我们在YOLO框架的基础上对模型进行改进,实现了一种轻量级实时目标检测算法,在服饰门店的真实场景下,和YOLOv3相比,模型性能下降不超过2%,模型大小缩小至原来的1/10,在Tesla P4上对比FPS提升268%,可直接部署到手机、芯片等边缘设备上,真实业务场景中一台GTX1070可以同时支持16路摄像机同时检测,有效节约了门店改造的经济成本。标准YOLOv3的网络结构有106层,模型大小有237M,为了设计一个轻量级的目标检测系统,我们使用Tiny DarkNet来作为骨干网络,Tiny DarkNet是一个极简的网络结构,最大通道数为512,模型大小仅4M,该模型结构比YOLO官方的YOLOv3-Tiny的骨干网络还要精简,但精简网络会造成特征抽取能力的衰减,模型性能下降剧烈,在我们人工标注的2万多张服饰门店场景数据集上,替换后的Tiny DarkNet + FPN结构较原生结构的AP-50(IOU=0.5)下降30%。我们在特征抽取网络之后进行Spatial Pyramid Pooling[10],与原特征一起聚合,之后通过下采样与反卷积操作将不同层级特征合并,希望将底层的像素特征和高层的语义特征进行更充分的融合来弥补特征抽取能力的下降,整体网络结构如下图所示,精简后的检测模型大小约为原来的1/10。知识蒸馏进一步优化知识蒸馏[2]通过Teacher Network输出的Soft Target来监督Student Network学习网络中Dark Knowledge,以实现Knowledge Transfer的目的,与量化、剪枝、矩阵近似等方法常被用来实现对模型的压缩。但蒸馏与量化等方法之间又是可以互相结合的,而且蒸馏本身对模型的修改更加透明,无需特殊的依赖及执行框架。上图是我们网络蒸馏的模型结构设计,蒸馏时我们采用原生YOLOv3作为Teacher Network,虽然YOLOv3拥有较好的检测性能,且结构上与我们的模型比较相似,但直接在二者输出层之间建立L2约束,无法克服Teacher Network中的噪声及回归预测的波动,结果反而抑制了Student Network的学习。实验中发现Hint Layer的损失设计和回归预测的不确定性是蒸馏效果的核心问题,强行在对应Channel之间建立损失约束的方式过于严苛。对于普通卷积而言,我们无需要求Teacher / Student Network的Input Channel顺序保持一致,仅需要整个输入的分布是一致的。每个Channel相当于一次采样结果,相同的分布,采出的样本顺序可能多种多样,但整体结果符合相同分布,同时经过激活函数的Channel分布不再稳定,需要进行归一处理。为了避免Teacher Network回归预测本身的不稳定,回归损失设计时仍以Ground Truth为目标,将Teacher Network的Output作为Bound,仅对误差大于Teacher Network的部分进行约束,本质上是在借Teacher Network来进行Online Hard Example Mining。行人重识别行人重识别(Person Re-identification)问题是指在跨摄像头场景下,给定待查找的行人图片,查找在其他摄像头是否出现该人。一般用来解决跨摄像头追踪。在线下门店场景中,每个门店都会在各个不同的区域安装摄像头,当顾客在店内逛时,我们需要了解用户是如何在各个区域之间活动,了解各个区域客流的去向与来源,因此需要将各个不同摄像头中同一个行人进行关联。行人特征提取行人重识别的难点在于,多个摄像头下拍摄行人的角度不同,图像中的行人可能72变,同时还有可能会有不同程度的遮挡,导致直接使用整体的行人特征来做重识别非常具有挑战性,那能不能用人脸识别做行人重识别?理论上是可以的,但是在实际场景中非常难应用,首先,广泛存在后脑勺和侧脸的情况,做正脸的人脸识别难,其次,摄像头拍摄的像素可能不高,尤其是远景摄像头里面人脸截出来很可能都没有32x32的像素。所以人脸识别在实际的重识别应用中存在很大的限制。行人重识别问题中,如何学得一个鲁棒的行人特征表示成为了一个很关键的问题。学得行人特征表示最直观的方式是直接以整张行人图片作为输入,提取一个全局特征,全局特征的目标是学到能够区分不同行人之间最突出的信息,比如衣服颜色等,来区分这个行人。然而监控场景的复杂性,使得这样的方法的准确性受到了很大的限制,比如,各个摄像头之间存在色差,并且门店的不同区域的光照条件会有差异,此外,还有很多穿相似服装的行人。同时由于目前行人重识别数据集在体量及丰富性上有比较大的欠缺,一些不突出,不频繁出现的细节特征在全局特征的训练中很容易被忽略。要解决上面提到的问题,使用局部特征替换全局特征是一个比较好的解决方案,基于局部特征的行人重识别方法将原始输入表示成多个特征块,每一个特征块代表一个局部的特征,基于局部特征的方法能够更关注行人的局部细节方面的特征。基于局部特征的方法,也存在一些问题,这一类方法将行人划分为各个独立的语义分块,并没有考虑各个局部特征之间的关联,因此,在我们的方案中,我们使用到了多级局部特征的融合方案,在考虑各个局部特征的同时考虑多个局部特征的关联关系,具体网络结构如下图所示,在原始的局部特征的基础之上增加了多个不同尺度的局部特征以及全局特征,学到的特征不仅能够表示各个部位的细节特征,还能表达不同部位融合在一起的特征,相较原始版本更加丰富化。目前基于此版本模型还在持续优化中,在Market数据集上Rank@1能达到96.19%,使用同样骨干网络结构的情况下提取全局特征的版本的Rank@1只能达到89.9%,而仅使用local特征的版本Rank@1能够达到92.5%,融合的方案相比两个版本均有较明显的提升。跨数据集的行人重识别的探索与尝试由于线下场景的特殊性,我们的模型需要部署到各家不同的门店,各个门店的光线,环境存在很大的差异,不同门店的摄像头安装的角度也会有些许不同,因此我们在一个数据集上训练的模型可能并不适用于所有门店,然而我们又不可能逐家门店去做数据的标注,因此,我们想通过一种方式,让我们的模型能够自适应到新的门店的数据中。在门店中,由于顾客是在一个封闭空间,因此顾客在各个摄像头之间的转移是存在一定的规律的,比如说:顾客肯定是最先出现在门口的摄像头,顾客只能在相邻的两个区域之间进行转移等,基于门店场景的特性,我们首先尝试了基于摄像头时空信息的混合模型,参考[7],模型结构如下图所示:混合模型首先基于原始的视觉特征的分类器来计算各个摄像头以及不同时间间隔之间转移的概率分布,再使用时空信息与原始分类器结合得到最终的结果。人货动作检测除了基础的客流动线数据以外,顾客在门店中的行为数据也是非常有价值的,我们尝试使用视觉结合RFID射频信号的融合方案,试图解决顾客在门店中与货物的交互问题,即哪个顾客在什么地点翻动/拿起了哪一件商品,比较类似线上的点击数据。人货交互的数据在线下是很重要的一个环节,人货交互的数据可以让商家知道哪些商品被翻动的多,了解哪些商品比较能够吸引顾客,哪一类顾客更喜欢哪些风格的商品,同时这一部分数据也完善了整个门店的漏斗转化,以前商家仅仅能根据成交来判定每个商品的受欢迎程度,而有些潜在畅销款可能是由于摆放的位置不恰当,导致可能根本没有顾客仔细看到,导致最终成交额较低,同时有的商品虽然成交笔数不少,但是实际上被顾客拿起的次数也特别多,可能是因为这件商品在一个更显眼的位置,相比同样成交笔数的拿起次数较少的商品,实际转化率更低。补全这个环节的数据对商家的线下运营有很关键的作用,同时这一部分行为数据在商家线上线下商品打通之后为线上服务起到最重要的作用。人货交互的数据是目前线下数据缺失的比较严重的环节,商家一般都能很容易的拿到商品的成交的统计数据,而人货交互的数据由于发生更频繁,且不易判断,因此整体数据的收集难度比较高,此外人货交互的数据需要精确到具体的SKU,单纯的顾客发生了动作并没有太大的意义,因此在人货动作检测的方案上,我们设计了一套结合视觉技术和RFID射频信号的融合方案,得到最终的人货交互数据。下图为整体方案:门店中装配有监控摄像机设备与RFID接收器器设备,分别录制实时视频与RFID标签受激反射的 时序信号,首先基于回传的RFID信号与检测哪些RFID标签可能被翻动了,由于店铺服务员已经将RFID标签的EPC编号与商品的 SKU编号关联入库,基于被翻动的标签EPC编号可以取到对应商品的SKU,同时,使用回传的顾客图片检测出疑似有在翻动商品的顾客,并根据顾客的图像坐标进行坐标变换,得到该顾客的真实物理坐标,最后,将检测出的疑似被翻动的商品与疑似有翻动商品动作的顾客进行关联,得到商品与行人的最佳匹配。其中基于RFID射频技术的商品动作识别是一个比较新的尝试。当顾客翻动衣服时,衣服上的RFID标签会随之发生微小抖动,RFID接收机设备记录标签反射的信号RSSI,Phase等特征值的变化,回传到后台,算法通过对每个天线回传的信号值进行分析判断商品是否发生翻动。基于RFID信号判断商品翻动存在诸多问题,包括信号自身噪声、环境多径效应、偶然电磁噪声、货柜对信号遮挡的影响等。同时RFID反射信号的大小与接收器离标签距离远近存在非线性关系,其中,d代表RFID标签与接收器之间距离, ,受Multipath和当前环境的影响,表示各种静态设备误差带来的偏移。从公式中可以看出,接收器安装的位置,商店环境等都会给RFID信号带来很大影响,寻找统一的可以适用于不同商店、不同位置接收器的翻动判断算法存在很大挑战。最初的版本我们使用RSSI和Phase的原始值作为特征值来训练模型,这样的模型存在一个问题,在我们的样本不充足的情况下,受环境的影响较大,在真实环境中往往不能达到离线测试的结果,因此,我们试图基于原始的信号值产生于空间位置不那么强相关的特征值来辅助动作的判断。虽然频率信息中的幅度信息与空间位置存在关系,但是当我们只关注于频率分布(不同频率成份的占比)时,可以将频率信息也当成与空间位置信息无关的特征。频率信息的获取需要对RSSI信号与Phase信号进行离散傅利叶变换, 然后统计频率信号与相位信号的分布图。对得到的分布图,计算当前分布与前一个时刻分布的JS散度(相对于KL散度,JS散度具有加法的对称性,因此可以用来衡量多个分布之间的相对距离)。基于相邻时刻前后两个样本的JS散差异的版本在我们的测试数据上能够达到94%的识别精度,相比最初版本基于原始的RSSI值和phase值作为特征的版本的91.9%的精度,有一定的提升。基于图像的顾客动作检测是经典的分类问题,为了减小对计算能力的需求,我们使用了:MobileNet[12]对行人检测的图像进一步分类,并根据模型Logits输出进行了最优化参数寻优,在保持分类精度时,提高正例召回率,确保正例尽可能被召回,如下图所示。我们通过时间关联程度与动作可疑程度两个维度同时进行匹配,使得最终的匹配行人与翻动商品的准确率达到85.8%。客流数字化应用客流数字化产出的客流相关数据不仅仅用于商家的线下运营,同时我们也基于这部分数据在线下场的流量分发上有一些初步应用,淘宝是线上的一个很大的流量分发的入口,淘宝的搜索和推荐决定了消费者当前能看到哪些商品,也同时影响了各个商家和商品的整体流量情况,搜索和推荐就是将商家、商品和用户做匹配,将适当的商品展示给合适的用户,满足消费者的购物体验的同时,也平衡各个商家商品的流量分配,避免流量的浪费,实现流量的最大化的价值。在线下商场,也有一样的流量分发的需求。但是线下场相比线上,有两个比较大的挑战:1) 线下目前没有统一的入口,类似线上的搜索和推荐应用,无法触达到用户;2) 线下没有类似线上丰富的日志和行为数据,没有数据支撑比较难做到精准的个性化,无法优化效果。在线下场的流量分发的探索中,我们使用商场已有的互动屏幕、门店的互动屏幕作为流量分发的出口,同时,利用前文提到的客流数字化沉淀的数据来支撑线下场的个性化流量分发。场外引流屏场外引流屏的作用,是进行第一级的流量分发,首先需要通过不同的互动玩法,营销活动吸引用户,再通过屏幕对用户进行个性化的优惠券投放,引导用户进入不同的门店。在传统商场中,用户刚进来商场,可能会随机地在这个楼层进行活动,当看到感兴趣的品牌完成进店的活动,或者用户会基于导览屏,大概了解商场楼层的品牌分布情况,再进行有一定针对性的浏览。而我们的引流屏的作用是将合适的优惠推荐给对应的人,从而引导用户进店,相当于在商场中岛进行整体的流量分发,将集中在中岛的用户往各个不同的方向进行引导。整体方案如下图所示:整体方案依赖三部分的数据,分别是基于用户的图像特征产出的人群属性数据,以及各个店铺的进店人群分布数据和店铺的其他统计量的特征,基于用户当前的属性特征与店铺的人群分布进行匹配,可以得到初步的个性化的店铺推荐结果,此外,使用店铺本身的统计量特征作为辅助信息,在同等匹配条件下额外考虑各个店铺本身的热度,效率等维度特征,以及当前所提供的优惠券的力度信息,得到最终的优惠券的排序,并展示给用户。场内试衣屏场内试衣屏的作用是做第二层的流量分发,即用户进店后,需要推荐哪些商品展示给用户。在传统的门店中,用户进店后会在店内进行随机的浏览,对于感兴趣的衣服会找导购员提供试穿,试穿后导购员也会对顾客进行推荐。整个过程中存在一些问题,首先,用户对于商品的浏览和商品摆放的位置关系很大,橱窗的商品会更容易吸引用户注意,而部分较密集的衣架区,用户可能没有办法注意到部分货品;其次,试穿之后导购进行的推荐也会因人而异,和导购本身的素质关系也较大,有些经验丰富的导购员可以根据你个人的长相气质推荐更适合你的商品,而更多的导购员只能简单的基于当前的热销款来进行推荐,无法做到因人而异。试衣屏推荐要解决的就是上述的两个问题,整体展现形式如下图:在用户进行试穿时,会在镜子侧方显示商品的详情信息,包括目前商品是否有折扣等,同时会基于用户的试穿行为,推荐相关商品与搭配商品,给部分商品一次额外的展示机会,同时也能够基于用户的试穿以及用户当前的图像特征给出个性化的推荐结果,方便用户的选购,即使用户暂时没有这个消费习惯,镜子屏幕上的推荐结果也能对导购员进行一些辅助决策,能够帮助导购员给用户推荐更加个性化更加丰富的商品。整体算法方案如下图所示:考虑到隐私问题,在我们的应用中,我们不去尝试通过人脸关联到对应的id,仅在场内通过用户的行为和其他用户行为的相似性进行推荐。工程实现AI inference是GPU终端计算重要的一环,最开始探索的时候,AI inference采用串行模式:通过观察测试数据,我们惊讶地发现,虽然程序已经处于视频流图片处理饱和的状态,但是6核心CPU的使用率才到150%,GPU的使用率才到30%,也就是说,超过一半的硬件资源处于闲置状态。 为了使得原本间歇性闲置的资源得到重新的利用,我们改造成了流水线模式,结构图如下所示:在多进程实现的流水线方案中,由于每个进程的数据都是相互独立的,一个进程产生或修改的数据对另一个进程而言它是无感知。如何提高进程间的数据传递是能否高效实现并发的关键点。 我们采用了基于mmap ctypes实现的共享内存,对比管道、socket多进程通讯机制,共享内存在多进程数据通讯方案中是非常高效和灵活,参考multiprocessing Value的解决方案,使用ctypes内置的基本数据结构来实现我们的数据模型,非常方便的进行内存切分并转换成可用的数据结构。结合业务情况,我们的流水线工作模式会将各个阶段分割为子任务,我们还设计了图片共享队列,整个过程只需要写入一次图片数据,各个阶段只需要从这个共享队列读取图片即可,等所有流程都操作完之后再从图片队列删除这个图片数据,这样就能保证图片操作的正确性和高效性。通过测试发现,我们实现的共享内存队列在读取数据上比pipe方式快了300多倍。业务效果目前我们客流数字化的数据已经沉淀到相应的产品,以下是基础客流的示意图,品牌商可以看到门店每日的基础客流量以及分时段的客流情况,了解各个门店当前的经营状况。下图为区域热力图和区域动线图,区域热力图展示了门店在一天内各个小时各个区域的人流量密度情况,我们将各个不同摄像头的数据进行整合,最终映射到门店的平面CAD图上展示区域热力,让门店能够更直观的看到各个区域的热度,区域动线图展示了各个区域客流的去向和来源的占比,基于区域热力和动线数据,商家能够清晰的了解到门店各个区域的密度情况以及各个区域之间顾客的转移情况,目前合作的品牌商也会基于区域的数据对店内的陈列做适当的调整,甚至有门店基于动线的数据重新调整整个门店的区域分布情况。 下图为门店进店客流的人群画像,展示了门店每天进店客流的性别和年龄的分布,商家会基于进店的人群画像数据与当前品牌的目标人群进行对比,并基于实际进店客流的分布调整门店陈列商品的品类结构以及不同类型商品的占比。本文作者:京五阅读原文本文来自云栖社区合作伙伴“阿里技术”,如需转载请联系原作者。

January 8, 2019 · 1 min · jiezi

k8s与aws--如何在cloud-provider=aws的k8s中设置externalTrafficPolicy为local

如何在启用cloud-provider=aws的k8s集群中设置service 的externalTrafficPolicy为local关于externalTrafficPolicy的local和cluster两个值,在之前的文章中,我们已经讲过。大家可以参考从service的externalTrafficPolicy到podAntiAffinity这篇文章。而在aws中,假如你自己部署k8s集群,并且启用了cloud-provider=aws,那么需要做一些其他的工作,否则service 的externalTrafficPolicy为local后,无法访问。首先保证kube-proxy启动参数里加入hostname-override设置 - –hostname-override=$(NODE_NAME) env: - name: NODE_NAME valueFrom: fieldRef: apiVersion: v1 fieldPath: spec.nodeName然后需要设置VPC的DHCP domainname 为 .compute.internal。参考资料:当您创建 VPC 时,我们会自动创建 DHCP 选项集,并将它们与 VPC 相关联。此设置包括两个选项:domain-name-servers=AmazonProvidedDNS 和 domain-name=domain-name-for-your-region。

January 7, 2019 · 1 min · jiezi

浏览器请求响应慢,该从哪些方面分析

查看网络面板响应比较慢可以从两个层次去考虑连接初始化阶段耗时请求和响应耗时查看关键指标:排队达到浏览器最大并发数量限制有更高优先级的请求插队,低优先级的任务被延后系统内存空间不足,浏览器使用磁盘空间拥堵 原因和排队中类似DNS查询 花在DNS查询上的时间Proxy negotiation. 代理协商Request sent. 请求被发送Request to ServiceWorker. 请求被发送到ServiceWorkWaiting (TTFB). 等待收到响应的第一个字节Content Download. 内容下载Receiving Push. 浏览器通过HTTP/2 Server Push接受数据Reading Push. 浏览器读取之前收到的数据常见问题现象及解决方法出现长时间的排队或者拥堵原因 浏览器对同一域名最大的TCP链接数有限制,超过限制的请求会被排队。参见浏览器同域名请求的最大并发数限制。为什么会达到最大并发数?一次性获取的资源数量太多资源体积太大,很多都在下载中有些请求响应太慢或者无响应。例如1分钟之内,每隔10秒钟发送一个无响应的请求,随着可用的请求慢慢被占满,正常的请求排队数量会越来越多。解决方法问题1和问题2比较容易发现,也比较好处理。主要从两个角度解决减少请求数量。可以移除不必要的请求,或者将多个请求合并成1个。例如雪碧图使用域名分片。例如使用不同的域名指向相同的资源,从而突破单域名的限制。例如img1.tt.cc/1.jpg和img2.tt.cc/1.jpg前端给每个ajax请求设置超时 防止过多的无响应请求占据着连接资源,可以在超时之后释放连接。有些ajax库,例如jQuery的ajax, 默认是没有设置超时时长的,当你在使用这些库时,最好明确的设置后端设置请求处理超时 后端接口应当设置最大超时时长长时间的TTFB出现这种问题要从两个方面排查客户端到服务端之间的网络通信比较慢服务端的响应比较慢,可能是服务端压力太大,到达带宽上线,内存溢出,高CPU, IOwait高, Recv-Q高,或者sql查询慢等各种原因。注意:对于同一个源的请求,如果有些请求很快,有些请求很慢。那么问题一般是服务端的问题。因为如果是出现网络通信比较慢,那么则所有的请求都会变慢。解决方案知道问题的真实原因,其实问题也就解决了一半了。参考network-performancenetwork issue guide浏览器同域名请求的最大并发数限制

January 2, 2019 · 1 min · jiezi

CSS和网络性能

CSS对于呈现页面至关重要 - 在找到,下载和解析所有CSS之前,浏览器不会开始呈现 - 因此我们必须尽可能快地将其加载到用户的设备上。 关键路径上的任何延迟都会影响我们的“开始渲染”并让用户看到空白屏幕。什么是大问题?从广义上讲,这就是CSS对性能至关重要的原因:浏览器在构建渲染树之前无法渲染页面;渲染树是DOM和CSSOM的组合结果;DOM是HTML加上需要对其进行操作的任何阻塞JavaScript;CSSOM是针对DOM应用的所有CSS规则;使用async和defer属性很容易使JavaScript无阻塞;CSS不容易异步;所以要记住的一个好的经验法则是,您的页面会在你最慢的样式表加载完成之后才展示。考虑到这一点,我们需要尽快构建DOM和CSSOM。 在大多数情况下,构建DOM相对较快:您的第一个HTML响应是DOM。 但是,由于CSS几乎总是HTML的子资源,因此构建CSSOM通常需要更长的时间。在这篇文章中,我想看看CSS如何证明是网络上的一个重大瓶颈(本身和其他资源)以及我们如何缓解它,从而缩短关键路径并缩短开始渲染的时间。使用关键CSS如果你有能力,减少Start Render时间的最有效方法之一就是使用Critical CSS模式:识别Start Render所需的所有样式(通常是首屏所需的样式), 将它们内联到文档的<head>中的<style>标记中,并从这里异步加载剩余的样式表。虽然这种策略是有效的,但并不简单:高度动态的网站很难从中提取样式,流程需要自动化,我们必须对折叠率甚至是什么做出假设,很难捕获边缘情况和工具 仍处于相对初期阶段。 如果您正在使用大型或遗留代码库,事情会变得更加困难……拆分媒体类型如果实现关键CSS非常棘手 - 它可能只是一种选择,我们将主要的CSS文件拆分为其各自的媒体查询。 这样做的实际结果是浏览器会……以非常高的优先级下载当前上下文所需的任何CSS(中等,屏幕大小,分辨率,方向等),阻止关键路径;以非常低的优先级下载当前上下文不需要的任何CSS,完全脱离关键路径。基本上,浏览器有效地延迟了不需要渲染当前视图的任何CSS。<link rel=“stylesheet” href=“all.css” />如果我们将所有CSS捆绑到一个文件中,那么它会这样子加载:如果我们可以将单个全渲染阻塞文件拆分为各自的媒体查询:<link rel=“stylesheet” href=“all.css” media=“all” /><link rel=“stylesheet” href=“small.css” media="(min-width: 20em)" /><link rel=“stylesheet” href=“medium.css” media="(min-width: 64em)" /><link rel=“stylesheet” href=“large.css” media="(min-width: 90em)" /><link rel=“stylesheet” href=“extra-large.css” media="(min-width: 120em)" /><link rel=“stylesheet” href=“print.css” media=“print” />然后我们看到网络以不同方式处理文件:浏览器仍将下载所有CSS文件,但它只会阻止渲染完成当前上下文所需的文件。避免在CSS文件中使用@import我们可以做的下一件事就是帮助Start Render更加简单。 避免在CSS文件中使用@import。@import,根据它的工作原理,很慢。 对于Start Render性能来说真的非常糟糕。 这是因为我们正在关键路径上积极创建更多循环路径:下载HTML;HTML请求CSS;(这是我们希望能够构建渲染树的地方,但是;)CSS请求更多CSS;构建渲染树。以下HTML:<link rel=“stylesheet” href=“all.css” />包含在all.css中@import@import url(imported.css);我们最终得到这样的瀑布图:通过简单地将其展平为两个<link rel =“stylesheet”/>和去掉@imports:<link rel=“stylesheet” href=“all.css” /><link rel=“stylesheet” href=“imported.css” />我们得到一个更健康的瀑布图:请注意HTML中的@import要完全理解本节,我们首先需要了解浏览器的预装载扫描程序:所有主流浏览器都实现了通常称为预装载扫描程序的辅助惰性解析器。 浏览器的主要解析器负责构建DOM,CSSOM,运行JavaScript等,并且随着文档的不同部分阻止它而不断停止和启动。 Preload Scanner可以安全地跳过主解析器并扫描HTML的其余部分,以发现对其他子资源(例如CSS文件,JS,图像)的引用。 一旦发现它们,Preload Scanner就会开始下载它们,以便主要解析器接收它们并在以后执行/应用它们。 Preload Scanner的推出使网页性能提高了大约19%,所有这些都不需要开发人员参与。 这对用户来说是个好消息!我们作为开发人员需要警惕的一件事是无意中隐藏了Preload Scanner中可能发生的事情。 稍后会详细介绍。本节介绍WebKit和Blink的Preload Scanner中的错误,以及Firefox和IE / Edge的Preload Scanner中的低效率。Firefox和IE / Edge:将@import放在HTML中的JS和CSS之前在Firefox和IE / Edge中,Preload Scanner似乎没有使用<script src =“”>或<link rel =“stylesheet”/>之后定义的任何@import。这意味着这个HTML:<script src=“app.js”></script><style> @import url(app.css);</style>将产生这个瀑布图:由于无效预装载扫描程序导致Firefox失去并行化(N.B.在IE / Edge中出现相同的瀑布。)这个问题的直接解决方案是交换<script>或<link rel =“stylesheet”/>和<style>块。 但是,当我们更改依赖顺序时,这可能会破坏事物(想想他们之间的关联)。这个问题的首选解决方案是完全避免使用@import并使用第二个<link rel =“stylesheet”/>:<link rel=“stylesheet” href=“style.css” /><link rel=“stylesheet” href=“app.css” />瀑布图如下:两个<link rel =“stylesheet”/> s让我们回到并行化。 (N.B. IE / Edge中出现相同的瀑布。)Blink和WebKit:用HTML格式引用@import URL仅当您的@import URL缺少引号(“)时,WebKit和Blink的行为与Firefox和IE / Edge完全相同。这意味着WebKit和Blink中的Preload Scanner存在错误。简单地将@import包装在引号中将解决问题,您无需重新排序任何内容。 不过,和以前一样,我的建议是完全避免使用@import,而是选择第二个<link rel =“stylesheet”/>。之前<link rel=“stylesheet” href=“style.css” /><style> @import url(app.css);</style>瀑布图:我们的@ import网址中缺少引号会破坏Chrome的预装扫描程序(N.B.在Opera和Safari中会出现相同的瀑布。)修改之后:<link rel=“stylesheet” href=“style.css” /><style> @import url(“app.css”);</style>在我们的@ import网址中添加引号可修复Chrome的Preload Scanner(N.B.在Opera和Safari中也会出现相同的瀑布。)这绝对是WebKit / Blink中的一个错误 - 缺少引号不应该隐藏Preload Scanner中的@imported样式表。不要在Async 脚本之前放置<link rel =“stylesheet”/>上一节讨论了如何通过其他资源减慢CSS,本节将讨论CSS如何无意中延迟下载资源的下载,主要是使用异步加载代码段插入的JavaScript,如下所示:<script> var script = document.createElement(‘script’); script.src = “analytics.js”; document.getElementsByTagName(‘head’)[0].appendChild(script);</script>在所有浏览器中都存在一种有意和预期的迷人行为,但我从未遇到过一个了解它的开发人员。 当您考虑它可以带来的巨大性能影响时,这是非常令人惊讶的:如果有任何当前CSS在加载,浏览器将不会执行<script>。<link rel=“stylesheet” href=“slow-loading-stylesheet.css” /><script> console.log(“I will not run until slow-loading-stylesheet.css is downloaded.”);</script>这是设计的。 这是故意的。 当前正在下载任何CSS时,HTML中的任何同步<script>都不会执行。 这是一个简单的防御策略来解决<script>可能会询问页面样式的边缘情况:如果脚本在CSS到达并被解析之前询问页面的颜色,那么JavaScript给我们的答案 可能是不正确或陈旧的。 为了缓解这种情况,浏览器在构造CSSOM之前不会执行<script>。这样做的结果是,CSS下载时间的任何延迟都会对你的异步片段产生连锁反应。 用一个例子可以很好地说明这一点。如果我们在异步片段前放置<link rel =“stylesheet”/>,则在下载和解析该CSS文件之前它不会运行。<link rel=“stylesheet” href=“app.css” /><script> var script = document.createElement(‘script’); script.src = “analytics.js”; document.getElementsByTagName(‘head’)[0].appendChild(script);</script>根据这个顺序,我们可以清楚地看到JavaScript文件甚至在构建CSSOM之前甚至没有开始下载。 我们完全失去了任何并行化:在异步代码段之前使用样式表可以撤消我们并行化的机会。有趣的是,Preload Scanner希望提前获得对analytics.js的引用,但是我们无意中隐藏了它:“analytics.js”是一个字符串,并且在<<之前不会成为可标记的src属性 script>元素存在于DOM中。 这是我早些时候说的,当我稍后再说这个时。第三方供应商提供这样的异步代码片段以更安全地加载脚本是很常见的。 开发人员对这些第三方持怀疑态度,并在页面后面放置异步片段也是很常见的。 虽然这是出于最好的意图 - 我不想在我自己的资产之前放置第三方<script>! - 通常可能是净损失。 事实上,谷歌分析甚至告诉我们该做什么,他们是对的:将此代码作为第一项复制并粘贴到您要跟踪的每个网页的<HEAD>中。所以我的建议是:如果您的<script> … </ script>块不依赖于CSS,请将它们放在样式表上方。以下是我们转向此模式时会发生的代码:<script> var script = document.createElement(‘script’); script.src = “analytics.js”; document.getElementsByTagName(‘head’)[0].appendChild(script);</script><link rel=“stylesheet” href=“app.css” />交换样式表和异步代码片段可以重新获得并行化。现在您可以看到我们已经完全重新获得了并行化,并且页面加载速度提高了近2倍。在CSS之前放置任何非CSSOM查询JavaScript; 在CSS之后放置任何CSSOM查询JavaScript更进一步,除了异步加载片段之外,我们应该如何更普适地加载CSS和JavaScript? 为了解决这个问题,我提出了以下问题并从那里开始工作:如果:在CSSOM构造上阻止CSS后定义的同步JS;同步JS阻止DOM构造那么 - 假设没有相互依赖 - 哪个更快/更喜欢?Script -> style;style -> script?答案是:如果文件不相互依赖,那么您应该将阻塞脚本置于阻塞样式之上 - 没有必要将JavaScript执行延迟到JavaScript实际上不依赖的CSS。(Preload Scanner确保即使在脚本上阻止了DOM构造,CSS仍然会并行下载。)如果你的一些JavaScript做了但有些不依赖于CSS,那么加载同步JavaScript和CSS的绝对最佳顺序是将JavaScript分成两部分并将其加载到CSS的任何一侧:<!– This JavaScript executes as soon as it has arrived. –><script src=“i-need-to-block-dom-but-DONT-need-to-query-cssom.js”></script><link rel=“stylesheet” href=“app.css” /><!– This JavaScript executes as soon as the CSSOM is built. –><script src=“i-need-to-block-dom-but-DO-need-to-query-cssom.js”></script>使用这种加载模式,我们可以按最佳顺序进行下载和执行。 我为下面的截图中的微小细节道歉,但希望你能看到代表JavaScript执行的小粉红色标记。 entry(1)是计划在其他文件到达和/或执行时执行某些JavaScript的HTML; entry(2)执行它到达的那一刻; entry(3)是CSS,所以不执行任何JavaScript; 在CSS完成之前,entry(4)实际上不会执行。注: 您必须根据自己的特定用例测试此模式:根据您之前的CSS JavaScript文件与CSS本身之间的文件大小和执行成本是否存在巨大差异,可能会有不同的结果。 测试,测试,测试。将<link rel =“stylesheet”/>放在<body>中这个最终策略是一个相对较新的策略,对感知性能和渐进式渲染有很大好处。 它也非常友好。在HTTP / 1.1中,我们将所有样式连接到一个主要包中是很典型的。 我们称之为app.css:<!DOCTYPE html><html><head> <link rel=“stylesheet” href=“app.css” /></head><body> <header class=“site-header”> <nav class=“site-nav”>…</nav> </header> <main class=“content”> <section class=“content-primary”> <h1>…</h1> <div class=“date-picker”>…</div> </section> <aside class=“content-secondary”> <div class=“ads”>…</div> </aside> </main> <footer class=“site-footer”> </footer></body>这带来三个关键的低效率:任何给定的页面只会使用app.css中的一小部分样式:我们几乎肯定会下载比我们需要的更多的CSS。我们受限于一种效率低下的缓存策略:例如,对仅在一个页面上使用的日期选择器上当前所选日期的背景颜色进行更改将需要我们缓存整个app.css。整个app.css阻止渲染:如果当前页面只需要17%的app.css并不重要,我们仍然需要等待其他83%才能开始渲染。使用HTTP / 2,我们可以开始解决点(1)和(2):<!DOCTYPE html><html><head> <link rel=“stylesheet” href=“core.css” /> <link rel=“stylesheet” href=“site-header.css” /> <link rel=“stylesheet” href=“site-nav.css” /> <link rel=“stylesheet” href=“content.css” /> <link rel=“stylesheet” href=“content-primary.css” /> <link rel=“stylesheet” href=“date-picker.css” /> <link rel=“stylesheet” href=“content-secondary.css” /> <link rel=“stylesheet” href=“ads.css” /> <link rel=“stylesheet” href=“site-footer.css” /></head><body> <header class=“site-header”> <nav class=“site-nav”>…</nav> </header> <main class=“content”> <section class=“content-primary”> <h1>…</h1> <div class=“date-picker”>…</div> </section> <aside class=“content-secondary”> <div class=“ads”>…</div> </aside> </main> <footer class=“site-footer”> </footer></body>现在我们正在解决冗余问题,因为我们能够加载更适合页面的CSS,而不是不加选择地下载所有内容。 这减少了关键路径上阻塞CSS的大小。我们还可以采用更有意思的缓存策略,只缓存破坏需要它的文件,并保持其余部分不受影响。我们还没有解决的问题是它仍然阻止渲染 - 我们仍然只有最慢的样式表。 这意味着如果无论出于何种原因,site-footer.css需要很长时间才能下载,浏览器无法开始渲染.site-header。但是,由于Chrome最近发生了变化(我相信版本69),以及Firefox和IE / Edge中已经存在的行为,<link rel =“stylesheet”/> 只会阻止后续内容的呈现,而不是 整页。 这意味着我们现在能够像这样构建我们的页面:<!DOCTYPE html><html><head> <link rel=“stylesheet” href=“core.css” /></head><body> <link rel=“stylesheet” href=“site-header.css” /> <header class=“site-header”> <link rel=“stylesheet” href=“site-nav.css” /> <nav class=“site-nav”>…</nav> </header> <link rel=“stylesheet” href=“content.css” /> <main class=“content”> <link rel=“stylesheet” href=“content-primary.css” /> <section class=“content-primary”> <h1>…</h1> <link rel=“stylesheet” href=“date-picker.css” /> <div class=“date-picker”>…</div> </section> <link rel=“stylesheet” href=“content-secondary.css” /> <aside class=“content-secondary”> <link rel=“stylesheet” href=“ads.css” /> <div class=“ads”>…</div> </aside> </main> <link rel=“stylesheet” href=“site-footer.css” /> <footer class=“site-footer”> </footer></body>这样做的实际结果是,我们现在能够逐步呈现我们的页面,在页面可用时有效地将页面输送样式添加到页面中。在目前不支持这种新行为的浏览器中,我们不会遇到性能下降:我们会回到原来的行为,我们只有最慢的CSS文件加载完成才会展示页面。总结本文中有很多要消化的内容。 它最终超越了我最初打算写的帖子。 尝试总结加载CSS的最佳网络性能实践:Lazyload Start Start Render不需要的任何CSS: 拆分关键CSS; 或将您的CSS拆分为媒体查询。避免@import: 在你的HTML中; 特别是在CSS中; 并提防Preload Scanner的奇怪之处。警惕同步CSS和JavaScript命令: 在CSSOM完成之前,CSS之后定义的JavaScript将无法运行 所以如果你的JavaScript不依赖于你的CSS,在CSS之前加载它; 如果它取决于你的CSS,在CSS之后加载它。在DOM需要时加载CSS,这将取消阻止“开始渲染”并允许渐进式渲染我上面概述的所有内容都遵循规范或已知/预期的行为,但是,一如既往,自己测试一切。 虽然这在理论上都是正确的,但在实践中事情总是有所不同。 套用中国的一句老话,实践出真知啊。 ...

December 7, 2018 · 3 min · jiezi