<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="/rss.xsl" type="text/xsl"?>
<rss version="2.0">
  <channel>
    <title>IT瘾geek推荐</title>
    <link>https://itindex.net/tags/geek</link>
    <description>IT社区推荐资讯 - ITIndex.net</description>
    <language>zh</language>
    <copyright>https://itindex.net/</copyright>
    <generator>https://itindex.net/</generator>
    <docs>http://backend.userland.com/rss</docs>
    <image>
      <url>https://itindex.net/images/logo.gif</url>
      <title>IT社区推荐资讯 - ITIndex.net</title>
      <link>https://itindex.net/tags/geek</link>
    </image>
    <item>
      <title>只花不到 150 元，部署一套可能是最佳体验的家庭网络系统</title>
      <link>https://itindex.net/detail/62939-%E4%BD%93%E9%AA%8C-%E5%AE%B6%E5%BA%AD-%E7%BD%91%E7%BB%9C</link>
      <description>&lt;div&gt;  &lt;p&gt;原文地址:   &lt;a href="https://yojigen.tech/30.html" rel="nofollow" title="https://yojigen.tech/30.html"&gt;https://yojigen.tech/30.html&lt;/a&gt;&lt;/p&gt;  &lt;h2&gt;前言&lt;/h2&gt;  &lt;p&gt;哪个男孩不想要一个可以自由驰骋互联网的网络呢？&lt;/p&gt;  &lt;p&gt;关于家中的互联网建设这一块，已经不知道有多少博主聊过，自己也是来来回回折腾了很多种方案。&lt;/p&gt;  &lt;p&gt;最近终于找到了我直到目前为止我认为可能是最完美的方案——PaoPaoDNS+PaoPaoGateWay 。然后就在自己家的软路由系统里部署了起来，目前也是稳定用了一个月感觉非常的舒适。&lt;/p&gt;  &lt;p&gt;但是我家里面用的是软路由+虚拟机的方案，本身全套机器下载价格要 400 元左右了，而且机器也是做了一点点硬改，没有改机能力的人恐怕用起来也会比较麻烦。&lt;/p&gt;  &lt;p&gt;于是我就在想，能不能用比较便宜的方案，和较低的功耗，用一些市面上常见的设备来实现这一套方案呢？&lt;/p&gt;  &lt;h2&gt;最终预期的结果&lt;/h2&gt;  &lt;p&gt;在尽可能保证正常网络不会受到影响的情况下，实现网内透明代理，以用于科学上网。&lt;/p&gt;  &lt;p&gt;附赠功能: 去广告，虚拟局域网组网等&lt;/p&gt;  &lt;h2&gt;设备选择和采购&lt;/h2&gt;  &lt;p&gt;于是经过我的思考，最后做出了如下的设备方案:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;一台硬路由，要可以刷入支持设置静态路由，静态 DHCP 的系统(OpenWRT 、爱快、ROS 等)&lt;/li&gt;   &lt;li&gt;一台可以部署 Docker 容器的机器，例如一台小型 ARM 主机(玩客云，x905 电视盒子等)&lt;/li&gt;   &lt;li&gt;一台低功耗 x86 小主机(最好支持 AES 硬解)&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;以上设备各位读者可以根据自身的经济条件和现有的设备来决定如何选择，比如全放进虚拟机里之类。而我这次选择的设备是下面这几个。&lt;/p&gt;  &lt;h3&gt;硬路由-JCG Q30 Pro: 59 元&lt;/h3&gt;  &lt;p&gt;   &lt;a href="https://yojigen.tech/wp-content/uploads/2024090412211939_c4ca4238a0b92382.webp" rel="nofollow"&gt;    &lt;img alt="" src="https://yojigen.tech/wp-content/uploads/2024090412211939_c4ca4238a0b92382.webp"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;这个价格不算特别的大众，这是一台天线坏掉的机器，而我的家里面因为还有三个其他的无线路由器当做 AP 使用，所以我不需要无线网络功能。&lt;/p&gt;  &lt;p&gt;这台机器的芯片是联发科的 mt7981 ，内存是 256m(也有 512m 的版本)，性能较好，可以轻松跑满千兆。而且这款机器还是被 OpenWRT 和 immortalwrt 官方支持的型号，可以直接刷主线版的固件，这样也可以避免一些网上所谓的“大神”编译的固件中的各种坑。&lt;/p&gt;  &lt;p&gt;机器怎么刷机我这里就不说了，网上有一些现成的资料，在这里说会导致教程太过于复杂，而且也许你的设备也不需要刷机，对吧。&lt;/p&gt;  &lt;p&gt;其实我个人目前比较推荐的机器是爱快 IK-Q3000 ，不用刷机就能享受官方的爱快的系统固件，机身自己支持 AX3000 ，如果没有其他无线组网，一台机器就能解决大部分人家中的路由和 WiFI 了。现在这个机器京东售价 159 ，同样所谓 mt7981 芯片的机器，他也就比别的机器贵了 20-30 。30 块钱买个免刷机带售后的爱快系统我觉得还是挺香的。(不过不清楚这台机器有没有硬件转发，如果没有的话还是不要用了)&lt;/p&gt;  &lt;p&gt;   &lt;a href="https://yojigen.tech/wp-content/uploads/2024090412213128_c81e728d9d4c2f63.webp" rel="nofollow"&gt;    &lt;img alt="" src="https://yojigen.tech/wp-content/uploads/2024090412213128_c81e728d9d4c2f63.webp"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;  &lt;h3&gt;ARM 主机-玩客云: 28 元&lt;/h3&gt;  &lt;p&gt;   &lt;a href="https://yojigen.tech/wp-content/uploads/2024090412214239_eccbc87e4b5ce2fe.webp" rel="nofollow"&gt;    &lt;img alt="" src="https://yojigen.tech/wp-content/uploads/2024090412214239_eccbc87e4b5ce2fe.webp"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;玩客云算是我们垃圾佬应该非常熟悉的一个东西了，他本身是一个拿来做 PCDN 的所谓的“挖矿”设备，后续随着 PCDN 的收益越来越低，运营商对 PCDN 的打击等等原因，现在网上有着大量的机器在流通。而且由于存世量多，玩的人也多，这机器在价格便宜的同时，机器的玩法也开发的比较全面，很多系统都能刷入进去。&lt;/p&gt;  &lt;p&gt;我们这里选择给它刷入 Armbian ，以用来部署 Docker 容器，刷机方法大家也可以参考网络资料。&lt;/p&gt;  &lt;h3&gt;X86 小主机-中兴 CT321G2: 本人购入 79 元，咸鱼现价 50 元&lt;/h3&gt;  &lt;p&gt;   &lt;a href="https://yojigen.tech/wp-content/uploads/2024090412215356_a87ff679a2f3e71d.webp" rel="nofollow"&gt;    &lt;img alt="" src="https://yojigen.tech/wp-content/uploads/2024090412215356_a87ff679a2f3e71d.webp"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;   &lt;a href="https://yojigen.tech/wp-content/uploads/2024090412215911_e4da3b7fbbce2345.webp" rel="nofollow"&gt;    &lt;img alt="" src="https://yojigen.tech/wp-content/uploads/2024090412215911_e4da3b7fbbce2345.webp"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;这个小机器是之前无意中发现的，机身外壳的做工还不错，虽然是塑料但是很厚实。这个机器由于只有一个千兆网口，不好做软路由，且只有一个无法更换 4g 硬盘，所以对于垃圾佬来说，可玩性就有点差了，导致价格一直不高。但是这台机器的 CPU 是小主机里面不太常见的 AMD GX-218GL ，这块 CPU 性能和 J1900 差不太多，功耗也都是在 10w 左右，但是他有一个 J1900 没有的优势: 支持 AES 硬解。&lt;/p&gt;  &lt;p&gt;大家都知道 AES 硬解意味着什么，加上小尺寸，低功耗，所以这台机器非常适合拿来做我们的网关机。&lt;/p&gt;  &lt;p&gt;注意: 这个机器只有 VGA 输出，需要你准备支持 VGA 的显示器和线，或者转换线。&lt;/p&gt;  &lt;h2&gt;系统搭建&lt;/h2&gt;  &lt;h3&gt;主网络&lt;/h3&gt;  &lt;p&gt;首先要保证你的网络是正常的，你需要了解如何使用你的路由器正确的设置上网功能。
我这里以最常见的网络地址作为演示，你可以根据你的需求来设置自己的网段。&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;路由器 IP: 192.168.1.1
DHCP 范围: 192.168.1.101-192.168.1.200
局域网段: 192.168.1.0/24
子网掩码: 255.255.255.0
网关机 IP: 192.168.1.2
Docker 容器机 IP: 192.168.1.3&lt;/code&gt;&lt;/pre&gt;  &lt;h3&gt;网关机&lt;/h3&gt;  &lt;p&gt;首先要准备一下 PaoPaoGateWay 的系统镜像，由于是在物理机运行，所以需要全网卡驱动的支持。官方 Github 上的镜像是默认不带全网卡驱动的，需要我们用官方的 docker 来定制一下 ios 镜像。
定制方式非常简单，随便找一台 x86 的装有 docker 的机器(官方容器不支持 arm)，运行下面两条命令，就能在当前目录获得一个具有全部网卡的 ios 镜像了。&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;docker pull sliamb/ppgwiso:fullmod
docker run --rm -v .:/data sliamb/ppgwiso:fullmod&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;如果你不方便定制，也可以下载我定制好的镜像文件(记得解压)。&lt;/p&gt;  &lt;p&gt;   &lt;a href="https://github.com/mouyase/PaoPaoGateWay/releases/download/20240812-9eb91d3/paopao-gateway-x86-64-custom-364b136.zip" rel="nofollow"&gt;paopao-gateway-x86-64-custom-364b136.zip&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;准备一个写磁盘的工具，Rufus 、balenaEtcher 、UltraISO 之类的都可以，我这里使用 Rufus 。
准备一个 U 盘，将镜像文件写入到 U 盘里。&lt;/p&gt;  &lt;p&gt;然后将 U 盘插入 x86 小主机，在 Bios 里设置为从 U 盘启动系统，通电自动启动系统，最后用网线将网口和路由器的 Lan 口进行连接。&lt;/p&gt;  &lt;p&gt;这样网关机这里就算设置完成了。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;注:&lt;/strong&gt;&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;如果有条件可以将网关机接上屏幕开机测试一下，看看能不能正常启动开机看到日志。&lt;/li&gt;   &lt;li&gt;系统测试稳定之后，可以尝试用 PE 将 ISO 镜像写入到系统内置磁盘里，这样就可以不插 U 盘启动了，不过这样后面更新会稍微麻烦点。&lt;/li&gt;&lt;/ul&gt;  &lt;h3&gt;Docker 机&lt;/h3&gt;  &lt;p&gt;首先搭建好 Docker 环境，这里根据不同的设备和系统会有不同的方法，请根据网络上的教程自行操作。&lt;/p&gt;  &lt;p&gt;创建目录 DNS ，并且在其中创建   &lt;code&gt;docker-compose.yaml&lt;/code&gt;文件，用于配置容器。&lt;/p&gt;  &lt;p&gt;   &lt;code&gt;docker-compose.yaml&lt;/code&gt;文件内容参考如下。&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;services:
  paopaodns_china:
    image: sliamb/paopaodns:latest
    container_name: PaoPaoDNS-China
    restart: always
    volumes:
      - ./PaoPaoDNS-China:/data
    networks:
      dns:
        ipv4_address: 172.30.1.10 # IP 地址为 Docker 内网分配地址，用于内网访问
    environment:
      - TZ=Asia/Shanghai
      - UPDATE=weekly
      - DNS_SERVERNAME=PaoPaoDNS-China
      - DNSPORT=53
      - CNAUTO=no

  paopaodns _global:
    image: sliamb/paopaodns:latest
    container_name: PaoPaoDNS-Global
    restart: always
    volumes:
      - ./PaoPaoDNS-Global:/data
    networks:
      dns:
        ipu4_address: 172.30.1.20 # IP 地址为 Docker 内网分配地址，用于内网访问
    environment:
      - TZ=Asia/Shanghai
      - UPDATE=weekly
      - DNS_SERVERNAME=PaoPaoDNS-Global
      - DNSPORT=53
      - CNAUTO=yes
      - CNFALL=yes
      - CN_TRACKER=yes
      - USE_HOSTS=no
      - IPU6=no
      - SOCKS5=192.168.1.2:1080 # IP 地址为网关机的 IP 地址
      - SERVER_IP=192.168.1.3 # IP 地址为本台宿主机的 IP 地址
      - CUSTOM_FORWARD=192.168.1.2:53 # IP 地址为网关机的 IP 地址
      - AUTO_FORWARD=yes
      - AUTO_FORWARD_CHECK=yes
      - USE_MARK_DATA=yes
      - HTTP_FILE=yes
    ports:
      - &amp;quot;5304:5304/udp&amp;quot;
      - &amp;quot;5304:5304/tcp&amp;quot;
      - &amp;quot;7889:7889/tcp&amp;quot;

  adguard_home:
    image: adguard/adguardhome:latest
    container_name: AdGuardHome
    restart: always
    depends_on:
    - paopaodns_china
    - paopaodns_global
    volumes:
    - ./AdGuardHome:/opt/adguardhome/work
    - ./AdGuardHome:/opt/adguardhome/conf
    networks:
      dns:
        ipv4_address: 172.30.1.2 # IP 地址为 Docker 内网分配地址，用于内网访问
    environment:
      - TZ=Asia/Shanghai
    ports:
      - &amp;quot;53:53/udp&amp;quot;
      - &amp;quot;53:53/tcp&amp;quot;
      - &amp;quot;80:80/tcp&amp;quot; # 如果不是使用 80 端口作为网页端口则需要添加对应的端口映射
      - &amp;quot;3000:3000/tcp&amp;quot; # 安装成功后可以删除

networks:
  dns:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 172.30.1.0/24
          gateway: 172.30.1.1&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;解释一下这个配置文件。&lt;/p&gt;  &lt;p&gt;这个配置文件定义了三个容器。&lt;/p&gt;  &lt;p&gt;两个 PaoPaoDNS ，用来作为 AdguardHome 的上游 DNS ，其中一个容器没有做特殊的配置，仅当做本地递归 DNS 服务器使用。而另一台则添加了分流相关的设置，用于对需要出国的设备进行 DNS 分流处理。&lt;/p&gt;  &lt;p&gt;AdguardHome ，用于提供本地 DNS 服务，给不同的客户端配置不同的上游 DNS ，以及去广告(虽然是他的本职，但是这里反而成了附赠的功能了)。&lt;/p&gt;  &lt;p&gt;注: 爱快官方之前在论坛中提到，系统更新到 3.7.12 后，DHCP 设置将支持对不同的客户端配置不同的 DNS ，所以用爱快系统的可以根据需求不使用 AdguardHome 。&lt;/p&gt;  &lt;p&gt;接下来启动容器。&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;docker compose up -d&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;容器正常启动后，则可以使用   &lt;code&gt;本机 IP:3000&lt;/code&gt;访问 AdguardHome 的安装页面了，设置一下用户名和密码，以及 WebUI 的端口(建议 80)即可。&lt;/p&gt;  &lt;h3&gt;网络配置&lt;/h3&gt;  &lt;p&gt;因为我文章里是使用的 OpenWRT 作为路由系统，所以这里也是用 OpenWRT 来演示。&lt;/p&gt;  &lt;p&gt;启动网关机和 Docker 容器机，让他们的信息出现在你的路由器里面。&lt;/p&gt;  &lt;p&gt;首先要固定一下 IP 。&lt;/p&gt;  &lt;p&gt;打开 网络→DHCP/DNS→静态地址分配，将网关机的 IP 固定为   &lt;code&gt;192.168.1.2&lt;/code&gt;，将 Docker 机的 IP 固定为   &lt;code&gt;192.168.1.3&lt;/code&gt;。&lt;/p&gt;  &lt;p&gt;然后将 DHCP 默认的 DNS 设置为   &lt;code&gt;192.168.1.3&lt;/code&gt;(如果需要输入两个地址就都填一样的)。&lt;/p&gt;  &lt;p&gt;   &lt;a href="https://yojigen.tech/wp-content/uploads/2024091719020618_1bb59a5cf2f75a5e.webp" rel="nofollow"&gt;    &lt;img alt="" src="https://yojigen.tech/wp-content/uploads/2024091719020618_1bb59a5cf2f75a5e.webp"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;接下来要设置网内设备的 DNS 。&lt;/p&gt;  &lt;p&gt;打开 网络→接口→lan→DHCP 服务器→高级设置，在 DHCP 选项中添加。&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;6,192.168.1.3&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;这个值就是你的 Docker 机的 IP 。&lt;/p&gt;  &lt;p&gt;   &lt;a href="https://yojigen.tech/wp-content/uploads/2024091719050519_dcf9d4e16ce100ea.webp" rel="nofollow"&gt;    &lt;img alt="" src="https://yojigen.tech/wp-content/uploads/2024091719050519_dcf9d4e16ce100ea.webp"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;然后重启一下网关机和 Docker 机，让他们可以获取到新的 IP 和 DNS 。&lt;/p&gt;  &lt;p&gt;接下来网页打开 AdguardHome 的后台   &lt;a href="http://192.168.1.3" rel="nofollow" title="http://192.168.1.3"&gt;http://192.168.1.3&lt;/a&gt;，在 DNS 设置中，将上游 DNS 设置为   &lt;code&gt;172.30.1.10&lt;/code&gt;，并且关闭缓存。&lt;/p&gt;  &lt;p&gt;   &lt;a href="https://yojigen.tech/wp-content/uploads/2024091719082383_16a1523166bd1ae9.webp" rel="nofollow"&gt;    &lt;img alt="" src="https://yojigen.tech/wp-content/uploads/2024091719082383_16a1523166bd1ae9.webp"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;这时我们可以试一下我们的 DNS 能否正常使用。&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;nslookup whoami.03k.org 192.168.1.3
服务器:  Unknown
Address:  192.168.1.3

非权威应答:
名称:    whoami.03k.org
Address:  123.234.123.234 #连接权威 DNS 服务器的 IP=你的宽带 IP&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;返回的地址如果是你的公网 IP 网段，就说明递归 DNS 已经生效了。&lt;/p&gt;  &lt;p&gt;接下来是设置静态路由，我们回到 OpenWRT 的界面。&lt;/p&gt;  &lt;p&gt;打开 网络→路由→静态 IPv4 路由，添加一条新的静态路由，类型   &lt;code&gt;unicast&lt;/code&gt;，目标   &lt;code&gt;11.0.0.0/8&lt;/code&gt;，网关   &lt;code&gt;192.168.1.2&lt;/code&gt;。&lt;/p&gt;  &lt;p&gt;   &lt;a href="https://yojigen.tech/wp-content/uploads/2024091719092071_6e583d6e5568ccf3.webp" rel="nofollow"&gt;    &lt;img alt="" src="https://yojigen.tech/wp-content/uploads/2024091719092071_6e583d6e5568ccf3.webp"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;   &lt;a href="https://yojigen.tech/wp-content/uploads/2024091719175972_96a852a85b5e3e8d.webp" rel="nofollow"&gt;    &lt;img alt="" src="https://yojigen.tech/wp-content/uploads/2024091719175972_96a852a85b5e3e8d.webp"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;最后要屏蔽掉对 FakeIP 的 NAT 。&lt;/p&gt;  &lt;p&gt;打开 网络→防火墙→通信规则，添加一条新的规则，源区域为   &lt;code&gt;lan&lt;/code&gt;，目标区域为   &lt;code&gt;wan&lt;/code&gt;，目标地址为   &lt;code&gt;11.0.0.0/8&lt;/code&gt;，操作为   &lt;code&gt;丢弃&lt;/code&gt;。&lt;/p&gt;  &lt;p&gt;   &lt;a href="https://yojigen.tech/wp-content/uploads/2024091719135684_6f2f75e01f73364b.webp" rel="nofollow"&gt;    &lt;img alt="" src="https://yojigen.tech/wp-content/uploads/2024091719135684_6f2f75e01f73364b.webp"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;   &lt;a href="https://yojigen.tech/wp-content/uploads/2024091719135961_b1a793f2fdf259c6.webp" rel="nofollow"&gt;    &lt;img alt="" src="https://yojigen.tech/wp-content/uploads/2024091719135961_b1a793f2fdf259c6.webp"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;注:&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;如果有使用一些纯靠 IP 访问的软件(例如网飞、Telegram)，还需要把他们的 IP 端也设置相同的静态路由和通信规则中，比较长这里我就不写了，有需求的可以自行添加。&lt;/p&gt;  &lt;p&gt;到这里网络路由相关内容设置完毕。&lt;/p&gt;  &lt;h3&gt;网关配置&lt;/h3&gt;  &lt;p&gt;网关配置文件在 Docker 机器的   &lt;code&gt;DNS/PaoPaoDNS-Global&lt;/code&gt;目录中的   &lt;code&gt;ppgw.ini&lt;/code&gt;。&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;#paopao-gateway

# mode=socks5|ovpn|yaml|suburl|free
# default: free
# free: 直接出站不走代理
# socks5: 使用 socks5 代理出站
# ovpn: 使用 ovpn 代理出站
# yaml: 使用 yaml 配置文件，yaml 配置文件在同目录下，文件名参考下面的 yamlfile
# suburl: 使用订阅地址，最常见的应该就是这种了
mode=suburl

# Set fakeip&amp;apos;s CIDR here
# default: fake_cidr=7.0.0.0/8
# FakeIP 的网段，如果要修改，请修改成看似是外网的空 IP 网段
fake_cidr=11.0.0.0/8

# Set your trusted DNS here
# default: dns_ip=1.0.0.1
# 这里需要和 Docker 机，也就是 DNS 的 IP 匹配
dns_ip=192.168.1.3
# default: dns_port=53
# If used with PaoPaoDNS, you can set the 5304 port
# 这是网关获取配置的端口，如果没有修改过端口映射，请使用默认值
dns_port=5304

# Clash&amp;apos;s web dashboard
# 这是网页管理面板的端口和密码
clash_web_port=&amp;quot;80&amp;quot;
clash_web_password=&amp;quot;clashpass&amp;quot;

# default：openport=no
# socks+http mixed 1080
openport=no

# default: udp_enable=no
udp_enable=no

# default:30
sleeptime=30

# socks5 mode settting
# default: socks5_ip=gatewayIP
# 这里需要填入网关 IP ，用于开启局域网内的 socks5 代理
socks5_ip=&amp;quot;192.168.1.2&amp;quot;
# default: socks5_port=&amp;quot;7890&amp;quot;
socks5_port=&amp;quot;7890&amp;quot;

# ovpn mode settting
# The ovpn file in the same directory as the ppgw.ini.
# default: ovpnfile=custom.ovpn
ovpnfile=&amp;quot;custom.ovpn&amp;quot;
ovpn_username=&amp;quot;&amp;quot;
ovpn_password=&amp;quot;&amp;quot;

# yaml mode settting
# The yaml file in the same directory as the ppgw.ini.
# default: yamlfile=custom.yaml
# 这里是 yaml 模式时，网关获取的配置文件的文件名
yamlfile=&amp;quot;custom.yaml&amp;quot;

# suburl mode settting
# 这里填入你的订阅地址
suburl=&amp;quot;https://...&amp;quot;
# 这里是订阅自动更新时间
subtime=1d

# fast_node=check/yes/no
# check: 代表会自动检查下面的 URL 能否访问，如果不能访问则自动重启服务并重新拉取订阅
# yes: 代表会自动根据延迟切换到延迟最低的节点，同时具有 check 的功能
# no: 代表不检查延迟与连通性
fast_node=yes
test_node_url=&amp;quot;https://www.youtube.com/generate_204&amp;quot;
ext_node=&amp;quot;Traffic|Expire| GB|Days|Date&amp;quot;
cpudelay=&amp;quot;3000&amp;quot;

# dns burn setting
# depend on fast_node=yes &amp;amp; mode=suburl/yaml
dns_burn=no
# If used with PaoPaoDNS, you can set the PaoPaoDNS:53
# 这里需要设置成 DNS 的 IP
ex_dns=&amp;quot;192.168.1.3:53&amp;quot;

# Network traffic records
net_rec=no
max_rec=5000&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;更详细的参数解释，以及自定义规则等等，请参考   &lt;a href="https://github.com/kkkgo/PaoPaoGateWay#ppgwini%E9%85%8D%E7%BD%AE%E8%AF%B4%E6%98%8E" rel="nofollow"&gt;官方的文档&lt;/a&gt;，如果没有特殊需求，按本文的配置即可。&lt;/p&gt;  &lt;h3&gt;配置设备 DNS&lt;/h3&gt;  &lt;p&gt;我的教程中的方案，是建立在网内只有一部分设备出国，另一部分正常用网的场景，所以需要针对不同的设备，设置不同的上游 DNS 服务器。&lt;/p&gt;  &lt;p&gt;如果你没有这种需求，可以直接在网络配置那一步中，将上游 DNS 设置为   &lt;code&gt;172.30.1.20&lt;/code&gt;，就不用继续往下看了。&lt;/p&gt;  &lt;p&gt;如果你也像我一样，只需要部分的设备走出国规则，那就还需要在多一步设置。&lt;/p&gt;  &lt;p&gt;打开 AdguardHome 的后台   &lt;a href="http://192.168.1.3" rel="nofollow" title="http://192.168.1.3"&gt;http://192.168.1.3&lt;/a&gt;。&lt;/p&gt;  &lt;p&gt;打开客户端设置，添加客户端。&lt;/p&gt;  &lt;p&gt;添加需要出国的设备的 IP 或者 IP 段，然后在下面的自定义上游的地方，将上游 DNS 设置为   &lt;code&gt;172.30.1.20&lt;/code&gt;，然后保存即可。&lt;/p&gt;  &lt;p&gt;   &lt;a href="https://yojigen.tech/wp-content/uploads/2024091719181210_2f4c713455403fc7.webp" rel="nofollow"&gt;    &lt;img alt="" src="https://yojigen.tech/wp-content/uploads/2024091719181210_2f4c713455403fc7.webp"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;至此系统全部搭建完毕。&lt;/p&gt;  &lt;h2&gt;测试&lt;/h2&gt;  &lt;p&gt;可以使用 Speedtest ，分别选择国内和国外的测速节点，看一下带宽能否跑满。&lt;/p&gt;  &lt;p&gt;可以在   &lt;a href="https://ip111.cn" rel="nofollow" title="https://ip111.cn"&gt;https://ip111.cn&lt;/a&gt;或者   &lt;a href="https://ip.skk.moe" rel="nofollow" title="https://ip.skk.moe"&gt;https://ip.skk.moe&lt;/a&gt;检查一下 IP 分流是否正常。&lt;/p&gt;  &lt;p&gt;   &lt;a href="https://yojigen.tech/wp-content/uploads/2024091719210259_aac670543d7d5533.webp" rel="nofollow"&gt;    &lt;img alt="" src="https://yojigen.tech/wp-content/uploads/2024091719210259_aac670543d7d5533.webp"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;   &lt;a href="https://yojigen.tech/wp-content/uploads/2024091719210965_2772b3be94a07bb8.webp" rel="nofollow"&gt;    &lt;img alt="" src="https://yojigen.tech/wp-content/uploads/2024091719210965_2772b3be94a07bb8.webp"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;可以用 NatTypeTester 检查一下 Nat 等级。&lt;/p&gt;  &lt;p&gt;   &lt;a href="https://yojigen.tech/wp-content/uploads/2024091719214622_af3abad9fd9dabd4.webp" rel="nofollow"&gt;    &lt;img alt="" src="https://yojigen.tech/wp-content/uploads/2024091719214622_af3abad9fd9dabd4.webp"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;  &lt;h2&gt;总结&lt;/h2&gt;  &lt;p&gt;整套设备花费是 59+28+50=137 元，这里是没有计算 U 盘和交换机的价格。&lt;/p&gt;  &lt;p&gt;U 盘作为一个搞机佬，我相信各位家里一定会有的，而交换机要看自己网内具体有多少需要网线的设备，所以我就没有算在内。&lt;/p&gt;  &lt;p&gt;这里面路由器的价格可能会有一些上下波动，比如你需要一款可以跑满千兆的路由器，又不想刷机，所以可能买了我推荐的爱快 Q3000 ，那价格就一下多了 100 元了，但是你买了更好的路由器就算不玩这套系统，那也可以让网络体验变好，也是不亏。而如果你家的网络没有达到千兆，比如只有 500M 甚至 100M ，那你大可买一些 mt7621 的路由器，刷个 OP 或者爱快的固件，这种路由器咸鱼 50 以内可以随便买。&lt;/p&gt;  &lt;p&gt;希望这篇文章可以帮助到一直想要一套舒适的网络的你。&lt;/p&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>geek</category>
      <guid isPermaLink="true">https://itindex.net/detail/62939-%E4%BD%93%E9%AA%8C-%E5%AE%B6%E5%BA%AD-%E7%BD%91%E7%BB%9C</guid>
      <pubDate>Wed, 18 Sep 2024 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>劳动仲裁成功！获赔 2N。</title>
      <link>https://itindex.net/detail/62324-%E5%8A%B3%E5%8A%A8-%E4%BB%B2%E8%A3%81-%E6%88%90%E5%8A%9F</link>
      <description>&lt;div&gt;  &lt;p&gt;本来不准备在 V 站分享这种内容，但由于之前 360+天之前在这里提过问。&lt;/p&gt;  &lt;p&gt;所以，为了 [有始有终] ，交代一下后续吧。&lt;/p&gt;  &lt;p&gt;360 多天之前，我发了这篇帖子：   &lt;a href="https://v2ex.com/t/788989" rel="nofollow"&gt;https://v2ex.com/t/788989&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;如今这个事情有了结果，时间线如下：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;2021 年 2 月初收到解雇通知&lt;/li&gt;   &lt;li&gt;2021-03-02 开始劳动仲裁&lt;/li&gt;   &lt;li&gt;2022-07-02 收到终审判决（终审即二审，此前还有一审）&lt;/li&gt;   &lt;li&gt;2022-07-08 收到赔偿钱款（全程总耗时约 1 年 5 个月）&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;[最终获赔]
1.违法裁员赔偿金 2N 。
2.未休完的年假，折成 2 倍工资。&lt;/p&gt;  &lt;p&gt;这里贴上一份《劳动人事争议仲裁办事指南.zip 》希望诸位永远用不上。&lt;/p&gt;  &lt;p&gt;链接:   &lt;a href="https://pan.baidu.com/s/16t8kjN4IJTkLSSRQslEWDw" rel="nofollow"&gt;https://pan.baidu.com/s/16t8kjN4IJTkLSSRQslEWDw&lt;/a&gt;提取码: abeq&lt;/p&gt;  &lt;p&gt;关于劳动仲裁过程中的经历，我写了 10 片“水文”《劳动仲裁回忆录》，放在个人号里面了。
（由于我无意广告，且相关纪录文章就是相关节点事情的回顾和纪录，也没啥含金量，这里就不贴了，真的有人想看故事的话，我再贴。）&lt;/p&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>geek</category>
      <guid isPermaLink="true">https://itindex.net/detail/62324-%E5%8A%B3%E5%8A%A8-%E4%BB%B2%E8%A3%81-%E6%88%90%E5%8A%9F</guid>
      <pubDate>Sun, 10 Jul 2022 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>华语音乐的最后希望在宝岛，宝岛音乐的希望在年轻的乐团，分享一下我心中那些值得一听的新时代台湾乐团</title>
      <link>https://itindex.net/detail/61967-%E5%8D%8E%E8%AF%AD-%E9%9F%B3%E4%B9%90-%E6%9C%80%E5%90%8E%E5%B8%8C%E6%9C%9B</link>
      <description>&lt;div&gt;  &lt;h2&gt;前言&lt;/h2&gt;  &lt;ol&gt;   &lt;li&gt;我会避免在标题和正文中使用“小众“、”宝藏乐队“等油腻或者已经开始变得油腻的词语。&lt;/li&gt;   &lt;li&gt;本文不会包含落日飞车、告五人、茄子蛋等耳熟能详的乐队。&lt;/li&gt;   &lt;li&gt;虽然按专辑分享会显得更懂音乐，但我还是会按单曲来分享。&lt;/li&gt;   &lt;li&gt;歌曲评级仅代表我个人的喜欢程度。&lt;/li&gt;   &lt;li&gt;部分歌曲名在 spotify 上会有不同，比如《挖掘机》在 spotify 上叫《怪手》。&lt;/li&gt;&lt;/ol&gt;  &lt;h2&gt;现在让我们开始分享吧&lt;/h2&gt;  &lt;h3&gt;1 、美秀集团&lt;/h3&gt;  &lt;ul&gt;   &lt;li&gt;S 级 ——《米儿》《生活袂晓过》&lt;/li&gt;   &lt;li&gt;A 级 —— 《细粒的目睭》《小老婆》《挡一根》《卷烟》《做事人》《马克吐温》&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;第一次听他们的歌曲可能会觉得这是城乡结合部的杀马特土嗨乐队，而且主唱的发型、长相和嗓音都有点像面筋哥。但是只要你接受了他们的风格，就会发现他们真的很牛逼（面筋哥其实也很牛逼）。&lt;/p&gt;  &lt;h3&gt;2 、浅堤&lt;/h3&gt;  &lt;ul&gt;   &lt;li&gt;S 级 ——《挖掘机》《多崎作》《高雄》&lt;/li&gt;   &lt;li&gt;A 级 ——《树影》《月光》《青春咱的梦》《叨位是你的厝》&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;一个轻快、清新的乐队，刚发了新专辑。听他们的快歌会让你觉得来到了阳光明媚的周六上午。但是轻快的歌曲可能在讲一个沉重的故事。女主唱又写词又作曲又唱歌，声音也很好听。我很喜欢他们的吉他和鼓点。&lt;/p&gt;  &lt;h3&gt;3 、怕胖团&lt;/h3&gt;  &lt;ul&gt;   &lt;li&gt;S 级 —— 《我没有用，没办法给你想要的生活》《后照镜》《想想当初吧》&lt;/li&gt;   &lt;li&gt;A 级 —— 《鱼》《月旁月光》《抱歉我不想跟你道歉》&lt;/li&gt;   &lt;li&gt;搞怪级 —— 《蚊子妳来咬我啊！我才不怕妳？！！！》《内外夹攻大力丸》&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;一个台味很浓的台湾乐队（诶，你很机车诶！），爱情 hàn 友情是他们的主要创作主题，三个很明显的元素：很纯粹的悲伤、带了点恶搞的悲伤、带了很多恶搞的快乐。但是有一点我不喜欢，就是他们很执着于《抄你妈的笔记本》这个很不雅的谐音梗（甚至还专门出了一个 2021 版）。查了一下居然是 2007 年就创立的乐队了，虽然已经不年轻了但是我还是想加进来。&lt;/p&gt;  &lt;h3&gt;4 、热写生&lt;/h3&gt;  &lt;ul&gt;   &lt;li&gt;S 级 —— 《爱与胶囊》&lt;/li&gt;   &lt;li&gt;A 级 —— 《在船底搔痒》《青元春朗》&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;说实话他们的歌我听不太懂，歌词看上去很有文化很哲学，歌曲听上去给人的感觉是独一无二的——目前还没有听过风格类似的乐队，即使是故弄玄虚也值得一听。&lt;/p&gt;  &lt;h3&gt;5 、青虫 aoi&lt;/h3&gt;  &lt;ul&gt;   &lt;li&gt;S 级 —— 《我毋是刁故意》《青鸟》《大树公》&lt;/li&gt;   &lt;li&gt;A 级 —— 《分手后你才送我生日蛋糕》《简幼》《媠花》&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;这支乐队大多数歌曲都是闽南语，女主唱的声音很温暖，歌词写得也很棒。擅于讲述亲情，发掘生活中有意义的事物。&lt;/p&gt;  &lt;h3&gt;6 、当代电影大师&lt;/h3&gt;  &lt;ul&gt;   &lt;li&gt;S 级 —— 《那些事情是真的有意思吗》&lt;/li&gt;   &lt;li&gt;A 级 —— 《我觉得春天好像要来了》&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;很装逼的一支乐队，刚发了第一张正式专辑(有 15 首歌)，我觉得最惊艳的还是《那些事情是真的有意思吗》的前半段，词、节奏和鼓点都太强了。&lt;/p&gt;  &lt;h3&gt;7 、Crispy 脆乐团&lt;/h3&gt;  &lt;ul&gt;   &lt;li&gt;S 级 —— 《转圈圈》&lt;/li&gt;   &lt;li&gt;A 级 —— 《 Deja Vu 》&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;他们应该还有其他好听的歌，但是因为我听得不多，难做选择。顺带一提，转圈圈的 MV 拍得很有意思，我是在 youtube 看到那个 MV 才关注他们的。&lt;/p&gt;  &lt;h3&gt;8 、好乐团 /Goodband&lt;/h3&gt;  &lt;ul&gt;   &lt;li&gt;S 级 —— 《我们一样可惜》《我把我的青春给你》&lt;/li&gt;   &lt;li&gt;A 级 —— 《他们说我是没有用的年轻人》《蒸发》&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;一个很丧的乐队，女主唱的假音相当牛逼，他们的歌很悲伤但是很好听，深夜 emo 必备。&lt;/p&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>geek</category>
      <guid isPermaLink="true">https://itindex.net/detail/61967-%E5%8D%8E%E8%AF%AD-%E9%9F%B3%E4%B9%90-%E6%9C%80%E5%90%8E%E5%B8%8C%E6%9C%9B</guid>
      <pubDate>Mon, 20 Dec 2021 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>树莓派不讲武德,自研双核 MCU Pico,STM32 哭晕在厕所!</title>
      <link>https://itindex.net/detail/61203-%E6%A0%91%E8%8E%93%E6%B4%BE-%E6%AD%A6%E5%BE%B7-%E5%8F%8C%E6%A0%B8</link>
      <description>&lt;div&gt;  &lt;h1&gt;树莓派不讲武德,自研双核 MCU Pico,STM32 哭晕在厕所!&lt;/h1&gt;  &lt;p&gt;重磅,树莓派再出 Pico 自研双核 MCU,国产能否跟上?&lt;/p&gt;  &lt;p&gt;树莓派 Raspberry Pi 近日发布自研的 40nm 双核 MCU,自带全新可编程 PIO 架构,能否开创 MCU 市场全新领域,STM,兆易创新,STC,全志,乐鑫等一票国产 MCU 能否跟上?&lt;/p&gt;  &lt;p&gt;   &lt;img alt="&amp;#26641;&amp;#33683;&amp;#27966;&amp;#19981;&amp;#35762;&amp;#27494;&amp;#24503; MCU &amp;#21457;&amp;#24067; PICO" src="https://gitee.com/pdusb/pdboltblog/raw/master/pdnas/pico-release-cover.jpg"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;h2&gt;树莓派发布双核基于 M0 的 MCU&lt;/h2&gt;  &lt;p&gt;近日,   &lt;a href="https://www.raspberrypi.org/documentation/pico/getting-started/" rel="nofollow"&gt;树莓派&lt;/a&gt;发布了自研的 40nm 双核 MCU,嵌入式 MCU 市场又要迎来真正的新气象.&lt;/p&gt;  &lt;p&gt;   &lt;img alt="&amp;#26641;&amp;#33683;&amp;#27966; PICO" src="https://gitee.com/pdusb/pdboltblog/raw/master/pdnas/rpi-pico-logo.jpg"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;一如之前发布的各种 Linux 开发板,树莓派的 Pico 同样是开发板样式.&lt;/p&gt;  &lt;p&gt;   &lt;img alt="&amp;#26641;&amp;#33683;&amp;#27966; PICO &amp;#30340;&amp;#31649;&amp;#33050;" src="https://gitee.com/pdusb/pdboltblog/raw/master/pdnas/rpi-pico-pinout.jpg"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;从数据手册可以清晰的看到,其和常见 MCU 相同的各类接口俱全,SPI/USB/I2C/ADC/PWM/SWD 等各种接口齐全.&lt;/p&gt;  &lt;p&gt;其典型特点有如下几个:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;双核 130MHz 的 M0&lt;/li&gt;   &lt;li&gt;无内置 Flash,需接外部 QSPI 的 Flash&lt;/li&gt;   &lt;li&gt;264K 大内存&lt;/li&gt;   &lt;li&gt;可编程 PIO&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;将其和当前典型 STM32F1 系列的 MCU 做个对比,可以看到其和当前主流 MCU 的明显不同.&lt;/p&gt;  &lt;p&gt;   &lt;img alt="PICO &amp;#23545;&amp;#27604; STM32" src="https://gitee.com/pdusb/pdboltblog/raw/master/pdnas/pico-stm32.jpg"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;对于 MCU 来说,264K Ram 比较充裕,需要外接 QSPI 的 Flash 会增加成本以及系统复杂度.&lt;/p&gt;  &lt;p&gt;从其开发板的设计来看,同样需要外置晶振以及匹配电路,同意的外围也没有极简,比较起来比起 STM 系列要简单的多.&lt;/p&gt;  &lt;p&gt;   &lt;img alt="&amp;#26641;&amp;#33683;&amp;#27966; PICO" src="https://gitee.com/pdusb/pdboltblog/raw/master/pdnas/rpi-pico-image.jpg"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;h2&gt;Python 和 C 双栈 SDK,要通杀的态势&lt;/h2&gt;  &lt;p&gt;树莓派官网提供了翔实的资料.&lt;/p&gt;  &lt;p&gt;   &lt;img alt="&amp;#26641;&amp;#33683;&amp;#27966; PICO &amp;#36164;&amp;#26009;" src="https://gitee.com/pdusb/pdboltblog/raw/master/pdnas/rpi-pico-full-docs.jpg"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;其 SDK 既包括了常见的 C 版本,也包括了大受欢迎的 Python 版本,这是要上下通吃的节奏.&lt;/p&gt;  &lt;p&gt;   &lt;img alt="&amp;#26641;&amp;#33683;&amp;#27966; PICO SDK" src="https://gitee.com/pdusb/pdboltblog/raw/master/pdnas/rpi-pico-sdk.jpg"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;h2&gt;重磅 PIO,MCU 市场功能大洗牌&lt;/h2&gt;  &lt;p&gt;MCU 往往具备丰富的外设接口,这些接口往往设计成固定的符合相关标准或者协议的形式,如 SPI/I2C 等.
而 PIO 是完全可编程的接口,当前 MCU 的 IO 往往也具备一些可自定义的设计,但是并没有特别聚集在这个功能上面.
PIO 本身硬件设计了对应的 FIFO 以及可以使用对应的汇编语言操作收发以及状态机,IO 外部连接合适的 RF 电路,甚至可以
实现简单的通信功能.&lt;/p&gt;  &lt;p&gt;   &lt;img alt="&amp;#26641;&amp;#33683;&amp;#27966; PICO &amp;#30340; PIO &amp;#26694;&amp;#26550;" src="https://gitee.com/pdusb/pdboltblog/raw/master/pdnas/rpi-pio.jpg"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;从 PIO 的框架可以清晰的看到其是一个典型的可编程逻辑.&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;每个通路有独立的 FIFO&lt;/li&gt;   &lt;li&gt;编程器有状态和指令集&lt;/li&gt;   &lt;li&gt;有中断和 MCU 通信&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;其编程器内部设计来看,是典型的 PSM 设计.&lt;/p&gt;  &lt;p&gt;   &lt;img alt="&amp;#26641;&amp;#33683;&amp;#27966; PICO &amp;#30340;&amp;#27719;&amp;#32534;&amp;#36923;&amp;#36753;" src="https://gitee.com/pdusb/pdboltblog/raw/master/pdnas/rpi-pio-programm.jpg"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;要是做 PIO 的编程的话,需要从汇编以及其提供的指令开始做起,典型的样式如下:&lt;/p&gt;  &lt;p&gt;   &lt;img alt="&amp;#26641;&amp;#33683;&amp;#27966; PICO &amp;#30340;&amp;#27719;&amp;#32534;&amp;#35821;&amp;#35328;" src="https://gitee.com/pdusb/pdboltblog/raw/master/pdnas/rpi-pio-sample.jpg"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;h2&gt;PIO 是首创?具有无可替换性?&lt;/h2&gt;  &lt;p&gt;双核 MCU 让我们不禁想起双核单片机.让我们一起来看看这个神器.&lt;/p&gt;  &lt;p&gt;   &lt;img alt="&amp;#24212;&amp;#24191;&amp;#21452;&amp;#26680;&amp;#21333;&amp;#29255;&amp;#26426;" src="https://gitee.com/pdusb/pdboltblog/raw/master/pdnas/pfc-232-logo.jpg"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;这颗单片机也是默认双核,并称之为 FPPA.&lt;/p&gt;  &lt;p&gt;从其资料中可以看出,其工作模式和 PIO 极其相似&lt;/p&gt;  &lt;p&gt;   &lt;img alt="&amp;#24212;&amp;#24191;&amp;#21452;&amp;#26680; fppa" src="https://gitee.com/pdusb/pdboltblog/raw/master/pdnas/pfc-fppa.jpg"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;其编程语言同样是基于汇编的,典型的样式如下:&lt;/p&gt;  &lt;p&gt;   &lt;img alt="&amp;#24212;&amp;#24191;&amp;#21333;&amp;#29255;&amp;#26426;&amp;#32534;&amp;#31243;" src="https://gitee.com/pdusb/pdboltblog/raw/master/pdnas/pfc-lan.jpg"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;h2&gt;树莓派 Pico 能血洗 MCU 市场吗?&lt;/h2&gt;  &lt;p&gt;从树莓派的规格和价格来看,其价格要低于 STM32,其规格要高于部分 STM32 对应款型.&lt;/p&gt;  &lt;p&gt;虽然是 40nm 制程,其并不是超低功耗的设计,因此电池类的应用估计很难适配.&lt;/p&gt;  &lt;p&gt;其 PIO 的聚焦设计,可以预见的会产生千奇百怪的各种应用.&lt;/p&gt;  &lt;p&gt;   &lt;img alt="&amp;#26641;&amp;#33683;&amp;#27966; PICO &amp;#30340; PIO &amp;#24212;&amp;#29992;" src="https://gitee.com/pdusb/pdboltblog/raw/master/pdnas/pico-pio-examples.jpg"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;树莓派不仅仅能抢占部分 STM32 的市场,还能挖点 FPGA 的墙角,尤其是后者的入门级型号.&lt;/p&gt;  &lt;h2&gt;树莓派 Pico 资源&lt;/h2&gt;  &lt;p&gt;树莓派 Pico 的开发环境是基于树莓派 3B/4B 来设计的,国内已经有爱好者将其适配到了 Ubuntu 等 Linux 系统上.
可以直接   &lt;a href="https://gitee.com/pdusb/pdusb-fast-pico.git" rel="nofollow"&gt;Gitee&lt;/a&gt;获取哦.&lt;/p&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>geek</category>
      <guid isPermaLink="true">https://itindex.net/detail/61203-%E6%A0%91%E8%8E%93%E6%B4%BE-%E6%AD%A6%E5%BE%B7-%E5%8F%8C%E6%A0%B8</guid>
      <pubDate>Sun, 31 Jan 2021 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>都来分享一下自己的效率工具吧，互相借鉴，我先来</title>
      <link>https://itindex.net/detail/60176-%E5%88%86%E4%BA%AB-%E5%B7%A5%E5%85%B7</link>
      <description>&lt;div&gt;上面说的基本都用过, 说几个 Windows 上小众的吧, 以前回过太多月经贴  &lt;br /&gt;  &lt;br /&gt;mouse manager, 帮我把鼠标侧键改成其它热键, 内存比鼠标驱动自带的小很多  &lt;br /&gt;  &lt;br /&gt;Strokeit, 用了十几年, 依然兼容的全局鼠标手势, 不是 WGestures 和 StrokePlus 不好, 是它太优秀, linux 上的差远了  &lt;br /&gt;  &lt;br /&gt;Wox, 大名鼎鼎不用介绍, 用了才三四年吧, 内存还是挺大的, 不是说 uTools 不够好, 而是 Wox 目前够我用了, 毕竟当年 everything + altrun 的七八年也用的好好的  &lt;br /&gt;  &lt;br /&gt;Claunch, 用了 2 年左右的快捷方式 launcher panel, 用了十几年的酷鱼快速启动, 结果去年 Windows10 一次小更新居然突然给我 C++ 丢指针了, 团队跑路了没法 fix, 换了市面上几乎所有 roland 什么的替代品, 比 Claunch 好的真没有  &lt;br /&gt;  &lt;br /&gt;cmder, 终端, 简而言之, 不是它多好使, 实在没什么对手... tmux redraw 的问题在底层没法修, 凑合用它或者临时 git-bash  &lt;br /&gt;  &lt;br /&gt;autohotkey, 用了十几年的老东西, 因为有以图找位置的功能, 所以可以把我 90% 的手动操作都自动完成了, 比如某些 app 签到刷分配合雷电模拟器(逆向是违法的, 所以怎么拟人怎么来)  &lt;br /&gt;  &lt;br /&gt;chrome 的 tampermonkey, 不用多说, 会的自然懂, 不懂的也不用会  &lt;br /&gt;  &lt;br /&gt;geek uninstaller, 这玩意卸载软件比某些软件自带的卸载工具都干净... 注册表相关都给找出来  &lt;br /&gt;  &lt;br /&gt;wiztree, 早年用的 spacesniffer 的替代品, 可视化查看磁盘使用情况, 清理某些大片时候用的, 不清理都不知道有的电影要十几 GB........  &lt;br /&gt;  &lt;br /&gt;anydesk, 下载安装使用都只单文件的远程协助工具, 不如 teamviewer 快, 不如 QQ 的稳定, 但是, 没招啊, 谁让它简单  &lt;br /&gt;  &lt;br /&gt;dism++, Windows 官方优化工具 dism 的 GUI 版  &lt;br /&gt;  &lt;br /&gt;===========================  &lt;br /&gt;  &lt;br /&gt;手打有点累, 错字请包涵  &lt;br /&gt;  &lt;br /&gt;2019 年 11 月 26 日 10:32:30&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>geek</category>
      <guid isPermaLink="true">https://itindex.net/detail/60176-%E5%88%86%E4%BA%AB-%E5%B7%A5%E5%85%B7</guid>
      <pubDate>Tue, 26 Nov 2019 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>区块链可以理解为是一种加密技术吗？</title>
      <link>https://itindex.net/detail/60119-%E5%8C%BA%E5%9D%97%E9%93%BE-%E7%90%86%E8%A7%A3-%E5%8A%A0%E5%AF%86%E6%8A%80%E6%9C%AF</link>
      <description>&lt;div&gt;首先区块链不是加密技术，区块链准确地说是一种链式、多节点分布式、P2P 的日志系统，区块链可以是弱中心化的（ EOS，有超级节点的概念，一共 21 个主超级节点，其余的做冗余，想成为超级节点需要机器配置达标且通过投票）也可以是去中心化的（ BTC、ETH 之类的，每个人都可以做节点）。  &lt;br /&gt;身份认证说白了只是以前的公私钥概念，加密只是说你可以把信息加密后再上链（记录到日志系统里）。  &lt;br /&gt;  &lt;br /&gt;至于“凡是涉及到“区块链”三个字的项目都被塑造成一种“高科技”、“很可靠”的感觉”这个感觉，只是因为商业公司都这么吹而已，实际玩过你就会发现很多东西都是虚的。做链的公司很多都只是拿开源的改一改，加点自己的私货进去就开始吹自己有多牛逼了，实际上东西还是那套东西。  &lt;br /&gt;  &lt;br /&gt;区块链这东西实际上对资源的浪费非常大，因为不管是弱中心化还是去中心化，每个节点都需要有一份完整备份，否则最基本的校验都无法进行。  &lt;br /&gt;  &lt;br /&gt;而一个性能高的链（比如你聊天，发个消息对方等个 0.5 秒就能收到）需要非常高的报块频率，节点间一方面需要保持稳定的网络传输，一方面需要承受大量的垃圾、重复信息（每个块的头部都会有很多重复数据）的存储压力，并且如果某一段时间内没有人进行操作，那么那段时间还会一直是存的空块（因为没有实际数据，只剩下头部了），节点们的存储空间都被浪费在垃圾上了。  &lt;br /&gt;  &lt;br /&gt;久而久之，“链”里面的数据会越来越多，对节点的要求也会越来越高，目前没有一家做链的能避开这个问题。所以大多数人不会直接把东西往链上放，而是只放个 hash 值，但是实际上这样也就不存在什么永久保存、不可篡改的特性了。  &lt;br /&gt;  &lt;br /&gt;所以说不要看别人怎么吹，区块链这东西现在乱得一批，很多技术层面的问题根本没法解决，更别说实用了...实际广泛运用的话还是需要中心化或者弱中心化的，跟不用区块链单纯用数据库多机冗余其实没啥区别，只是让人感觉更“可信”了一点而已。&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>geek</category>
      <guid isPermaLink="true">https://itindex.net/detail/60119-%E5%8C%BA%E5%9D%97%E9%93%BE-%E7%90%86%E8%A7%A3-%E5%8A%A0%E5%AF%86%E6%8A%80%E6%9C%AF</guid>
      <pubDate>Mon, 28 Oct 2019 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>数学对程序员来说真的很重要吗？</title>
      <link>https://itindex.net/detail/59911-%E6%95%B0%E5%AD%A6-%E7%A8%8B%E5%BA%8F%E5%91%98-%E8%AF%B4%E7%9C%9F%E7%9A%84</link>
      <description>&lt;div&gt;在本站搜索一下，就有很多结果的哦：  &lt;br /&gt;  &lt;br /&gt;《冒号课堂》----答读者问(1)——对程序员的一些个人建议  &lt;br /&gt;  &lt;br /&gt;  &lt;a href="http://blog.zhenghui.org/2010/06/03/advice-on-programmer/" rel="nofollow" target="_blank"&gt;http://blog.zhenghui.org/2010/06/03/advice-on-programmer/&lt;/a&gt;  &lt;br /&gt;  &lt;br /&gt;  &lt;br /&gt;关于数学基础，窃以为并非什么太大的问题。几乎每个得知我数学背景的人都会对我说：哦，学数学的人来学计算机自然容易啦。事实上，这种观点虽然极为普遍，但也极为肤浅。本人从事数学 14 年（从本科算起）、从事计算机 12 年（与前者有部分重合），在这一点上还是比较有发言权的。事先说明，以下提到的数学不包括高中数学。其实大多数从事软件开发的人员用不到太多的数学知识，他们只需要正常的逻辑思维能力和抽象思维能力。整天拿数学说事，要么是无知，要么是找借口，要么是装高深。当然，我不否认一些高级算法、计算机理论以及人工智能等领域可能涉及到高深的数学知识（其实也只是图论、组合数学、数论、概率论、计算几何、抽象代数、数学逻辑等中的一小部分），但那毕竟只是少数。我也不否认自己的数学背景有助于对编程的理解，但投入产出比太低，不值得作为经验来推广。不过若想成为一位计算机科学家，那就另作别论了——这时数学懂得再多也会嫌少的。  &lt;br /&gt;  &lt;br /&gt;  &lt;br /&gt;倒是英语我希望你更重视些。我在《冒号课堂》中专门提过阅读原著的必要性，而且你也意识到译著的质量问题。建议不必特地去学习英语（你本来就会了，不是吗？），只要坚持读经典原著即可。其实，计算机方面的英文算是很容易的了，关键是克服自己的惯性和惰性。开始可能不习惯，看多了就习惯了。在此提醒一点，在阅读时请有意识地培养自己对英语的语感，就像编程时要有意识地培养自己对编程语言的语感一样。  &lt;br /&gt;  &lt;br /&gt;  &lt;br /&gt;总之，对于程序员来说，数学没有人们认为的那么重要，英语没有人们认为的那么不重要。  &lt;br /&gt;  &lt;br /&gt;  &lt;br /&gt;再说说专业方面的问题。你提到愿意重新自学大学课程，虽精神可嘉，但未必可取。从软件（或建筑）设计的观点来看，这是 bottom-up 法。作为学生，最好采用这种方法，但你已经参加工作了，所以我建议你更多地采用 top-down 法。这当然不是轻视基础知识，而是认为获取知识最高效的方法莫过于按需（ on demand ）学习。在实际工作中意识到某个知识点的重要性，从而有针对性地弥补短板，这样学习起来不仅更有效率，也更有兴味。需要强调的是，绝不能只是 “头痛医头”，而要“拔萝卜带出泥”。只有寻根究底、以点带面，才能快速有效地建立起自己的知识结构体系。对于软件开发这类实践性很强的专业来说，该法尤其奏效。&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>geek</category>
      <guid isPermaLink="true">https://itindex.net/detail/59911-%E6%95%B0%E5%AD%A6-%E7%A8%8B%E5%BA%8F%E5%91%98-%E8%AF%B4%E7%9C%9F%E7%9A%84</guid>
      <pubDate>Mon, 05 Aug 2019 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>避免面试尴尬，你需要了解的20个架构师相关缩写</title>
      <link>https://itindex.net/detail/59869-%E9%9D%A2%E8%AF%95-%E5%B0%B4%E5%B0%AC-%E9%9C%80%E8%A6%81</link>
      <description>&lt;div&gt;  &lt;p&gt;   &lt;img alt="&amp;#22312;&amp;#36825;&amp;#37324;&amp;#25554;&amp;#20837;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://img-blog.csdnimg.cn/20190717095800476.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2FuZHJvaWRva2s=,size_16,color_FFFFFF,t_70"&gt;&lt;/img&gt;   &lt;br /&gt;作为一个架构师，如果在面试的时候，面试官说出了一个英文缩写，这个时候如果你没有听过，是不是很尴尬?而且你也没办法针对这个问题进行描述回答!所以，多学习一些基础的英文缩写，一是面试可以游刃有余，二是可以装逼!下面的20个缩写，你都能讲清楚吗?&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;IOC：&lt;/strong&gt;   &lt;br /&gt;控制反转(   &lt;em&gt;Inversion of Control&lt;/em&gt;，缩写为IoC)，是面向对象编程中的一种设计原则，可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection，简称DI)，还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转，对象在被创建的时候，由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说，依赖被注入到对象中。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;AOP&lt;/strong&gt;：   &lt;br /&gt;在软件业，AOP为   &lt;em&gt;Aspect Oriented Programming&lt;/em&gt;的缩写，意为：面向切面编程，通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续，是软件开发中的一个热点，也是Spring框架中的一个重要内容，是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离，从而使得业务逻辑各部分之间的耦合度降低，提高程序的可重用性，同时提高了开发的效率。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;CAS：&lt;/strong&gt;   &lt;br /&gt;是一种乐观锁机制。CAS是英文单词   &lt;em&gt;Compare And Swap&lt;/em&gt;的缩写，翻译过来就是比较并替换。CAS机制当中使用了3个基本操作数：内存地址V，旧的预期值A，要修改的新值B。更新一个变量的时候，只有当变量的预期值A和内存地址V当中的实际值相同时，才会将内存地址V对应的值修改为B。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;CAP：&lt;/strong&gt;   &lt;br /&gt;CAP原则又称CAP定理，指的是在一个分布式系统中，一致性(   &lt;em&gt;Consistency)&lt;/em&gt;、可用性(   &lt;em&gt;Availability)&lt;/em&gt;、分区容忍性(Partition tolerance)。CAP 原则指的是，这三个要素最多只能同时实现两点，不可能三者兼顾。&lt;/p&gt;  &lt;p&gt;一致性©：在分布式系统中的所有数据备份，在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本)&lt;/p&gt;  &lt;p&gt;可用性(A)：在集群中一部分节点故障后，集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性)&lt;/p&gt;  &lt;p&gt;分区容忍性§：以实际效果而言，分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性，就意味着发生了分区的情况，必须就当前操作在C和A之间做出选择。&lt;/p&gt;  &lt;p&gt;AICD：   &lt;br /&gt;事务属性AICD：&lt;/p&gt;  &lt;p&gt;原子性(Atomicity)：整体不可分割性，要么全做要不全不做。&lt;/p&gt;  &lt;p&gt;一致性(   &lt;em&gt;Consistency&lt;/em&gt;) ：事务执行前、后数据库状态均一致。&lt;/p&gt;  &lt;p&gt;隔离性(   &lt;em&gt;Isolation)&lt;/em&gt;：在事务未提交前，它操作的数据，对其它用户不可见(从其他session读取的数据还是commit之前的数据状态)。&lt;/p&gt;  &lt;p&gt;持久性*(Durability*)：一旦事务成功，将进行永久的变更，记录与redo日志。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;RC&lt;/strong&gt;：   &lt;br /&gt;数据库隔离级别，   &lt;em&gt;Read Committed&lt;/em&gt;(读取提交内容)。&lt;/p&gt;  &lt;p&gt;(1) 这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)&lt;/p&gt;  &lt;p&gt;(2)它满足了隔离的简单定义：一个事务只能看见已经提交事务所做的改变&lt;/p&gt;  &lt;p&gt;(3)这种隔离级别出现的问题是——不可重复读(Nonrepeatable Read)：不可重复读意味着我们在同一个事务中执行完全相同的select语句时可能看到不一样的结果。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;RR：&lt;/strong&gt;   &lt;br /&gt;数据库隔离级别，   &lt;em&gt;Repeatable Read&lt;/em&gt;(可重读)。&lt;/p&gt;  &lt;p&gt;(1)这是MySQL的默认事务隔离级别。&lt;/p&gt;  &lt;p&gt;(2)它确保同一事务的多个实例在并发读取数据时，会看到同样的数据行。&lt;/p&gt;  &lt;p&gt;(3)此级别可能出现的问题——幻读(   &lt;em&gt;Phantom Read&lt;/em&gt;)：当用户读取某一范围的数据行时，另一个事务又在该范围内插入了新行，当用户再读取该范围的数据行时，会发现有新的“幻影” 行。&lt;/p&gt;  &lt;p&gt;(4)InnoDB和   &lt;em&gt;Falcon&lt;/em&gt;存储引擎通过多版本并发控制(MVCC，   &lt;em&gt;Multiversion Concurrency Control&lt;/em&gt;)机制解决了该问题。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;Binlog：&lt;/strong&gt;   &lt;br /&gt;是The Binary Log的简称，意思就是二进制的日志文件。binlog是一个二进制格式的文件，用于记录用户对数据库更新的SQL语句信息，例如更改数据库表和更改内容的SQL语句都会记录到binlog里，但是对库表等内容的查询不会记录。默认情况下，binlog日志是二进制格式的，不能使用查看文本工具的命令(比如，cat，vi等)查看，而使用mysqlbinlog解析查看。当有数据写入到数据库时，还会同时把更新的SQL语句写入到对应的binlog文件里，这个文件就是上文说的binlog文件。使用mysqldump备份时，只是对一段时间的数据进行全备，但是如果备份后突然发现数据库服务器故障，这个时候就要用到binlog的日志了。主要作用是用于数据库的主从复制及数据的增量恢复。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;MVCC：&lt;/strong&gt;   &lt;br /&gt;   &lt;em&gt;Multi-Version Concurrency Control&lt;/em&gt;多版本并发控制，MVCC 是一种并发控制的方法，一般在数据库管理系统中，实现对数据库的并发访问;在编程语言中实现事务内存。MVCC是通过保存数据在某个时间点的快照来实现的. 不同存储引擎的MVCC. 不同存储引擎的MVCC实现是不同的,典型的有乐观并发控制和悲观并发控制。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;RESP：&lt;/strong&gt;   &lt;br /&gt;*Redis Serialization Protocol，*RESP 是 Redis 序列化协议的简写。它是一种直观的文本协议，优势在于实现异常简 单，解析性能极好。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;COW：&lt;/strong&gt;   &lt;br /&gt;写入时复制(英语：Copy-on-write，简称COW)是一种计算机程序设计领域的优化策略。其核心思想是，如果有多个调用者(callers)同时请求相同资源(如内存或磁盘上的数据存储)，他们会共同获取相同的指针指向相同的资源，直到某个调用者试图修改资源的内容时，系统才会真正复制一份专用副本(private copy)给该调用者，而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是透明的(transparently)。 优点是如果调用者没有修改该资源，就不会有副本(private copy)被建立，因此多个调用者只是读取操作时可以共享同一份资源。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;AMQP：&lt;/strong&gt;   &lt;br /&gt;即   &lt;em&gt;Advanced Message Queuing Protocol&lt;/em&gt;,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息，并不受客户端/中间件不同产品，不同的开发语言等条件的限制。Erlang中的实现有 RabbitMQ等。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;SDS：&lt;/strong&gt;   &lt;br /&gt;简单动态字符串(   &lt;em&gt;simple dynamic string&lt;/em&gt;，SDS)的抽象类型。字符串是Redis中最为常见的数据存储类型，其底层实现是简单动态字符串sds(   &lt;em&gt;simple dynamic string&lt;/em&gt;)，是可以修改的字符串。它类似于Java中的ArrayList，它采用预分配冗余空间的方式来减少内存的频繁分配。C语言字符串使用长度为n+1的字符数组来表示长度为n的字符串，并且字符数组的最后一个元素总是空字符’\0’，因为这种字符串表示方式不能满足Redis对字符串在安全性、效率以及功能方面的要求，所以Redis自己构建了SDS，用于满足其需求。在Redis里，C语言字符串只用于一些无须对字符串值进行修改的地方，比如：日志。在Redis中，包含字符串值的键值对都是使用SDS实现的，除此之外，SDS还被用于AOF缓冲区、客户端状态的输入缓冲区。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;DDD：&lt;/strong&gt;   &lt;br /&gt;DDD(   &lt;em&gt;Domain-Driven Design&lt;/em&gt;领域驱动设计)是由Eric Evans最先提出，目的是对软件所涉及到的领域进行建模，以应对系统规模过大时引起的软件复杂性的问题。整个过程大概是这样的，开发团队和领域专家一起通过 通用语言(   &lt;em&gt;Ubiquitous Language&lt;/em&gt;)去理解和消化领域知识，从领域知识中提取和划分为一个一个的子领域(核心子域，通用子域，支撑子域)，并在子领域上建立模型，再重复以上步骤，这样周而复始，构建出一套符合当前领域的模型。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;RDB：&lt;/strong&gt;   &lt;br /&gt;Redis支持RDB和AOF两种持久化机制，持久化功能有效地避免因进程退出造成的数据丢失问题，当下次重启时利用之前持久化文件即可实现数据恢复。RDB (Redis DataBase)。RDB 是 Redis 默认的持久化方案。在指定的时间间隔内，执行指定次数的写操作，则会将内存中的数据写入到磁盘中。即在指定目录下生成一个dump.rdb文件。Redis 重启会通过加载dump.rdb文件恢复数据。RDB持久化是把当前进程数据生成快照保存到硬盘的过程，触发RDB持久化过程分为手动触发和自动触发。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;AOF：&lt;/strong&gt;   &lt;br /&gt;Redis支持RDB和AOF两种持久化机制，持久化功能有效地避免因进程退出造成的数据丢失问题，当下次重启时利用之前持久化文件即可实现数据恢复。AOF(append only file)持久化：以独立日志的方式记录每次写命令，重启时再重新执行AOF文件中命令达到恢复数据的目的。AOF的主要作用是解决了数据持久化的实时性，目前已经是Redis持久化的主流方式。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;ZAB：&lt;/strong&gt;   &lt;br /&gt;ZAB 协议全称：   &lt;em&gt;Zookeeper Atomic Broadcast(Zookeeper&lt;/em&gt;原子广播协议)。ZAB 协议的消息广播过程使用的是一个原子广播协议，类似一个 二阶段提交过程。对于客户端发送的写请求，全部由 Leader 接收，Leader 将请求封装成一个事务 Proposal，将其发送给所有 Follwer ，然后，根据所有 Follwer 的反馈，如果超过半数成功响应，则执行 commit 操作(先提交自己，再发送 commit 给所有 Follwer)。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;ACK：&lt;/strong&gt;   &lt;br /&gt;ACK消息，   &lt;em&gt;Acknowledgement&lt;/em&gt;。是在计算机网上中通信协议的一部分，是设备或是进程发出的消息，回复已收到数据。例如在传输控制协议(TCP，Transmission Control Protocol)中就有用ACK来告知创建链接时有收到SYN数据包、使用链接时有收到数据包，或是在中止链接有收到FIN数据包。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;BIO：&lt;/strong&gt;   &lt;br /&gt;   &lt;em&gt;Blocking Input Output&lt;/em&gt;，同步阻塞I/O模式，数据的读取写入必须阻塞在一个线程内等待其完成。服务器实现模式为一个连接一个线程，即客户端有连接请求时服务器端就需要启动一个线程进行处理，如果这个连接不做任何事情会造成不必要的线程开销，当然可以通过线程池机制改善。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;NIO：&lt;/strong&gt;   &lt;br /&gt;NIO是指将IO模式设为*“Non-Blocking”*模式。NIO是一种同步非阻塞的I/O模型，在Java 1.4 中引入了NIO框架，对应 java.nio 包，提供了 Channel , Selector，Buffer等抽象。NIO中的N可以理解为Non-blocking，不单纯是New。它支持面向缓冲的，基于通道的I/O操作方法。&lt;/p&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>geek</category>
      <guid isPermaLink="true">https://itindex.net/detail/59869-%E9%9D%A2%E8%AF%95-%E5%B0%B4%E5%B0%AC-%E9%9C%80%E8%A6%81</guid>
      <pubDate>Sat, 27 Jul 2019 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>k8s外网如何访问业务应用之Service 池化pod</title>
      <link>https://itindex.net/detail/59789-k8s-%E5%A4%96%E7%BD%91-%E8%AE%BF%E9%97%AE</link>
      <description>&lt;div&gt;  &lt;h5&gt;   &lt;a&gt;&lt;/a&gt;一、废话：先讲述一个k8s重要概念，我觉得这个概念是整个k8s集群实现微服务的最核心的概念&lt;/h5&gt;  &lt;h6&gt;   &lt;a&gt;&lt;/a&gt;概念：Service&lt;/h6&gt;  &lt;p&gt;Service定义了Pod的逻辑集合和访问该集合的策略，是真实服务的抽象。Service提供了一个统一的服务访问入口以及服务代理和发现机制，用户不需要了解后台Pod是如何运行。只需要将一组跑同一服务的pod池化成一个service，k8s集群会自动给这个service分配整个集群唯一ip和端口号（这个端口号自己在yaml文件中定义），一个service定义了访问pod的方式，就像单个固定的IP地址和与其相对应的DNS名之间的关系。   &lt;br /&gt;Service其实就是我们经常提起的微服务架构中的一个&amp;quot;微服务&amp;quot;，每个微服务后端负载均衡多个业务pod，由版本控制（deployment）控制pod数量，保证服务的高可靠性与冗余性。最终我们的系统由多个提供不同业务能力而又彼此独立的微服务单元所组成，服务之间通过TCP/IP（k8s集群分配的整个集群唯一的）进行通信，从而形成了我们强大而又灵活的弹性网络，拥有了强大的分布式能力、弹性扩展能力、容错能力；&lt;/p&gt;  &lt;p&gt;理解service这个概念之后，我们跑去service后端看看具体是怎么实现的？   &lt;br /&gt;需求：有一个问题就是现在我的业务分配在多个Pod上，那么如果我某个Pod死掉岂不是业务完蛋了，当然也会有人说Pod死掉没问题啊，K8S自身机制Deployment和Controller会动态的创建和销毁Pod来保证应用的整体稳定性，那这时候还会有问题，那就是每个Pod产生的IP都是动态的，那所以说重新启动了我对外访问的IP岂不是要变了，别急，下面我们来解决下这个问题。&lt;/p&gt;  &lt;p&gt;可以通过Service来解决如上所遇到的问题&lt;/p&gt;  &lt;h6&gt;   &lt;a&gt;&lt;/a&gt;二、Service是kubernetes最核心的概念，通过创建Service，可以为一组具有相同功能的容器应用提供一个统一的入口地址，并且将请求进行负载分发到后端的各个容器应用上。至于如何负载分发，我们不用去担心，k8s自己搞定。&lt;/h6&gt;  &lt;p&gt;简单来说Service就是一个把所有Pod统一成一个组，然后对外提供固定一个IP，具体是哪些Pod，可以通过之前介绍到的Label标签来进行设置，假设一个pod死掉，副本控制器在生成一个pod,这是pod ip肯定会变，但是我们不去关心你pod ip是多少，我们只知道service ip 没变就好了，因为新的pod 早就加入到我的service中了，各个服务之间通信是通过service 唯一ip来通信的。&lt;/p&gt;  &lt;p&gt;上述所有操作，为什么实现了所谓的“”微服务”，举个大家都懂的例子；   &lt;br /&gt;一套简单的架构，nginx做反向代理，后端web为8个tomcat实例，在k8s集群中怎么实现呢，我只需要跑8个pod,每个pod运行tomcat容器，service池化这8个pod,而且副本控制会自动动态控制pod数量，少于8个他会创建到8个，多余8个会自动删除到8个。而且我们访问service ip，k8s中的kube-proxy会自动负载均衡后端的8个pod,这一套服务集群内部访问，只需要一个service ip 和端口号就可以；同理，redis集群，同样可以这样实现，每个service相当于微服务，各个服务之间灵活通信。&lt;/p&gt;  &lt;h6&gt;   &lt;a&gt;&lt;/a&gt;废话不多说，咱们直接实操演示&lt;/h6&gt;  &lt;h6&gt;   &lt;a&gt;&lt;/a&gt;三、 创建副本控制资源名为nginx-deployment，运行两个pod，每个pod运行一个nginx容器，容器端口开放80.&lt;/h6&gt;  &lt;pre&gt;   &lt;code&gt;[root@master yaml]# cat nginx.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx  
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx  就是说哪些Pod被Service池化是根据Label标签来的，此行nginx字样，后面我们创建Service会用到
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80


kubectl create -f nginx.yaml 
deployment.apps/nginx-deployment created&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;   &lt;img alt="" src="https://upload-images.jianshu.io/upload_images/15572192-6cc4fced076223f9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;h6&gt;   &lt;a&gt;&lt;/a&gt;创建Service 池化刚才的两个nginx pod&lt;/h6&gt;  &lt;pre&gt;   &lt;code&gt;[root@master yaml]# cat nginx-svc.yml 
apiVersion: v1
kind: Service
metadata:
  name: nginx-svc
spec:
  type: NodePort
  selector:
    app: nginx
  ports:
  - protocol: TCP
    port: 8080  这个资源(svc)开放的端口
    targetPort: 80

selector选择之前Label标签为nginx的Pod作为Service池化的对象，
最后说的是把Service的8080端口映射到Pod的80端口。

kubectl apply -f nginx-svc.yml 
service/nginx-svc created&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;   &lt;img alt="" src="https://upload-images.jianshu.io/upload_images/15572192-aa920984d2f26b87.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;创建完成之后nginx-svc会分配到一个cluster-ip，可以通过该ip访问后端nginx业务。    &lt;br /&gt;那它是怎么实现的呢？答案是通过iptables实现的地址转换和端口转换，自己可以去研究&lt;/strong&gt;   &lt;br /&gt;   &lt;img alt="" src="https://upload-images.jianshu.io/upload_images/15572192-0f8813999618c3d8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;那这时候有人说了，还是不能外网访问啊，别急下面我们来进行外网地址访问设置。在实际生产环境中，对Service的访问可能会有两种来源：Kubernetes集群内部的程序（Pod）和Kubernetes集群外部，为了满足上述的场景，Kubernetes service有以下三种类型：   &lt;br /&gt;1.ClusterIP：提供一个集群内部的虚拟IP（与Pod不在同一网段)，以供集群内部的pod之间通信使用。   &lt;br /&gt;2.NodePort：在每个Node上打开一个随机端口并且每个Node的端口都是一样的，通过:NodePort的方式Kubernetes集群外部的程序可以访问Service。   &lt;br /&gt;3.LoadBalancer：利用Cloud Provider特有的Load Balancer对外提供服务，Cloud Provider负责将Load Balancer的流量导向Service&lt;/p&gt;  &lt;p&gt;本篇文章我着重讲下第二种方式，也就是NodePort方式，修改nginx-svc.yml文件，也就是刚才前面创建的Service文件，相信细心的同学会发现在之前截图的时候已经做好了NodePort，因为我的环境已经配置好了所以这样就不在截图了，配置很简单，可以网上看下截图，就是添加一个type：NodePort，然后重新创建下nginx-svc，命令的话和创建的命令一样，我们来看看创建完事的结果。   &lt;br /&gt;   &lt;img alt="" src="https://upload-images.jianshu.io/upload_images/15572192-c24280f0c864458d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;h6&gt;   &lt;a&gt;&lt;/a&gt;如果刚开始你没有设置NodePort这个type的时候在端口那只会显示一个8080端口，而设置了之后会看到多了一个端口也就是37884，那8080就是是cluster-ip监听的端口，那37844就是在node节点上新起的一个端口(K8s会从30000~32767中分配一个可用的端口)，只用于端口转发，转发到service的端口。&lt;/h6&gt;  &lt;h6&gt;   &lt;a&gt;&lt;/a&gt;用户访问服务，每个节点都会监听这个端口，并转发给Service，service负载均衡调度后端pod,也就是防止说一个节点挂了影响访问&amp;gt;。&lt;/h6&gt;  &lt;h6&gt;   &lt;a&gt;&lt;/a&gt;说的再通俗一点，集群内部可以直接通过service的ip+端口访问，而nodeport就是为了外网访问服务，给node开了一个端口，转发到service的ip+端口。&lt;/h6&gt;  &lt;h6&gt;   &lt;a&gt;&lt;/a&gt;可能有人会问了，说这里的Service可不可以固定？当时可以了，可以在Service nginx-svc.yml文件里面添加一个nodeport。&lt;/h6&gt;  &lt;p&gt;   &lt;img alt="&amp;#22312;&amp;#36825;&amp;#37324;&amp;#25554;&amp;#20837;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://img-blog.csdnimg.cn/20190701093258450.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3podXRvbmdjbG91ZA==,size_16,color_FFFFFF,t_70"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;h4&gt;   &lt;a&gt;&lt;/a&gt;当然，这个访问依然是负载均衡的！&lt;/h4&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>geek</category>
      <guid isPermaLink="true">https://itindex.net/detail/59789-k8s-%E5%A4%96%E7%BD%91-%E8%AE%BF%E9%97%AE</guid>
      <pubDate>Fri, 05 Jul 2019 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>LANMP安全配置之Nginx安全配置</title>
      <link>https://itindex.net/detail/59717-lanmp-%E5%AE%89%E5%85%A8-nginx</link>
      <description>&lt;div&gt;  &lt;h3&gt;   &lt;a&gt;&lt;/a&gt;0x00 前言&lt;/h3&gt;  &lt;p&gt;比起前几篇的   &lt;a href="https://blog.csdn.net/syy0201/article/details/90050043" rel="nofollow"&gt;Apache安全配置&lt;/a&gt;、   &lt;a href="https://blog.csdn.net/syy0201/article/details/89974148" rel="nofollow"&gt;PHP安全配置&lt;/a&gt;、   &lt;a href="https://blog.csdn.net/syy0201/article/details/90644695" rel="nofollow"&gt;Mysql安全配置&lt;/a&gt;，对Nginx的了解巨少，没怎么用过除了知道Nginx解析漏洞就啥也不知道了   &lt;br /&gt;好了，开始学习   &lt;br /&gt;   &lt;img alt="&amp;#22312;&amp;#36825;&amp;#37324;&amp;#25554;&amp;#20837;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://img-blog.csdnimg.cn/20190611180649757.jpg"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;h3&gt;   &lt;a&gt;&lt;/a&gt;0x01 账户权限管理&lt;/h3&gt;  &lt;p&gt;   &lt;strong&gt;1.1 更改默认用户名&lt;/strong&gt;   &lt;br /&gt;nginx默认nobody,可更改默认用户名防止他人利用&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;1.2 新增用户&lt;/strong&gt;   &lt;br /&gt;（1）新增组   &lt;br /&gt;groupadd -g 108  -r nginx   &lt;br /&gt;（2）新增用户   &lt;br /&gt;useradd -u 108 -r -g 108 nginx   &lt;br /&gt;（3）id nginx&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;1.3 限制账户目录权限&lt;/strong&gt;   &lt;br /&gt;防止某账户权限过大，可提前设置，步骤如下：   &lt;br /&gt;（1）chmod o-r -R /									 让该账户失去所有权限   &lt;br /&gt;（2）chmod o-r -R html/          					 单独赋予Web目录权限   &lt;br /&gt;（3）执行命令 chmod 776 /bin/sh				 限制账户命令执行权限   &lt;br /&gt;nginx默认   &lt;code&gt;nobody&lt;/code&gt;,没有访问目录权限,设置网站目录对于nobody的权限为可读、可执行。上传目录和写入目录给读取和写入权限，不要给执行权限！   &lt;br /&gt;   &lt;img alt="&amp;#22312;&amp;#36825;&amp;#37324;&amp;#25554;&amp;#20837;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://img-blog.csdnimg.cn/20190611104254917.png"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;h2&gt;   &lt;a&gt;&lt;/a&gt;0x02 限制IP访问&lt;/h2&gt;  &lt;p&gt;打开配置文件，按下图设置语法来设置禁止访问IP和允许访问IP   &lt;br /&gt;   &lt;img alt="&amp;#22312;&amp;#36825;&amp;#37324;&amp;#25554;&amp;#20837;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://img-blog.csdnimg.cn/20190611110418563.png"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;h2&gt;   &lt;a&gt;&lt;/a&gt;0x03 文件目录设置&lt;/h2&gt;  &lt;p&gt;   &lt;strong&gt;3.1 文件权限&lt;/strong&gt;   &lt;br /&gt;通过   &lt;code&gt;chmod&lt;/code&gt;命令将web目录设置成可执行脚本，但不可以写入。一个目录不能同时有写入和执行权限。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;3.2 目录访问限制&lt;/strong&gt;   &lt;br /&gt;打开nginx配置文件   &lt;br /&gt;   &lt;img alt="&amp;#22312;&amp;#36825;&amp;#37324;&amp;#25554;&amp;#20837;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://img-blog.csdnimg.cn/2019061111192492.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3N5eTAyMDE=,size_16,color_FFFFFF,t_70"&gt;&lt;/img&gt;   &lt;br /&gt;autoindex on;  //添加此行 目录列表展示&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;3.3 目录执行权限&lt;/strong&gt;   &lt;br /&gt;打开nginx配置文件，以上传目录为例，加入下面代码对上传目录加以限制。   &lt;br /&gt;   &lt;img alt="&amp;#22312;&amp;#36825;&amp;#37324;&amp;#25554;&amp;#20837;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://img-blog.csdnimg.cn/20190611101222329.png"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;3.4 限制TXT文件被访问&lt;/strong&gt;   &lt;br /&gt;这里是指具体的文件被禁止访问,你也可以设置成其他文件   &lt;br /&gt;   &lt;img alt="&amp;#22312;&amp;#36825;&amp;#37324;&amp;#25554;&amp;#20837;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://img-blog.csdnimg.cn/20190611103912620.png"&gt;&lt;/img&gt;   &lt;br /&gt;同时设置多个文件语法：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;location ~* \.(txt|doc)$             //表示禁止访问*.txt和*.doc文件&lt;/code&gt;&lt;/pre&gt;  &lt;h2&gt;   &lt;a&gt;&lt;/a&gt;0x04 日志文件设置&lt;/h2&gt;  &lt;p&gt;   &lt;strong&gt;4.1 开启日志默认配置&lt;/strong&gt;   &lt;br /&gt;将   &lt;code&gt;error_log&lt;/code&gt;前的“#”去掉，记录错误日志   &lt;br /&gt;   &lt;img alt="&amp;#22312;&amp;#36825;&amp;#37324;&amp;#25554;&amp;#20837;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://img-blog.csdnimg.cn/20190611111533387.png"&gt;&lt;/img&gt;   &lt;br /&gt;将   &lt;code&gt;access_log&lt;/code&gt;和   &lt;code&gt;log_format&lt;/code&gt;前的“#”去掉，记录访问日志&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;补充：Nginx访问日志主要有两个参数控制&lt;/li&gt;   &lt;li&gt;log_format #用来定义记录日志的格式（可以定义多种日志格式，取不同名字即可）&lt;/li&gt;   &lt;li&gt;access_log #用来指定日至文件的路径及使用的何种日志格式记录日志&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;详细访问日志设置参考：   &lt;a href="https://www.cnblogs.com/xuyuQAQ/p/8728773.html" rel="nofollow"&gt;https://www.cnblogs.com/xuyuQAQ/p/8728773.html&lt;/a&gt;   &lt;br /&gt;   &lt;img alt="&amp;#22312;&amp;#36825;&amp;#37324;&amp;#25554;&amp;#20837;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://img-blog.csdnimg.cn/20190611112442268.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3N5eTAyMDE=,size_16,color_FFFFFF,t_70"&gt;&lt;/img&gt;   &lt;br /&gt;   &lt;strong&gt;4.2 日志文件限制&lt;/strong&gt;   &lt;br /&gt;具体设置格式同上一条txt文件&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;4.3 访问日志的权限设置&lt;/strong&gt;   &lt;br /&gt;假如日志目录为/app/logs,则授权方法如下：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;chown -R root.root /app/logs
chown -R 700 /app/logs&lt;/code&gt;&lt;/pre&gt;  &lt;h3&gt;   &lt;a&gt;&lt;/a&gt;0x05 关闭报错信息&lt;/h3&gt;  &lt;p&gt;关闭Nginx版本号   &lt;br /&gt;打开配置文件，插入如下代码   &lt;br /&gt;   &lt;img alt="&amp;#22312;&amp;#36825;&amp;#37324;&amp;#25554;&amp;#20837;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://img-blog.csdnimg.cn/2019061117530185.png"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;h3&gt;   &lt;a&gt;&lt;/a&gt;0x06 WAF扩展&lt;/h3&gt;  &lt;p&gt;Nginx有三个常见的漏洞防御模式，   &lt;code&gt;modsecurity&lt;/code&gt;、   &lt;code&gt;Naxsi&lt;/code&gt;和   &lt;code&gt;ngx_lua_waf&lt;/code&gt;，可通过安装这些模块来提升安全性   &lt;br /&gt;emmmm留个坑之后再学&lt;/p&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>geek</category>
      <guid isPermaLink="true">https://itindex.net/detail/59717-lanmp-%E5%AE%89%E5%85%A8-nginx</guid>
      <pubDate>Thu, 20 Jun 2019 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>AwesomePlayer实现过程分析</title>
      <link>https://itindex.net/detail/59709-awesomeplayer-%E5%88%86%E6%9E%90</link>
      <description>&lt;div&gt;  &lt;p&gt;在Android的多媒体框架中，stagefrightplayer是对Awesomeplayer的封装，是AwesomePlayer的代理，所以这里面实际干活的当然是我们今天的主角-AwesomePlayer。AwesomePlayer说白了也是一个普通的播放器，他与VCL、mplayer、ffmpeg等开源的结构是一致的，只是实现的方式有所不同，这里就按照以下四个步骤来分析AwesomePlayer的实现。&lt;/p&gt;  &lt;p&gt;一、多媒体播放器的基本模型&lt;/p&gt;  &lt;p&gt;二、AwesomePlayer的总体结构&lt;/p&gt;  &lt;p&gt;三、AwesomePlayer的驱动方式&lt;/p&gt;  &lt;p&gt;四、AwesomePlayer的关键成员分析&lt;/p&gt;  &lt;p&gt;五、AwesomePlayer的实现过程代码分析&lt;/p&gt;  &lt;p&gt;一、多媒体播放器的基本模型&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" src="https://img-blog.csdn.net/20150225160134440?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbHVvemlyb25n/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;1. source：&lt;/strong&gt;数据源，数据的来源不一定都是本地file，也有可能是网路上的各种协议例如：http、rtsp、HLS等。source的任务就是把数据源抽象出来，为下一个demux模块提供它需要的稳定的数据流。demux不用关心数据到底是从什么地方来的。   &lt;br /&gt;   &lt;strong&gt;2. demux解复用：&lt;/strong&gt;视频文件一般情况下都是把音视频的ES流交织的通过某种规则放在一起。这种规则就是容器规则。现在有很多不同的容器格式。如ts、mp4、flv、mkv、avi、rmvb等等。demux的功能就是把音视频的ES流从容器中剥离出来，然后分别送到不同的解码器中。其实音频和视频本身就是2个独立的系统。容器把它们包在了一起。但是他们都是独立解码的，所以解码之前，需要把它分别独立出来。demux就是干这活的，他为下一步decoder解码提供了数据流。   &lt;br /&gt;   &lt;strong&gt;3. decoder解码：&lt;/strong&gt;解码器——播放器的核心模块。分为音频和视频解码器。影像在录制后, 原始的音视频都是占用大量空间, 而且是冗余度较高的数据. 因此, 通常会在制作的时候就会进行某种压缩 ( 压缩技术就是将数据中的冗余信息去除数据之间的相关性 ). 这就是我们熟知的音视频编码格式, 包括MPEG1（VCD）/ MPEG2（DVD）/ MPEG4 / H.264 等等. 音视频解码器的作用就是把这些压缩了的数据还原成原始的音视频数据。当然, 编码解码过程基本上都是有损的 .解码器的作用就是把编码后的数据还原成原始数据。   &lt;br /&gt;   &lt;strong&gt;4. output输出：&lt;/strong&gt;输出部分分为音频和视频输出。解码后的音频（pcm）和视频（yuv）的原始数据需要得到音视频的output模块的支持才能真正的让人的感官系统（眼和耳）辨识到。&lt;/p&gt;  &lt;p&gt;所以，一个多媒体播放器大致分成上述4部分。怎么抽象的实现这4大部分，以及找到一种合理的方式将这几部分组织并运动起来。是每个播放器不同的实现方式而已。&lt;/p&gt;  &lt;p&gt;二、AwesomePlayer的总体结构&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" src="https://img-my.csdn.net/uploads/201308/18/1376814497_3711.jpg"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" src="https://img-blog.csdn.net/20170904160305057?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbTBfMzc4MjU1MzI=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;mExtractor属于MediaExtractor类， 对于每种格式的媒体文件，均需要实现一个MediaExtractor 的子类，例如AVI文件的extractor类为AVIExtractor,在构造函数中完成对数据的解析，主要信息由：媒体文件中流的个数，音频流的采样率、声道数、量化位数等，视频流的宽度、高度、帧率等信息。&lt;/p&gt;  &lt;p&gt;mAudioTrack和mVideoTrack属于MediaSource类，mVideoTrack和mAudioTrack在 onPrepareAsyncEvent事件被触发时,由MediaExtractor分离出来.对于每个流，都对应一个单独的MediaSource，以avi为例，为AVISource，MediaSource 提供了状态接口（start stop），数据读取接口（read），参数查询接口（getFormat）。其中调用一次read函数，可以认为是读取对应的一个数据包。一般而言，由于MediaExtractor 负责解析工作，因此MediaSource 的read操作一般也通过MediaExtractor 的接口获取offset和size，对于MediaSource 只进行数据的读取工作，不参与解析，VideoTrack与AudioTrack指的是Extractor(即demux)的两个通道,从这里输出的分别就是单纯的解复用后的Video/Audio流了。&lt;/p&gt;  &lt;p&gt;mAudioSource 和mVideoSource也属于MediaSource类，这里mAudioSource &amp;amp;mVideoSource可以认为是awesomeplayer与decoder之间的桥梁，awesomeplayer从mAudioSource &amp;amp;mVideoSource 里获取数据，进行播放，而decoder则负责解码并向mAudioSource &amp;amp;mVideoSource 填充数据， 这里awesomeplayer与decoder的通信是通过OMXClient mClient; 成员进行的，OMX*解码器是一个服务，每个awesomeplayer对象都包含一个单独的client端与之交互。&lt;/p&gt;  &lt;p&gt; mAudioPlayer属于AudioPlayer类，mVideoRenderer属于AwesomeRenderer类，mAudioPlayer 是负责音频输出的模块，主要封装关系为：mAudioPlayer-&amp;gt;mAudioSink-&amp;gt;mAudioTrack(这里的mAudioTrack 与前面不同，此处为AudioTrack对象)实际进行音频播放的对象为mAudioTrack-audioflinger 结构，mVideoRenderer 是负责视频显示的模块，封装关系为：mVideoRenderer -&amp;gt;mTarget（SoftwareRenderer）-&amp;gt;mNativeWindow ，即VideoRenderer + Surface即视频的输出，AudioSink即音频的输出.&lt;/p&gt;  &lt;p&gt;三、AwesomePlayer的驱动方式&lt;/p&gt;  &lt;p&gt;这里就不得不提到android中的一个类即TimedEventQueue，这是一个消息处理类， 这里通过对每个事件消息提供一个fire函数完成相应的操作。而整个播放过程的驱动方式为递归的触发mVideoEvent 事件来控制媒体文件的播放。TimedEventQueue资料建议自行查找下。&lt;/p&gt;  &lt;p&gt;说明：详细可参考下面的代码分析&lt;/p&gt;  &lt;p&gt;    awesomeplayer中主要有如下几个事件&lt;/p&gt;  &lt;p&gt;    sp&amp;lt;TimedEventQueue::Event&amp;gt; mVideoEvent = new AwesomeEvent(this, &amp;amp;AwesomePlayer::onVideoEvent);   --- 显示一帧画面，并负责同步处理   &lt;br /&gt;    sp&amp;lt;TimedEventQueue::Event&amp;gt; mStreamDoneEvent = new AwesomeEvent(this, &amp;amp;AwesomePlayer::onStreamDone);   -- 播放结束的处理&lt;/p&gt;  &lt;p&gt;    sp&amp;lt;TimedEventQueue::Event&amp;gt; mBufferingEvent = new AwesomeEvent(this, &amp;amp;AwesomePlayer::onBufferingUpdate); -- cache数据   &lt;br /&gt;    sp&amp;lt;TimedEventQueue::Event&amp;gt; mCheckAudioStatusEvent = new AwesomeEvent(this, &amp;amp;AwesomePlayer::onCheckAudioStatus); -- 监测audio 状态：seek以及播放结束   &lt;br /&gt;    sp&amp;lt;TimedEventQueue::Event&amp;gt; mVideoLagEvent = new AwesomeEvent(this, &amp;amp;AwesomePlayer::onVideoLagUpdate);  -- 监测video 解码性能&lt;/p&gt;  &lt;p&gt;    sp&amp;lt;TimedEventQueue::Event&amp;gt; mAsyncPrepareEvent = new AwesomeEvent(this, &amp;amp;AwesomePlayer::onPrepareAsyncEvent); -- 完成prepareAsync工作&lt;/p&gt;  &lt;p&gt;四、AwesomePlayer的关键成员分析&lt;/p&gt;  &lt;p&gt;关键成员之demux相关&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;sp&amp;lt;MediaSource&amp;gt; mVideoTrack;
sp&amp;lt;MediaSource&amp;gt; mAudioTrack&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;分别代表一个视频轨道和音频轨道,用于提取视频流和音频流(Demux后但未解码的数据). mVideoTrack和mAudioTrack在 onPrepareAsyncEvent事件被触发时,由MediaExtractor分离出来.&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;void AwesomePlayer::onPrepareAsyncEvent() {   
        status_t err = finishSetDataSource_l();   
}  
 
status_t AwesomePlayer::finishSetDataSource_l() {   
 sp&amp;lt;MediaExtractor&amp;gt; extractor = MediaExtractor::Create(dataSource);   
    return setDataSource_l(extractor);   
}   
 
status_t AwesomePlayer::setDataSource_l(const sp&amp;lt;MediaExtractor&amp;gt; &amp;amp;extractor) {   
    for (size_t i = 0; i &amp;lt; extractor-&amp;gt;countTracks(); ++i) {   
        sp&amp;lt;MetaData&amp;gt; meta = extractor-&amp;gt;getTrackMetaData(i);   
 
        const char *mime;   
        CHECK(meta-&amp;gt;findCString(kKeyMIMEType, &amp;amp;mime));   
 
        if (!haveVideo &amp;amp;&amp;amp; !strncasecmp(mime, &amp;quot;video/&amp;quot;, 6)) {   
            setVideoSource(extractor-&amp;gt;getTrack(i));   //
            haveVideo = true;   
        } else if (!haveAudio &amp;amp;&amp;amp; !strncasecmp(mime, &amp;quot;audio/&amp;quot;, 6)) {   
            setAudioSource(extractor-&amp;gt;getTrack(i));   
            haveAudio = true;   
        if (haveAudio &amp;amp;&amp;amp; haveVideo) {   
            break;   
        }   
    }   
}  &lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;这里依据提供的dataSource建立对应的MediaExtractor，这里说明下，有了dataSource，就可以从文件中读取数据，就可以通过分析文件头解析出文件具体是哪种格式，然后建立相应的MediaExtractor，之前有介绍，在MediaExtractor 建立的时候便完成了对文件的解析，包括流数量，各个流的具体信息等，下面就可以直接使用了，得到extractor之后，通过setVideoSource() setAudioSource()产生独立的mVideoTrack(视频)、mAudioTrack(音频)数据流，分别为音视频解码器提供有各自需要的数据流。从上面这段代码可以看到AwesomePlayer默认采用第一个VideoTrack和第一个AudioTrack,&lt;/p&gt;  &lt;p&gt;流数量可以通过extractor-&amp;gt;countTracks() 得到，每个流对应的信息存储在一个MetaData中通过extractor-&amp;gt;getTrackMetaData(i);&lt;/p&gt;  &lt;p&gt;获取kKeyMIMEType参数，此参数标示了流类型是音频 视频 还是字幕&lt;/p&gt;  &lt;p&gt;关键成员之音频相关&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;sp&amp;lt;MediaSource&amp;gt; mAudioSource;&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;mAudioSource可以认为是一个音频解码器的封装，&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;sp&amp;lt;MediaPlayerBase::AudioSink&amp;gt; mAudioSink;&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;mAudioSink代表一个音频输出设备.用于播放解码后的音频数据&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;AudioPlayer *mAudioPlayer;&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;mAudioPlayer把mAudioSource和mAudioSink包起来,完成一个音频播放器的功能.如start, stop, pause, seek 等.&lt;/p&gt;  &lt;p&gt;AudioPlayer和AudioSink通过Callback建立关联.当AudioSink可以输出音频时,会通过回调通知AudioPlayer填充音频数据.而此时AudioPlayer会尝试从AudioSource读取音频数据.&lt;/p&gt;  &lt;p&gt;关键成员之视频相关&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;sp&amp;lt;MediaSource&amp;gt; mVideoSource&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;mVideoSource可以认为是一个视频解码器的封装,用于产生视频图像供AwesomeRender渲染, mVideoSource的数据源则由mVideoTrack提供.mVideoSource由OMXCodec创建.&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;status_t AwesomePlayer::initVideoDecoder(uint32_t flags) {   
    mVideoSource = OMXCodec::Create(   
            mClient.interface(), mVideoTrack-&amp;gt;getFormat(),   
            false, // createEncoder   
            mVideoTrack,   
            NULL, flags);   
}&lt;/code&gt;&lt;/pre&gt;  &lt;pre&gt;   &lt;code&gt;sp&amp;lt;AwesomeRenderer&amp;gt; mVideoRenderer &lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;mVideoRenderer负责将解码后的图片渲染输出&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;sp&amp;lt;ISurface&amp;gt; mISurface&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;mISurface供播放器渲染的画布&lt;/p&gt;  &lt;p&gt;关键数据之其他&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;OMXClient mClient&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;OMX可以理解为一个编解码器的库,AwesomePlayer利用OMXClient跟OMXIL进行通信.这里OMX IL类似于一个服务端. AwesomePlayer作为一个客户端,请求OMX IL进行解码的工作.可参考下图：&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" src="https://img-blog.csdn.net/20160922154612747"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;TimedEventQueue mQueue&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;TimedEventQueue一个消息队列类，可以认为TimeEventQueue 是一个调度系统，调度的对象是事件Event，一个TimeEventQueue 可以同时处理多个事件TimedEventQueue中维护了一个队列，外面通过调用其提供的方法postEvent, postEventWithDelay等等来向队列添加事件，在执行完mQueue.start()后，TimedEventQueue将启动一个线程，用来取出队列中的事件，并执行之&lt;/p&gt;  &lt;p&gt;AwesomePlayer采用定时器队列的方式进行运作. mQueue在MediaPlayer调用 prepare的时候就开始运作, (而不是MediaPlayer.start).&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;status_t AwesomePlayer::prepareAsync() {  
    if (!mQueueStarted) {  
        mQueue.start();  
        mQueueStarted = true;  
    }  
    return OK;  
}&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;AwesomePlayer处理了几个定时器事件,包括:&lt;/p&gt;  &lt;p&gt;(1)onVideoEvent();//&lt;/p&gt;  &lt;p&gt;(2)onStreamDone();//&lt;/p&gt;  &lt;p&gt;(3)onBufferingUpdate();//&lt;/p&gt;  &lt;p&gt;(4)onCheckAudioStatus();//&lt;/p&gt;  &lt;p&gt;(5)onPrepareAsyncEvent();//&lt;/p&gt;  &lt;p&gt;总结:从关键的成员可以看出,AwesomePlayer拥有视频源和音频源 (VideoTrack, AudioTrack),有音视频解码器(VideoSoure, AudioSource),可以渲染图像(AwesomeRenderer) , 可以输出声音 (AudioSink),具备一个播放器完整的材料了.&lt;/p&gt;  &lt;p&gt;五、AwesomePlayer的实现过程代码分析&lt;/p&gt;  &lt;p&gt;5.1总的基本播放流程分析&lt;/p&gt;  &lt;p&gt;还是从之前的一个播放器的播放序列看起&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;StagefrightPlayer player =newStagefrightPlayer();  
player-&amp;gt;setDataSource(*)  
player-&amp;gt;prepareAsync()  
player-&amp;gt;start();&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;①由于StagefrightPlayer是AwesomePlayer的代理，所以我们接下来看下AwesomePlayer的构造函数&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;AwesomePlayer::AwesomePlayer()  
    : mQueueStarted(false),  
      mUIDValid(false),  
      mTimeSource(NULL),  
      mVideoRenderingStarted(false),  
      mVideoRendererIsPreview(false),  
      mAudioPlayer(NULL),  
      mDisplayWidth(0),  
      mDisplayHeight(0),  
      mVideoScalingMode(NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW),  
      mFlags(0),  
      mExtractorFlags(0),  
      mVideoBuffer(NULL),  
      mDecryptHandle(NULL),  
      mLastVideoTimeUs(-1),  
      mTextDriver(NULL) {  
    CHECK_EQ(mClient.connect(), (status_t)OK);  
    DataSource::RegisterDefaultSniffers();  
    mVideoEvent =newAwesomeEvent(this, &amp;amp;AwesomePlayer::onVideoEvent);  
    mVideoEventPending =false;  
    mStreamDoneEvent =newAwesomeEvent(this, &amp;amp;AwesomePlayer::onStreamDone);  
    mStreamDoneEventPending =false;  
    mBufferingEvent =newAwesomeEvent(this, &amp;amp;AwesomePlayer::onBufferingUpdate);  
    mBufferingEventPending =false;  
    mVideoLagEvent =newAwesomeEvent(this, &amp;amp;AwesomePlayer::onVideoLagUpdate);  
    mVideoEventPending =false;  
    mCheckAudioStatusEvent =newAwesomeEvent(  
            this, &amp;amp;AwesomePlayer::onCheckAudioStatus);  
    mAudioStatusEventPending =false;  
    reset();  
}  &lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;从上述代码中可见：&lt;/p&gt;  &lt;p&gt;在awesomeplayer的构造函数中主要就是做些准备工作：如创立event对象，并提供fire函数，设置对应的各个状态变量&lt;/p&gt;  &lt;p&gt;还有一点需要注意的是 mClient.connect() 建立了awesomeplayer与omx解码器之间的链接&lt;/p&gt;  &lt;p&gt;②设置数据源URI&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;status_t AwesomePlayer::setDataSource(   
        const char *uri, const KeyedVector&amp;lt;String8, String8&amp;gt; *headers) {     
    mUri = uri;                // 这里只是把URL保存起来而已, 真正的工作在Prepare之后进行  
    return OK;   
}&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;③开启定时器队列,并且 Post一个AsyncPrepareEvent事件&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;status_t AwesomePlayer::prepareAsync() {   
    
    mQueue.start();                                               // 开启定时器队列 
    
    mAsyncPrepareEvent = new AwesomeEvent(   
            this, &amp;amp;AwesomePlayer::onPrepareAsyncEvent);           // Post AsyncPrepare 事件 
 
    mQueue.postEvent(mAsyncPrepareEvent);   
    return OK;   
}&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;Prepare之后,AwesomePlayer开始运作.&lt;/p&gt;  &lt;p&gt;④AsyncPrepare事件被触发&lt;/p&gt;  &lt;p&gt;当这个事件被触发时, AwesomePlayer开始创建 VideoTrack和AudioTrack ,然后创建 VideoDecoder和AudioDecoder&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;void AwesomePlayer::onPrepareAsyncEvent() {   
   
    finishSetDataSource_l();            // a. 创建视频源和音频源
    
    initVideoDecoder();                 // b. 创建视频解码器
    
    initAudioDecoder();                 // c. 创建音频解码器
}&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;至此,播放器准备工作完成,可以开始播放了&lt;/p&gt;  &lt;p&gt;⑤Post第一个VideoEvent（显示一帧画面并进行同步处理）&lt;/p&gt;  &lt;p&gt;调用顺序：AwesomePlayer::play()调用 -&amp;gt; AwesomePlayer::play_l()调用 -&amp;gt;AwesomePlayer::postVideoEvent_l(int64_t delayUs)&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;void AwesomePlayer::postVideoEvent_l(int64_t delayUs) {   
    mQueue.postEventWithDelay(mVideoEvent, delayUs &amp;lt; 0 ? 10000 : delayUs);   
}&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;⑥VideoEvent被触发&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;void AwesomePlayer::onVideoEvent() {   
    
    mVideoSource-&amp;gt;read(&amp;amp;mVideoBuffer, &amp;amp;options);                   // 从视频解码器中读出视频图像
    
    if (mVideoRendererIsPreview || mVideoRenderer == NULL) {       // 创建AwesomeRenderer (如果没有的话)
        initRenderer_l();   
    }   
    
     mVideoRenderer-&amp;gt;render(mVideoBuffer);                         // 渲染视频图像
    
     postVideoEvent();                                             // 再次发送一个VideoEvent, 这样播放器就不停的播放了  
}&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;总结: SetDataSource -&amp;gt; Prepare -&amp;gt; Play-&amp;gt; postVieoEvent -&amp;gt; OnVideoEvent -&amp;gt; postVideoEvent-&amp;gt; ....onVideoEvent-&amp;gt; postStreamDoneEvent -&amp;gt;播放结束&lt;/p&gt;  &lt;p&gt;5.2、视频 /音频的分离&lt;/p&gt;  &lt;p&gt;①就像前面基本播放流程分析描述的那样，当AsyncPrepare事件被触发时, AwesomePlayer会调用finishSetDataSource_l创建 VideoTrack 和 AudioTrack，finishSetDataSource_l通过URI前缀判断 媒体类型,比如http, rtsp,或者本地文件等 这里的uri就是一开始 通过setDataSource设置的 根据uri创建相应的DataSource,再进一步的利用 DataSource创建MediaExtractor做A/V分离&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;status_t AwesomePlayer::finishSetDataSource_l() {   
        sp&amp;lt;datasource&amp;gt; dataSource;   
        /// 通过URI前缀判断媒体类型, 比如 http, rtsp,或者本地文件等   
        /// 这里的uri就是一开始 通过setDataSource设置的   
        /// 根据uri 创建相应的MediaExtractor   
 
        if (!strncasecmp(&amp;quot;http://&amp;quot;, mUri.string(), 7)) {   
            mConnectingDataSource = new NuHTTPDataSource;   
            mConnectingDataSource-&amp;gt;connect(mUri, &amp;amp;mUriHeaders);   
            mCachedSource = new NuCachedSource2(mConnectingDataSource);   
            dataSource = mCachedSource;   
        } else if (!strncasecmp(&amp;quot;rtsp://&amp;quot;, mUri.string(), 7)) {   
            mRTSPController-&amp;gt;connect(mUri.string());   
            sp&amp;lt;mediaextractor&amp;gt; extractor = mRTSPController.get();   
 
            /// rtsp 比较特殊, MediaExtractor对象的创建不需要DataSource   
            return setDataSource_l(extractor);   
        } else {   
            /// 本地文件   
            dataSource = DataSource::CreateFromURI(mUri.string(), &amp;amp;mUriHeaders);   
        }   
 
        /// 用dataSource创建一个MediaExtractor用于A/V分离        
        sp&amp;lt;mediaextractor&amp;gt; extractor = MediaExtractor::Create(dataSource);   
 
        return setDataSource_l(extractor);   
    }  
&amp;lt;/mediaextractor&amp;gt;&amp;lt;/mediaextractor&amp;gt;&amp;lt;/datasource&amp;gt;&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;②根据根据DataSource创建MediaExtractor&lt;/p&gt;  &lt;p&gt;先来看AwesomePlayer的构造函数,里面有一行代码.&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;AwesomePlayer::AwesomePlayer(){   
    //...   
    DataSource::RegisterDefaultSniffers();   
    //...   
}&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;RegisterDefaultSniffers注册了一些了媒体的MIME类型的探测函数.&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;void DataSource::RegisterDefaultSniffers() {   
    RegisterSniffer(SniffMPEG4);   
    RegisterSniffer(SniffMatroska);   
    RegisterSniffer(SniffOgg);   
    RegisterSniffer(SniffWAV);   
    RegisterSniffer(SniffAMR);   
    RegisterSniffer(SniffMPEG2TS);   
    RegisterSniffer(SniffMP3);   
}&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;这些探测用于判断媒体的MIME类型,进而决定要创建什么样的MediaExtractor.&lt;/p&gt;  &lt;p&gt;再回到 MediaExtractor::Create, MediaExtractor对象在这里创建.下面代码有点长,其实这段代码只是根据MIME类型创建Extractor的各个分支而已.&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;sp&amp;lt;mediaextractor&amp;gt; MediaExtractor::Create(
            const sp&amp;lt;datasource&amp;gt; &amp;amp;source, const char *mime) {
        sp&amp;lt;amessage&amp;gt; meta;
 
        String8 tmp;
        if (mime == NULL) {
            float confidence;
            if (!source-&amp;gt;sniff(&amp;amp;tmp, &amp;amp;confidence, &amp;amp;meta)) {
                LOGV(&amp;quot;FAILED to autodetect media content.&amp;quot;);
 
                return NULL;
            }
 
            mime = tmp.string();
            LOGV(&amp;quot;Autodetected media content as &amp;apos;%s&amp;apos; with confidence %.2f&amp;quot;,
                 mime, confidence);
        }
 
        bool isDrm = false;
        // DRM MIME type syntax is &amp;quot;drm+type+original&amp;quot; where
        // type is &amp;quot;es_based&amp;quot; or &amp;quot;container_based&amp;quot; and
        // original is the content&amp;apos;s cleartext MIME type
        if (!strncmp(mime, &amp;quot;drm+&amp;quot;, 4)) {
            const char *originalMime = strchr(mime+4, &amp;apos;+&amp;apos;);
            if (originalMime == NULL) {
                // second + not found
                return NULL;
            }
            ++originalMime;
            if (!strncmp(mime, &amp;quot;drm+es_based+&amp;quot;, 13)) {
                // DRMExtractor sets container metadata kKeyIsDRM to 1
                return new DRMExtractor(source, originalMime);
            } else if (!strncmp(mime, &amp;quot;drm+container_based+&amp;quot;, 20)) {
                mime = originalMime;
                isDrm = true;
            } else {
                return NULL;
            }
        }
 
        MediaExtractor *ret = NULL;
        if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG4)
                || !strcasecmp(mime, &amp;quot;audio/mp4&amp;quot;)) {
            ret = new MPEG4Extractor(source);
        } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_MPEG)) {
            ret = new MP3Extractor(source, meta);
        } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_NB)
                || !strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_WB)) {
            ret = new AMRExtractor(source);
        } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_FLAC)) {
            ret = new FLACExtractor(source);
        } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_WAV)) {
            ret = new WAVExtractor(source);
        } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_OGG)) {
            ret = new OggExtractor(source);
        } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MATROSKA)) {
            ret = new MatroskaExtractor(source);
        } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG2TS)) {
            ret = new MPEG2TSExtractor(source);
        } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_AVI)) {
            ret = new AVIExtractor(source);
        } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_WVM)) {
            ret = new WVMExtractor(source);
        } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AAC_ADTS)) {
            ret = new AACExtractor(source);
        }
 
        if (ret != NULL) {
           if (isDrm) {
               ret-&amp;gt;setDrmFlag(true);
           } else {
               ret-&amp;gt;setDrmFlag(false);
           }
        }
 
        return ret;
    }
&amp;lt;/amessage&amp;gt;&amp;lt;/datasource&amp;gt;&amp;lt;/mediaextractor&amp;gt;&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;③根据根据MediaExtractor做A/V分离&lt;/p&gt;  &lt;p&gt;再来看看setDataSource_l(const sp&amp;amp;extractor) ,这是A/V分离的最后步骤&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;status_t AwesomePlayer::setDataSource_l(const sp&amp;lt;mediaextractor&amp;gt; &amp;amp;extractor) {   
 
        /// 从全部的Track中选取一个Video Track和一个AudioTrack   
        for (size_t i = 0; i &amp;lt; extractor-&amp;gt;countTracks(); ++i) {   
            sp&amp;lt;metadata&amp;gt; meta = extractor-&amp;gt;getTrackMetaData(i);   
 
            const char *mime;   
            CHECK(meta-&amp;gt;findCString(kKeyMIMEType, &amp;amp;mime));   
 
            if (!haveVideo &amp;amp;&amp;amp; !strncasecmp(mime, &amp;quot;video/&amp;quot;, 6)) {   
                setVideoSource(extractor-&amp;gt;getTrack(i));   
                haveVideo = true;   
            } else if (!haveAudio &amp;amp;&amp;amp; !strncasecmp(mime, &amp;quot;audio/&amp;quot;, 6)) {   
                setAudioSource(extractor-&amp;gt;getTrack(i));   
                haveAudio = true;   
            }   
 
            if (haveAudio &amp;amp;&amp;amp; haveVideo) {   
                break;   
            }   
        }&lt;/code&gt;&lt;/pre&gt;  &lt;pre&gt;   &lt;code&gt;/// Flags 标志这个媒体的一些属性:   
        /// CAN_SEEK_BACKWARD 是否能后退10秒   
        /// CAN_SEEK_FORWARD 是否能前进10秒   
        /// CAN_SEEK 能否Seek?   
        /// CAN_PAUSE 能否暂停   
        mExtractorFlags = extractor-&amp;gt;flags();   
        return OK;   
    }&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;从这个函数可以看到MediaExtractor需要实现的基本比较重要的接口 (这个几个接口都是纯虚函数,可见Extractor的子类是一定要搞定它们的)&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;virtual size_t countTracks() = 0;&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;该媒体包含了几个Track?&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;virtual sp getTrack(size_t index) = 0;&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;获取指定的Video/Audio Track,可以看到一个Track本质上就是一个MediaSource&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;virtual sp getTrackMetaData ( size_t index,uint32_t flags = 0) = 0;&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;获取指定的Track的MetaData.在AwesomePlayer 中, MetaData实际上就是一块可以任意信息字段的叉烧, 字段类型可以是字符串或者是整形等等.这里Track的MetaData包含了Track的MIME类型.这样AwesomePlayer就可以知道这个Track是Video还是Audio的了   &lt;br /&gt;总结:至此,AwesomePlayer 就拥有VideoTrack和AudioTrack了 (可能只有VideoTrack或者只有AudioTrack,例如MP3).接下来 音视频解码器VideoSource/AudioSource 将从Video/Audio Track 中读取数据进行解码.&lt;/p&gt;  &lt;p&gt;④创建视频解码器&lt;/p&gt;  &lt;p&gt;VideoTrack/AudioTrack创建完毕之后, 紧接着就是创建 VideoSource了 (见 1.2.3).看看initVideoDecoder&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;status_t AwesomePlayer::initVideoDecoder(uint32_t flags) {   
    mVideoSource = OMXCodec::Create(   
            mClient.interface(), mVideoTrack-&amp;gt;getFormat(),   
            false, // createEncoder   
            mVideoTrack,   
            NULL, flags);   
    /// ...   
    return mVideoSource != NULL ? OK : UNKNOWN_ERROR;   
}&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;VideoSource是由OMXCodec::Create 创建的.从OMXCodec::Create的参数可以看出创建一个视频解码器需要什么材料:&lt;/p&gt;  &lt;p&gt;a. OMXClient.用于跟OMXIL 通讯.假如最后用的是OMXCodec 也不是SoftCodec的话,需要用到它.&lt;/p&gt;  &lt;p&gt;b. mVideoTrack-&amp;gt;getFormat (). getFormat返回包含该video track格式信息的MetaData.&lt;/p&gt;  &lt;p&gt;c. mVideoTrack，解码器会从 Video Track 中读取数据进行解码.&lt;/p&gt;  &lt;p&gt;⑤创建视频解码器的函数OMXCodec::Create&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;sp&amp;lt;mediasource&amp;gt; OMXCodec::Create(   
            const sp&amp;lt;iomx&amp;gt; &amp;amp;omx,   
            const sp&amp;lt;metadata&amp;gt; &amp;amp;meta, bool createEncoder,   
            const sp&amp;lt;mediasource&amp;gt; &amp;amp;source,   
            const char *matchComponentName,   
            uint32_t flags) {   
 
        /// 获取MIME类型   
        const char *mime;   
        bool success = meta-&amp;gt;findCString(kKeyMIMEType, &amp;amp;mime);   
 
        /// 根据MIME找出可能匹配的Codec   
        Vector&amp;lt;string8&amp;gt; matchingCodecs;   
        findMatchingCodecs(   
                mime, createEncoder, matchComponentName, flags, &amp;amp;matchingCodecs);   
 
        IOMX::node_id node = 0;   
 
        /// 对每一种可能匹配的Codec, 尝试申请Codec   
        const char *componentName;   
        for (size_t i = 0; i &amp;lt; matchingCodecs.size(); ++i) {   
            componentName = matchingCodecs[i].string();   
 
            /// 尝试申请软Codec   
            sp&amp;lt;mediasource&amp;gt; softwareCodec = createEncoder?   
                InstantiateSoftwareEncoder(componentName, source, meta):   
                InstantiateSoftwareCodec(componentName, source);   
 
            if (softwareCodec != NULL) {   
                return softwareCodec;   
            }   
 
            /// 尝试申请OMXCodec   
            status_t err = omx-&amp;gt;allocateNode(componentName, observer, &amp;amp;node);   
            if (err == OK) {   
                sp&amp;lt;omxcodec&amp;gt; codec = new OMXCodec(   
                        omx, node, quirks,   
                        createEncoder, mime, componentName,   
                        source);   
 
                /// 配置申请出来的OMXCodec   
                err = codec-&amp;gt;configureCodec(meta, flags);   
                if (err == OK) {   
                    return codec;   
                }   
            }   
        }   
 
        return NULL;   
    }  
&amp;lt;/omxcodec&amp;gt;&amp;lt;/mediasource&amp;gt;&amp;lt;/string8&amp;gt;&amp;lt;/mediasource&amp;gt;&amp;lt;/metadata&amp;gt;&amp;lt;/iomx&amp;gt;&amp;lt;/mediasource&amp;gt;&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;Create中OMXCodec::findMatchingCodecs函数找出可能匹配的Codec&lt;/p&gt;  &lt;p&gt;findMatchingCodecs根据传入的MIME 从kDecoderInfo中找出MIME对于的Codec名 (一种MIME可能对应多种Codec)&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;void OMXCodec::findMatchingCodecs(   
            const char *mime,   
            bool createEncoder, const char *matchComponentName,   
            uint32_t flags,   
            Vector&amp;lt;string8&amp;gt; *matchingCodecs) {   
 
        for (int index = 0;; ++index) {   
            const char *componentName;   
 
        componentName = GetCodec(   
                        kDecoderInfo,   
                        sizeof(kDecoderInfo) / sizeof(kDecoderInfo[0]),   
                        mime, index);   
 
 
            matchingCodecs-&amp;gt;push(String8(componentName));   
        }   
    }  
&amp;lt;/string8&amp;gt;&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;看看 kDecoderInfo里面包含了什么Codec吧,有点长.&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;static const CodecInfo kDecoderInfo[] = {   
    { MEDIA_MIMETYPE_IMAGE_JPEG, &amp;quot;OMX.TI.JPEG.decode&amp;quot; },   
//    { MEDIA_MIMETYPE_AUDIO_MPEG, &amp;quot;OMX.TI.MP3.decode&amp;quot; },   
    { MEDIA_MIMETYPE_AUDIO_MPEG, &amp;quot;MP3Decoder&amp;quot; },   
//    { MEDIA_MIMETYPE_AUDIO_MPEG, &amp;quot;OMX.PV.mp3dec&amp;quot; },   
//    { MEDIA_MIMETYPE_AUDIO_AMR_NB, &amp;quot;OMX.TI.AMR.decode&amp;quot; },   
    { MEDIA_MIMETYPE_AUDIO_AMR_NB, &amp;quot;AMRNBDecoder&amp;quot; },   
//    { MEDIA_MIMETYPE_AUDIO_AMR_NB, &amp;quot;OMX.PV.amrdec&amp;quot; },   
    { MEDIA_MIMETYPE_AUDIO_AMR_WB, &amp;quot;OMX.TI.WBAMR.decode&amp;quot; },   
    { MEDIA_MIMETYPE_AUDIO_AMR_WB, &amp;quot;AMRWBDecoder&amp;quot; },   
//    { MEDIA_MIMETYPE_AUDIO_AMR_WB, &amp;quot;OMX.PV.amrdec&amp;quot; },   
    { MEDIA_MIMETYPE_AUDIO_AAC, &amp;quot;OMX.TI.AAC.decode&amp;quot; },   
    { MEDIA_MIMETYPE_AUDIO_AAC, &amp;quot;AACDecoder&amp;quot; },   
//    { MEDIA_MIMETYPE_AUDIO_AAC, &amp;quot;OMX.PV.aacdec&amp;quot; },   
    { MEDIA_MIMETYPE_AUDIO_G711_ALAW, &amp;quot;G711Decoder&amp;quot; },   
    { MEDIA_MIMETYPE_AUDIO_G711_MLAW, &amp;quot;G711Decoder&amp;quot; },   
    { MEDIA_MIMETYPE_VIDEO_MPEG4, &amp;quot;OMX.qcom.7x30.video.decoder.mpeg4&amp;quot; },   
    { MEDIA_MIMETYPE_VIDEO_MPEG4, &amp;quot;OMX.qcom.video.decoder.mpeg4&amp;quot; },   
    { MEDIA_MIMETYPE_VIDEO_MPEG4, &amp;quot;OMX.TI.Video.Decoder&amp;quot; },   
    { MEDIA_MIMETYPE_VIDEO_MPEG4, &amp;quot;OMX.SEC.MPEG4.Decoder&amp;quot; },   
    { MEDIA_MIMETYPE_VIDEO_MPEG4, &amp;quot;OMX.TCC.mpeg4dec&amp;quot; },   
    { MEDIA_MIMETYPE_VIDEO_MPEG4, &amp;quot;M4vH263Decoder&amp;quot; },   
//    { MEDIA_MIMETYPE_VIDEO_MPEG4, &amp;quot;OMX.PV.mpeg4dec&amp;quot; },   
    { MEDIA_MIMETYPE_VIDEO_H263, &amp;quot;OMX.qcom.7x30.video.decoder.h263&amp;quot; },   
    { MEDIA_MIMETYPE_VIDEO_H263, &amp;quot;OMX.qcom.video.decoder.h263&amp;quot; },   
    { MEDIA_MIMETYPE_VIDEO_H263, &amp;quot;OMX.SEC.H263.Decoder&amp;quot; },   
    { MEDIA_MIMETYPE_VIDEO_H263, &amp;quot;OMX.TCC.h263dec&amp;quot; },   
    { MEDIA_MIMETYPE_VIDEO_H263, &amp;quot;M4vH263Decoder&amp;quot; },   
//    { MEDIA_MIMETYPE_VIDEO_H263, &amp;quot;OMX.PV.h263dec&amp;quot; },   
    { MEDIA_MIMETYPE_VIDEO_AVC, &amp;quot;OMX.qcom.7x30.video.decoder.avc&amp;quot; },   
    { MEDIA_MIMETYPE_VIDEO_AVC, &amp;quot;OMX.qcom.video.decoder.avc&amp;quot; },   
    { MEDIA_MIMETYPE_VIDEO_AVC, &amp;quot;OMX.TI.Video.Decoder&amp;quot; },   
    { MEDIA_MIMETYPE_VIDEO_AVC, &amp;quot;OMX.SEC.AVC.Decoder&amp;quot; },   
    { MEDIA_MIMETYPE_VIDEO_AVC, &amp;quot;OMX.TCC.avcdec&amp;quot; },   
    { MEDIA_MIMETYPE_VIDEO_AVC, &amp;quot;AVCDecoder&amp;quot; },   
//    { MEDIA_MIMETYPE_VIDEO_AVC, &amp;quot;OMX.PV.avcdec&amp;quot; },   
    { MEDIA_MIMETYPE_AUDIO_VORBIS, &amp;quot;VorbisDecoder&amp;quot; },   
    { MEDIA_MIMETYPE_VIDEO_VPX, &amp;quot;VPXDecoder&amp;quot; },   
 
    // TELECHIPS, SSG   
    { MEDIA_MIMETYPE_AUDIO_MPEG_TCC, &amp;quot;OMX.TCC.mp3dec&amp;quot; },   
    { MEDIA_MIMETYPE_AUDIO_AAC_TCC, &amp;quot;OMX.TCC.aacdec&amp;quot; },   
    { MEDIA_MIMETYPE_AUDIO_VORBIS_TCC, &amp;quot;OMX.TCC.vorbisdec&amp;quot; },   
    { MEDIA_MIMETYPE_AUDIO_WMA, &amp;quot;OMX.TCC.wmadec&amp;quot; },   
    { MEDIA_MIMETYPE_AUDIO_AC3, &amp;quot;OMX.TCC.ac3dec&amp;quot; },   
    { MEDIA_MIMETYPE_AUDIO_RA, &amp;quot;OMX.TCC.radec&amp;quot; },   
    { MEDIA_MIMETYPE_AUDIO_FLAC, &amp;quot;OMX.TCC.flacdec&amp;quot; },   
    { MEDIA_MIMETYPE_AUDIO_APE, &amp;quot;OMX.TCC.apedec&amp;quot; },   
    { MEDIA_MIMETYPE_AUDIO_MP2, &amp;quot;OMX.TCC.mp2dec&amp;quot; },   
    { MEDIA_MIMETYPE_AUDIO_PCM, &amp;quot;OMX.TCC.pcmdec&amp;quot; },   
    { MEDIA_MIMETYPE_AUDIO_DTS, &amp;quot;OMX.TCC.dtsdec&amp;quot; },   
 
    { MEDIA_MIMETYPE_VIDEO_VC1, &amp;quot;OMX.TCC.wmvdec&amp;quot; },   
    { MEDIA_MIMETYPE_VIDEO_WMV12, &amp;quot;OMX.TCC.wmv12dec&amp;quot; },   
    { MEDIA_MIMETYPE_VIDEO_RV, &amp;quot;OMX.TCC.rvdec&amp;quot; },   
    { MEDIA_MIMETYPE_VIDEO_DIVX, &amp;quot;OMX.TCC.divxdec&amp;quot; },   
    { MEDIA_MIMETYPE_VIDEO_MPEG2, &amp;quot;OMX.TCC.mpeg2dec&amp;quot; },   
    { MEDIA_MIMETYPE_VIDEO_MJPEG, &amp;quot;OMX.TCC.mjpegdec&amp;quot; },   
    { MEDIA_MIMETYPE_VIDEO_FLV1, &amp;quot;OMX.TCC.flv1dec&amp;quot; },   
};&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;可以看到MPEG4就对应了6种Codec.&lt;/p&gt;  &lt;p&gt;InstantiateSoftwareCodec创建软解码器&lt;/p&gt;  &lt;p&gt;InstantiateSoftwareCodec从 kFactoryInfo (软解码器列表)挑挑看有没有. 有的话就创建一个软解码器.看看kFactoryInfo里面有哪些软解码器&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;static const FactoryInfo kFactoryInfo[] = {   
    FACTORY_REF(MP3Decoder)   
    FACTORY_REF(AMRNBDecoder)   
    FACTORY_REF(AMRWBDecoder)   
    FACTORY_REF(AACDecoder)   
    FACTORY_REF(AVCDecoder)   
    FACTORY_REF(G711Decoder)   
    FACTORY_REF(M4vH263Decoder)   
    FACTORY_REF(VorbisDecoder)   
    FACTORY_REF(VPXDecoder)   
};&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;解码器名称的一点说明&lt;/p&gt;  &lt;p&gt;OMX.XXX.YYY  中间的XXX是厂商名称.如OMX.TI.Video.Decoder 就是TI芯片的硬视频解码器. 而 OMX.TCC.avcdec则是TCC的AVC视频解码器. 没有OMX开头的,说明是软解码器.&lt;/p&gt;  &lt;p&gt;以AVC为例:&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;{ MEDIA_MIMETYPE_VIDEO_AVC, &amp;quot;OMX.qcom.7x30.video.decoder.avc&amp;quot; },
{ MEDIA_MIMETYPE_VIDEO_AVC, &amp;quot;OMX.qcom.video.decoder.avc&amp;quot; },
{ MEDIA_MIMETYPE_VIDEO_AVC, &amp;quot;OMX.TI.Video.Decoder&amp;quot; },
{ MEDIA_MIMETYPE_VIDEO_AVC, &amp;quot;OMX.SEC.AVC.Decoder&amp;quot; },
{ MEDIA_MIMETYPE_VIDEO_AVC, &amp;quot;OMX.TCC.avcdec&amp;quot; },
{ MEDIA_MIMETYPE_VIDEO_AVC, &amp;quot;AVCDecoder&amp;quot; },&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;可以看到软解码器被放到最后.这样的话后面尝试申请解码器的时候便会优先申请硬Codec. 除非硬Codec不存在.&lt;/p&gt;  &lt;p&gt;创建OMXCodec&lt;/p&gt;  &lt;p&gt;申请OMXCodec比较简单,调用IOMX::allocateNode 申请即可.编解码器的名称例如 OMX.TCC.avcdec 即是OMX组件(Component)的名称&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;IOMX::node_id node = 0;
omx-&amp;gt;allocateNode(componentName, observer, &amp;amp;node);&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;这个时候就已经是和OMX IL 层进行通讯了,虽然是进程间通讯. 但是封装成这个样子,我们也看不出来了,和本地调用一样.&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;sp&amp;lt;OMXCodec&amp;gt; codec = new OMXCodec(
                    omx, node, quirks,
                    createEncoder, mime, componentName,
                    source);
codec-&amp;gt;configureCodec(meta, flags);              // codec 创建出来后, 要配置一下codec.&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;如果进去看看configureCodec的代码,可以看到实际上是调用 IOMX::setParameter, 和IOMX::setConfig. 同样,也是属于IPC,因为是和OMX IL 通讯.&lt;/p&gt;  &lt;p&gt;总结:理想的情况下, 调用OMXCodec::Create应该返回一个OMXCodec 对象而不是软解对象. Android默认的策略也是优先创建硬解码器. 至此AwesomePlayer通过OMXCodec 进而跟OML IL打交道. 其中关键的对象为IOMX和IOMX::node_id. node_id相当于一个OMX Component的句柄.音频解码器的创建过程跟视频解码器的创建过程几乎一样, 所以不分析了.&lt;/p&gt;  &lt;p&gt;⑥解封装, 解码&lt;/p&gt;  &lt;p&gt;看之前的我们知道，当VideoEvent 被触发时, AwesomePlayer::onVideoEvent会被调用. onVideoEvent 会尝试调用 mVideoSource.read读取视频图像,然后将视频图像交给AwesomeRenderer进行渲染.&lt;/p&gt;  &lt;p&gt;如果采用硬解码的话 mVideoSource实际是就是一个OMXCodec 对象.&lt;/p&gt;  &lt;p&gt;至此，一个AwesomePlayer播放的实现就完成了！&lt;/p&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>geek</category>
      <guid isPermaLink="true">https://itindex.net/detail/59709-awesomeplayer-%E5%88%86%E6%9E%90</guid>
      <pubDate>Tue, 18 Jun 2019 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>Nginx安全防范配置</title>
      <link>https://itindex.net/detail/59677-nginx-%E5%AE%89%E5%85%A8</link>
      <description>&lt;div&gt;  &lt;p&gt;隐藏版本号&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;http {
    server_tokens off;
}&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;经常会有针对某个版本的nginx安全漏洞出现，隐藏nginx版本号就成了主要的安全优化手段之一，当然最重要的是及时升级修复漏洞&lt;/p&gt;  &lt;p&gt;开启HTTPS&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;server {
    listen 443;
    server_name ops-coffee.cn;

    ssl on;
    ssl_certificate /etc/nginx/server.crt;
    ssl_certificate_key /etc/nginx/server.key;
    ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers         HIGH:!aNULL:!MD5;
}&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;ssl on： 开启https&lt;/p&gt;  &lt;p&gt;ssl_certificate： 配置nginx ssl证书的路径&lt;/p&gt;  &lt;p&gt;ssl_certificate_key： 配置nginx ssl证书key的路径&lt;/p&gt;  &lt;p&gt;ssl_protocols： 指定客户端建立连接时使用的ssl协议版本，如果不需要兼容TSLv1，直接去掉即可&lt;/p&gt;  &lt;p&gt;ssl_ciphers： 指定客户端连接时所使用的加密算法，你可以再这里配置更高安全的算法&lt;/p&gt;  &lt;p&gt;添加黑白名单&lt;/p&gt;  &lt;p&gt;白名单配置&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;location /admin/ {
    allow   192.168.1.0/24;
    deny    all;
}&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;上边表示只允许192.168.1.0/24网段的主机访问，拒绝其他所有&lt;/p&gt;  &lt;p&gt;也可以写成黑名单的方式禁止某些地址访问，允许其他所有，例如&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;location /ops-coffee/ {
    deny   192.168.1.0/24;
    allow    all;
}&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;更多的时候客户端请求会经过层层代理，我们需要通过$http_x_forwarded_for来进行限制，可以这样写&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;set $allow false;
if ($http_x_forwarded_for = &amp;quot;211.144.204.2&amp;quot;) { set $allow true; }
if ($http_x_forwarded_for ~ &amp;quot;108.2.66.[89]&amp;quot;) { set $allow true; }
if ($allow = false) { return 404; }&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;添加账号认证&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;server {
    location / {
        auth_basic &amp;quot;please input user&amp;amp;passwd&amp;quot;;
        auth_basic_user_file key/auth.key;
    }
}&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;限制请求方法&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;if ($request_method !~ ^(GET|POST)$ ) {
    return 405;
}&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;$request_method能够获取到请求nginx的method&lt;/p&gt;  &lt;p&gt;配置只允许GET\POST方法访问，其他的method返回405&lt;/p&gt;  &lt;p&gt;拒绝User-Agent&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;if ($http_user_agent ~* LWP::Simple|BBBike|wget|curl) {
    return 444;
}&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;可能有一些不法者会利用wget/curl等工具扫描我们的网站，我们可以通过禁止相应的user-agent来简单的防范&lt;/p&gt;  &lt;p&gt;Nginx的444状态比较特殊，如果返回444那么客户端将不会收到服务端返回的信息，就像是网站无法连接一样&lt;/p&gt;  &lt;p&gt;图片防盗链&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;location /images/ {
    valid_referers none blocked www.ops-coffee.cn ops-coffee.cn;
    if ($invalid_referer) {
        return  403;
    }
}&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;valid_referers： 验证referer，其中none允许referer为空，blocked允许不带协议的请求，除了以上两类外仅允许referer为www.ops-coffee.cn或ops-coffee.cn时访问images下的图片资源，否则返回403&lt;/p&gt;  &lt;p&gt;当然你也可以给不符合referer规则的请求重定向到一个默认的图片，比如下边这样&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;location /images/ {
    valid_referers blocked www.ops-coffee.cn ops-coffee.cn
    if ($invalid_referer) {
        rewrite ^/images/.*\.(gif|jpg|jpeg|png)$ /static/qrcode.jpg last;
    }
}&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;控制并发连接数&lt;/p&gt;  &lt;p&gt;可以通过ngx_http_limit_conn_module模块限制一个IP的并发连接数&lt;/p&gt;  &lt;p&gt;http {   &lt;br /&gt;limit_conn_zone $binary_remote_addr zone=ops:10m;&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;server {
    listen       80;
    server_name  ops-coffee.cn;

    root /home/project/webapp;
    index index.html;

    location / {
        limit_conn ops 10;
    }

    access_log  /tmp/nginx_access.log  main;
}&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;}   &lt;br /&gt;limit_conn_zone： 设定保存各个键(例如$binary_remote_addr)状态的共享内存空间的参数，zone=空间名字:大小&lt;/p&gt;  &lt;p&gt;大小的计算与变量有关，例如$binary_remote_addr变量的大小对于记录IPV4地址是固定的4 bytes，而记录IPV6地址时固定的16 bytes，存储状态在32位平台中占用32或者64 bytes，在64位平台中占用64 bytes。1m的共享内存空间可以保存大约3.2万个32位的状态，1.6万个64位的状态&lt;/p&gt;  &lt;p&gt;limit_conn： 指定一块已经设定的共享内存空间(例如name为ops的空间)，以及每个给定键值的最大连接数&lt;/p&gt;  &lt;p&gt;上边的例子表示同一IP同一时间只允许10个连接&lt;/p&gt;  &lt;p&gt;当有多个limit_conn指令被配置时，所有的连接数限制都会生效&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;http {
    limit_conn_zone $binary_remote_addr zone=ops:10m;
    limit_conn_zone $server_name zone=coffee:10m;

    server {
        listen       80;
        server_name  ops-coffee.cn;

        root /home/project/webapp;
        index index.html;

        location / {
            limit_conn ops 10;
            limit_conn coffee 2000;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;上边的配置不仅会限制单一IP来源的连接数为10，同时也会限制单一虚拟服务器的总连接数为2000&lt;/p&gt;  &lt;p&gt;缓冲区溢出攻击&lt;/p&gt;  &lt;p&gt;缓冲区溢出攻击 是通过将数据写入缓冲区并超出缓冲区边界和重写内存片段来实现的，限制缓冲区大小可有效防止&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;client_body_buffer_size  1K;
client_header_buffer_size 1k;
client_max_body_size 1k;
large_client_header_buffers 2 1k;&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;client_body_buffer_size： 默认8k或16k，表示客户端请求body占用缓冲区大小。如果连接请求超过缓存区指定的值，那么这些请求实体的整体或部分将尝试写入一个临时文件。&lt;/p&gt;  &lt;p&gt;client_header_buffer_size： 表示客户端请求头部的缓冲区大小。绝大多数情况下一个请求头不会大于1k，不过如果有来自于wap客户端的较大的cookie它可能会大于 1k，Nginx将分配给它一个更大的缓冲区，这个值可以在large_client_header_buffers里面设置&lt;/p&gt;  &lt;p&gt;client_max_body_size： 表示客户端请求的最大可接受body大小，它出现在请求头部的Content-Length字段， 如果请求大于指定的值，客户端将收到一个&amp;quot;Request Entity Too Large&amp;quot; (413)错误，通常在上传文件到服务器时会受到限制&lt;/p&gt;  &lt;p&gt;large_client_header_buffers 表示一些比较大的请求头使用的缓冲区数量和大小，默认一个缓冲区大小为操作系统中分页文件大小，通常是4k或8k，请求字段不能大于一个缓冲区大小，如果客户端发送一个比较大的头，nginx将返回&amp;quot;Request URI too large&amp;quot; (414)，请求的头部最长字段不能大于一个缓冲区，否则服务器将返回&amp;quot;Bad request&amp;quot; (400)&lt;/p&gt;  &lt;p&gt;同时需要修改几个超时时间的配置&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;client_body_timeout   10;
client_header_timeout 10;
keepalive_timeout     5 5;
send_timeout          10;&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;client_body_timeout： 表示读取请求body的超时时间，如果连接超过这个时间而客户端没有任何响应，Nginx将返回&amp;quot;Request time out&amp;quot; (408)错误&lt;/p&gt;  &lt;p&gt;client_header_timeout： 表示读取客户端请求头的超时时间，如果连接超过这个时间而客户端没有任何响应，Nginx将返回&amp;quot;Request time out&amp;quot; (408)错误&lt;/p&gt;  &lt;p&gt;keepalive_timeout： 参数的第一个值表示客户端与服务器长连接的超时时间，超过这个时间，服务器将关闭连接，可选的第二个参数参数表示Response头中Keep-Alive: timeout=time的time值，这个值可以使一些浏览器知道什么时候关闭连接，以便服务器不用重复关闭，如果不指定这个参数，nginx不会在应Response头中发送Keep-Alive信息&lt;/p&gt;  &lt;p&gt;send_timeout： 表示发送给客户端应答后的超时时间，Timeout是指没有进入完整established状态，只完成了两次握手，如果超过这个时间客户端没有任何响应，nginx将关闭连接&lt;/p&gt;  &lt;p&gt;Header头设置&lt;/p&gt;  &lt;p&gt;通过以下设置可有效防止XSS攻击&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;add_header X-Frame-Options &amp;quot;SAMEORIGIN&amp;quot;;
add_header X-XSS-Protection &amp;quot;1; mode=block&amp;quot;;
add_header X-Content-Type-Options &amp;quot;nosniff&amp;quot;;&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;X-Frame-Options： 响应头表示是否允许浏览器加载frame等属性，有三个配置DENY禁止任何网页被嵌入,SAMEORIGIN只允许本网站的嵌套,ALLOW-FROM允许指定地址的嵌套&lt;/p&gt;  &lt;p&gt;X-XSS-Protection： 表示启用XSS过滤（禁用过滤为X-XSS-Protection: 0），mode=block表示若检查到XSS攻击则停止渲染页面&lt;/p&gt;  &lt;p&gt;X-Content-Type-Options： 响应头用来指定浏览器对未指定或错误指定Content-Type资源真正类型的猜测行为，nosniff 表示不允许任何猜测&lt;/p&gt;  &lt;p&gt;在通常的请求响应中，浏览器会根据Content-Type来分辨响应的类型，但当响应类型未指定或错误指定时，浏览会尝试启用MIME-sniffing来猜测资源的响应类型，这是非常危险的&lt;/p&gt;  &lt;p&gt;例如一个.jpg的图片文件被恶意嵌入了可执行的js代码，在开启资源类型猜测的情况下，浏览器将执行嵌入的js代码，可能会有意想不到的后果&lt;/p&gt;  &lt;p&gt;另外还有几个关于请求头的安全配置需要注意&lt;/p&gt;  &lt;p&gt;Content-Security-Policy： 定义页面可以加载哪些资源，&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;add_header Content-Security-Policy &amp;quot;default-src &amp;apos;self&amp;apos;&amp;quot;;&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;上边的配置会限制所有的外部资源，都只能从当前域名加载，其中default-src定义针对所有类型资源的默认加载策略，self允许来自相同来源的内容&lt;/p&gt;  &lt;p&gt;Strict-Transport-Security： 会告诉浏览器用HTTPS协议代替HTTP来访问目标站点&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;add_header Strict-Transport-Security &amp;quot;max-age=31536000; includeSubDomains&amp;quot;;&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;上边的配置表示当用户第一次访问后，会返回一个包含了Strict-Transport-Security响应头的字段，这个字段会告诉浏览器，在接下来的31536000秒内，当前网站的所有请求都使用https协议访问，参数includeSubDomains是可选的，表示所有子域名也将采用同样的规则&lt;/p&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>geek</category>
      <guid isPermaLink="true">https://itindex.net/detail/59677-nginx-%E5%AE%89%E5%85%A8</guid>
      <pubDate>Wed, 12 Jun 2019 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>人工智能 | 自动驾驶与人工智能前沿研究报告（应用篇）</title>
      <link>https://itindex.net/detail/59531-%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD-%E8%87%AA%E5%8A%A8%E9%A9%BE%E9%A9%B6-%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD</link>
      <description>&lt;div&gt;  &lt;p&gt;博主github：   &lt;a href="https://github.com/MichaelBeechan" rel="nofollow"&gt;https://github.com/MichaelBeechan&lt;/a&gt;   &lt;br /&gt;博主CSDN：   &lt;a href="https://blog.csdn.net/u011344545" rel="nofollow"&gt;https://blog.csdn.net/u011344545&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;===========================================   &lt;br /&gt;概念篇：   &lt;a href="https://blog.csdn.net/u011344545/article/details/89432313" rel="nofollow"&gt;https://blog.csdn.net/u011344545/article/details/89432313&lt;/a&gt;   &lt;br /&gt;技术篇：   &lt;a href="https://blog.csdn.net/u011344545/article/details/89432433" rel="nofollow"&gt;https://blog.csdn.net/u011344545/article/details/89432433&lt;/a&gt;   &lt;br /&gt;人才篇：   &lt;a href="https://blog.csdn.net/u011344545/article/details/89432856" rel="nofollow"&gt;https://blog.csdn.net/u011344545/article/details/89432856&lt;/a&gt;   &lt;br /&gt;应用篇：   &lt;a href="https://blog.csdn.net/u011344545/article/details/89433021" rel="nofollow"&gt;https://blog.csdn.net/u011344545/article/details/89433021&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;===========================================   &lt;br /&gt;清华AMiner团队&lt;/p&gt;  &lt;h2&gt;   &lt;a&gt;&lt;/a&gt;   &lt;strong&gt;应用：&lt;/strong&gt;&lt;/h2&gt;  &lt;p&gt;业界普遍认为，自动驾驶技术在公共交通领域和特定场所的使用将早于在个人乘用车市场的普及。自动驾驶汽车将最先应用的行业包括公共交通、快递运输、服务于老年人和残疾人，如下图所示。   &lt;br /&gt;   &lt;img alt="&amp;#22312;&amp;#36825;&amp;#37324;&amp;#25554;&amp;#20837;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://img-blog.csdnimg.cn/20190421145436339.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTEzNDQ1NDU=,size_16,color_FFFFFF,t_70"&gt;&lt;/img&gt;   &lt;br /&gt;   &lt;strong&gt;（1）公共交通&lt;/strong&gt;   &lt;br /&gt;相比于小汽车，公共交通更能惠及普通群众，让民众感受到人工智能、自动驾驶带来的技术革新和便利，这也是该项技术最初的出发点。   &lt;br /&gt;自动驾驶巴士被认为是解决城市“最后一公里”难题的有效方案，大多用于机场、旅游景区和办公园区等封闭的场所。   &lt;br /&gt;   &lt;img alt="&amp;#22312;&amp;#36825;&amp;#37324;&amp;#25554;&amp;#20837;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://img-blog.csdnimg.cn/20190421145524361.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTEzNDQ1NDU=,size_16,color_FFFFFF,t_70"&gt;&lt;/img&gt;   &lt;br /&gt;自动驾驶汽车在公共交通领域的另一个重要应用是出租车。   &lt;br /&gt;   &lt;strong&gt;（2）快递运输&lt;/strong&gt;   &lt;br /&gt;快递用车和“列队”卡车将是另外一个较快采用自动驾驶汽车的领域。随着全球老龄化问题的加剧，自动驾驶技术在快递等行业的应用将极大地弥补劳动力不足的问题，并且随着自动驾驶技术的成熟与市场普及程度的提高，无人配送将成为必然的趋势。   &lt;br /&gt;   &lt;img alt="&amp;#22312;&amp;#36825;&amp;#37324;&amp;#25554;&amp;#20837;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://img-blog.csdnimg.cn/20190421145733552.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTEzNDQ1NDU=,size_16,color_FFFFFF,t_70"&gt;&lt;/img&gt;   &lt;br /&gt;   &lt;strong&gt;（3） 服务于老年人和残疾人&lt;/strong&gt;   &lt;br /&gt;自动驾驶汽车已经开始在老年人和残疾人这两个消费群体中有所应用。自动驾驶汽车不仅可增强老年人的移动能力，也能帮助残疾人旅行。   &lt;br /&gt;2012年，谷歌展示了自动驾驶技术的巨大潜力，谷歌员工让失去95%视力的Steve Mahan坐上谷歌自动驾驶车，体验其中的乐趣。随后，谷歌公司为他颁发了“首位无人汽车驾驶者”的称号。自动驾驶技术将使得残疾人变得更加独立。&lt;/p&gt;  &lt;h2&gt;   &lt;a&gt;&lt;/a&gt;   &lt;strong&gt;趋势&lt;/strong&gt;&lt;/h2&gt;  &lt;p&gt;   &lt;strong&gt;（1）未来&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;据《日本经济新闻》5 月 18 日报道，欧盟的欧洲委员会当地时间 5 月 17 日公布的自动驾驶时间进度表指出，计划到 2020 年在高速公路上实现自动驾驶，在城市中心区域实现低速自动驾驶；到 2030 年普及完全自动驾驶。为了助力自动驾驶，到 2022 年，欧盟所有新车都将具备通信功能，实现 100％“车联网”。委员会呼吁成员国和汽车企业制定确保安全和明确事故责任的通用规则。力争在国际规则出台之前制定出地区标准，在新一代产业领域掌握主导权。   &lt;br /&gt;此前，我国发布的《节能与新能源汽车技术路线图》指出，到 2020 年，驾驶辅助/部分自动驾驶车辆市场占有率将达50％；到2025年，高度自动驾驶车辆市场占有率将达约15％；到 2030 年，完全自动驾驶车辆市场占有率将近 10％。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;（2）自动驾驶汽车面临挑战&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;自动驾驶车辆事故表明，自动驾驶技术距离成熟应用还有一段漫长而艰辛的发展历程，这不是一蹴而就的。目前，国内自动驾驶技术取得了长足的进步，自动驾驶汽车也逐步得到了公众的认可，但是要实现自动驾驶汽车的普及化仍然需要很长的路要走。核心技术水平不高，关键零部件非国产化严重，政策法规需要逐步完善等问题依然需要不断努力。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;技术问题&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;目前自动驾驶技术在“人不多、规定车道、车速不快”的前提条件下确实可以实现，但是面对真正的复杂交通环境，自动驾驶汽车的安全性还需要更长时间来完善。如人类驾驶员能判断前方车辆的驾驶员是老司机还是新手，从而决定与前车保持多远的距离才合适，而自动驾驶技术要理解这些细微的预行为就显得很困难。   &lt;br /&gt;精确定位和导航是实现自动驾驶最为重要的部分之一，只有实现精确定位和导航的精细化才能保证自动驾驶的安全性与可靠性。目前，我国自主研制的北斗导航系统在性能上与美国的 GPS 相比还存在一定的差距。   &lt;br /&gt;高精度激光雷达、毫米波雷达等车载传感器作为自动驾驶汽车的眼睛，国内近几年虽有快速的进步，但与国外先进水平相比仍有相当的差距。自动驾驶技术要大规模普及，一方面有待在低成本、高性能的传感技术方面取得突破，还需要大幅提升自动驾驶的计算能力，而这两方面都是国内仍需努力的。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;法律&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;自动驾驶汽车要想合法上路行驶，首先要解决政策法规的问题。自动驾驶汽车实际行驶过程中难免会因为某些原因而产生事故，如何划分事故责任，如何做到公正裁决等这些都是要进行深入讨论与验证的问题。目前并没有哪些国家对自动驾驶汽车专门制定完善的政策法规，要想真正的实现自动驾驶汽车的普及化，这是一个必须解决并且需要格外小心的问题。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;挑战&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;自动驾驶的一个很重要的用途是用于某些特殊的环境下，由于在某些特殊的环境下，人员生存困难，自动驾驶能克服这些问题，但是其也要解决如极寒、道路条件复杂等各种极端环境的影响，这同样也是自动驾驶未来发展所应面临的困难。   &lt;br /&gt;由于人工智能的大量应用，自动驾驶技术更依赖于网络，如通过云端获取的高精地图、精准导航等的数据，其安全性显得尤为突出。如何打造安全可靠的数据链路，不被黑客侵扰等也将是需要长期面临的困难与挑战。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;参考文献&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;[1] SAE International.Automated Driving - Levels of Driving Automation are Defined in New   &lt;br /&gt;SAE International Standard J3016.   &lt;br /&gt;[2] 陈虹，郭露露，边宁.对汽车智能化进程及其关键技术的思考［J］.科技导报，2017，35   &lt;br /&gt;（11）：52-59.   &lt;br /&gt;[3] 陈慧岩，熊光明，龚建伟，等.无人驾驶汽车概论.北京：北京理工大学出版社，2014.   &lt;br /&gt;[4] 刘少山，唐洁，吴双，等.第一本无人驾驶技术书.北京：电子工业出版社，2017.   &lt;br /&gt;[5] 国家制造强国建设战略咨询委员会.《中国制造 2025》.2015.   &lt;br /&gt;[6] Wolfgang Bernhart，Marc WinTerhoff，Christopher Hoyes，etc.Autonomous Driving.Roland   &lt;br /&gt;Berger，2014，11.   &lt;br /&gt;[7] 班智飞，黄波.无人驾驶：在腾飞的前夜.中关村，2018，1.   &lt;br /&gt;[8] 乔维高，徐学进.无人驾驶汽车的发展现状及方向[J].上海汽车，2007(07).   &lt;br /&gt;[9] 晏欣炜，朱政泽，周奎，彭彬.人工智能在汽车自动驾驶系统中的应用分析.湖北汽车工   &lt;br /&gt;业学院学报.2018，3.   &lt;br /&gt;[10] 夏伟，李慧云.基于深度强化学习的自动驾驶策略学习方法.集成技术，2017，5.   &lt;br /&gt;[11] 国家发展改革委办公厅.智能汽车关键技术产业化实施方案.2017，12.   &lt;br /&gt;[12] 李克强，戴一凡，李升波，等.智能网联汽车（ICV）技术的发展现状及趋势[J].汽车安全   &lt;br /&gt;与节能学报，2017，8（1）：1-14.   &lt;br /&gt;[13] 丁毅.浅析无人驾驶汽车环境感知技术.数字技术与应用，2018，1.   &lt;br /&gt;[14] 贺汉根，孙振平，徐昕.智能交通条件下车辆自主驾驶技术展望.中国科学基金会，2016.   &lt;br /&gt;[15] 陈帅.无人驾驶汽车安全行驶的三大系统.中小企业管理与科技，2018，4.&lt;/p&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>geek</category>
      <guid isPermaLink="true">https://itindex.net/detail/59531-%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD-%E8%87%AA%E5%8A%A8%E9%A9%BE%E9%A9%B6-%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD</guid>
      <pubDate>Mon, 29 Apr 2019 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>阿里物联网平台的使用</title>
      <link>https://itindex.net/detail/59420-%E9%98%BF%E9%87%8C-%E7%89%A9%E8%81%94%E7%BD%91-%E5%B9%B3%E5%8F%B0</link>
      <description>&lt;div&gt;  &lt;p&gt;此处我们不使用具体的硬件设备来与物联网平台进行交互，我们可以使用MQTT.fx软件来模拟相关的硬件设备，让该设备与阿里物联网平台进行通信。&lt;/p&gt;  &lt;h3&gt;1.物联网平台创建设备&lt;/h3&gt;  &lt;p&gt;登录到阿里云物联网平台    &lt;a href="https://iot.console.aliyun.com/product" rel="nofollow"&gt;https://iot.console.aliyun.com/product&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;首先点击创建产品&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" height="207" src="https://img-blog.csdnimg.cn/20190325164739486.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p6NTMxOTg3NDY0,size_16,color_FFFFFF,t_70" width="900"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;这里我们选择基础版本，点击下一步&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" height="506" src="https://img-blog.csdnimg.cn/20190325165053307.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p6NTMxOTg3NDY0,size_16,color_FFFFFF,t_70" width="614"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;设置产品名称为ESP8266,其他默认设置，点击完成。接下来在该产品中添加设备&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" height="319" src="https://img-blog.csdnimg.cn/20190325170637810.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p6NTMxOTg3NDY0,size_16,color_FFFFFF,t_70" width="900"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;产品选择前面新建的ESP8266,这里DeviceName我这里定义为MQTT.fx_test，点击确认后弹出&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" height="341" src="https://img-blog.csdnimg.cn/20190325171008422.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p6NTMxOTg3NDY0,size_16,color_FFFFFF,t_70" width="598"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;我们点击一键复制，然后粘贴到notepad中。接下来我们在MQTT.fx配置中需要使用到相关字符串。&lt;/p&gt;  &lt;h3&gt;2.配置MQTT.fx软件&lt;/h3&gt;  &lt;p&gt;首先我们在网络上下载MQTT.fx的windows版本软件并默认安装好即可。打开软件界面如下：&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" height="305" src="https://img-blog.csdnimg.cn/20190325161145969.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p6NTMxOTg3NDY0,size_16,color_FFFFFF,t_70" width="831"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;我们点击齿轮按钮进入配置界面：&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" height="579" src="https://img-blog.csdnimg.cn/20190325162055511.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p6NTMxOTg3NDY0,size_16,color_FFFFFF,t_70" width="800"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;首先点击左下角的加号，添加一个命名为Aliyun_IOT的配置工程项，然后在该配置项中配置MQTT相关的一些参数。&lt;/p&gt;  &lt;p&gt;图中的参数配置主要可由如下的网址中生成：&lt;/p&gt;  &lt;p&gt;   &lt;a href="http://www.norra.cn:9091/?spm=a2c4e.11153940.blogcont625885.13.641064c5oG4krM" rel="nofollow"&gt;http://www.norra.cn:9091/?spm=a2c4e.11153940.blogcont625885.13.641064c5oG4krM&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;打开该网页，填写在前面从物联网平台中一键粘贴出来的相关字符串&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" height="262" src="https://img-blog.csdnimg.cn/20190325171537834.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p6NTMxOTg3NDY0,size_16,color_FFFFFF,t_70" width="724"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;点击计算Calculate后会生成相关的需要填入到MQTT.fx的配置项&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" height="567" src="https://img-blog.csdnimg.cn/20190325172025638.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p6NTMxOTg3NDY0,size_16,color_FFFFFF,t_70" width="719"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;将Domain Name的字符串填入到Broken Address&lt;/p&gt;  &lt;p&gt;将客户端编号ClientId的字符串填入到ClientID&lt;/p&gt;  &lt;p&gt;用户名Username的字符串填入到User Credentials中的user name中&lt;/p&gt;  &lt;p&gt;密码Password的字符串填入到 User Credentials中的Password中即可。&lt;/p&gt;  &lt;p&gt;最后点击apply，cancle。&lt;/p&gt;  &lt;h3&gt;3.测试MQTT.fx与阿里物联网平台之间的通信&lt;/h3&gt;  &lt;p&gt;回到MQTT.fx的软件主页面，选择Aliyun_IOT，点击Connect。然后我们在物联网平台上可以看到设备显示在线字样。&lt;/p&gt;  &lt;p&gt;接下来我们实验一下物联网平台与MQTT.fx两者之间的订阅与发布消息。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" height="288" src="https://img-blog.csdnimg.cn/20190325173413392.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p6NTMxOTg3NDY0,size_16,color_FFFFFF,t_70" width="900"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;h3&gt;3.1订阅消息&lt;/h3&gt;  &lt;p&gt;将Topic列表中的 /×××××××××××/MQTT.fx_test/get 复制到MQTT.fx中的Subscribe中，点击Subscribe。&lt;/p&gt;  &lt;p&gt;我们在/×××××××××××/MQTT.fx_test/get对应的操作点击发布消息，设置发送的消息，Qos为1&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" height="428" src="https://img-blog.csdnimg.cn/20190325173821899.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p6NTMxOTg3NDY0,size_16,color_FFFFFF,t_70" width="524"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;点击确认，我们在MQTT.fx中就会接收到订阅的消息&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" height="342" src="https://img-blog.csdnimg.cn/20190325174121107.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p6NTMxOTg3NDY0,size_16,color_FFFFFF,t_70" width="1011"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;h3&gt;3.2发布消息&lt;/h3&gt;  &lt;p&gt;同理将Topic列表中的/×××××××××/MQTT.fx_test/update 中拷贝到MQTT.fx中&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" height="265" src="https://img-blog.csdnimg.cn/20190325174903855.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p6NTMxOTg3NDY0,size_16,color_FFFFFF,t_70" width="850"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;点击Publish，发送数据到物联网平台中。打开物联网平台设备的日志服务&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" height="231" src="https://img-blog.csdnimg.cn/20190325175122203.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p6NTMxOTg3NDY0,size_16,color_FFFFFF,t_70" width="900"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;拷贝MessageID到消息内容查询，搜索可以看到MQTT.fx设备发送过来的消息   &lt;img alt="" height="182" src="https://img-blog.csdnimg.cn/20190325175318227.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p6NTMxOTg3NDY0,size_16,color_FFFFFF,t_70" width="900"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;如此说明设备MQTT.fx与物联网平台能够进行信息的交互。&lt;/p&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>geek</category>
      <guid isPermaLink="true">https://itindex.net/detail/59420-%E9%98%BF%E9%87%8C-%E7%89%A9%E8%81%94%E7%BD%91-%E5%B9%B3%E5%8F%B0</guid>
      <pubDate>Thu, 04 Apr 2019 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>AI在运维中的应用</title>
      <link>https://itindex.net/detail/59418-ai-%E8%BF%90%E7%BB%B4-%E5%BA%94%E7%94%A8</link>
      <description>&lt;div&gt;  &lt;h1&gt; &lt;/h1&gt;  &lt;p&gt;   &lt;strong&gt;摘&lt;/strong&gt;   &lt;strong&gt;&lt;/strong&gt;   &lt;strong&gt;要：&lt;/strong&gt;随着X86分布式技术应用，服务器数量越来越多，网络拓扑结构越来越复杂，运维越来越辛苦，风险越来越高。智能化运维AIOPS将AI技术应用在运维场景,是DevOps的运维部分，是“开发运维一体化云中心”的重要基础设施之一，其最大的价值在于缩短故障恢复时间，提高IT服务连续性。&lt;/p&gt;  &lt;p&gt;本文描述一个运维及在这个场景下对AI的需求，目标是尝试将AI引入运维过程，提高运维效率、缩短故障恢复时间。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;关键字：&lt;/strong&gt;机器学习；DEVOPS、AIOPS、流量预测&lt;/p&gt;  &lt;p&gt;随着X86分布式架构应用，服务器规模越来越大，一个交易经过的服务数量，一个请求的可能路径以笛卡尔乘积方式增加，一个节点异常往往会引起网络上多个服务器告警。这给故障定位、故障应急处理、系统瓶颈预测带来巨大的挑战。针对这种情况业内把人工智能引入到分布式系统运维管理中，以期通过人工智能提高运维效率，缩短故障恢复时间。业内称加入人工智能的运维为AIOPS。根据 Gartner Report，智能运维相关的技术产业处于上升期。2016 年，AIOPS 的部署率低于 5%，Gartner 预计 2019 年 AIOPS 的全球部署率可以达到 25%。随着人工智能的成熟，运维工程师将逐渐转型为大数据工程师，主要负责开发数据采集程序以及自动化执行脚本，负责搭建大数据基础架构，同时高效实现基于机器学习的算法。&lt;/p&gt;  &lt;p&gt;AIOPS代表结合人工智能的IT运维。它是指利用机器学习从各种IT运营工具和设备收集的大数据并训练模型，实时自动发现问题、分析问题、响应问题的多层技术平台。Gartner通过图1解释了AIOPS平台如何工作。AIOPS有两个主要组件：大数据和机器学习。为了将大数据平台中的参与数据（通常在票据、事件和事件记录中找到）与观测数据（如监控系统和作业日志中的观测数据）结合起来，AIOPS需要从传统IT数据中移除。 AIOPS针对合并的IT数据实施全面的分析和机器学习（ML）策略，找到故障模式和故障处理的关系，AIOPS可以被认为是核心IT功能的持续集成和部署（CI / CD）。现阶段网络性能管理的难点在于缺少业务视角，同时缺少覆盖全局和第三方的视图。Gartner提出了AIOps的概念，并预测到2020年，AIOps的采用率将会达到50%。（部分论述来自Gartner Report）&lt;/p&gt;  &lt;p&gt;&lt;/p&gt;  &lt;p&gt;   &lt;img alt="&amp;#22270;1&amp;#65288;&amp;#26469;&amp;#33258;gartner rreport&amp;#65289; &amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#22270;2" src="https://img-blog.csdnimg.cn/20190328094617104.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Z1ZGFsaWFuZzE5OTk=,size_16,color_FFFFFF,t_70"&gt;&lt;/img&gt;        &lt;/p&gt;  &lt;p&gt;从外部看，分为感知、分析和处置三部分.如图2所示，在AIOPS前端是传统的监控系统——称为感知部分，主要负责现实物理状态的获取，它是整个AIOPS的前提，只有准确、及时、全面的了解系统当前运行状况，后面的处理才可能实现。在AIOPS后端是IT运维管理系统、自动部署系统、通知告警系统——统称为处置类系统，这类系统根据AIOPS指令转换为操作具体服务器等IT设备的批量作业流，并通过执行作业流响应AIOPS指令。&lt;/p&gt;  &lt;p&gt;从内部看AIOPS内部主要是平台资源管理、大数据管理、机器学习和模型应用几个部分。平台资源管理主要是计算资源管理、存储资源管理、任务调度等，可以基于现有的云平台实现；大数据管理主要包括数据介入、管理ETL、数据检索等大数据平台功能；模型应用主要是运用训练好的模型，根据感知输入，决定处置动作。&lt;/p&gt;  &lt;p&gt;机器学习是AIOPS的核心，机器学习用来识别感知数据中的模式。虽然限制机器学习算法很健全，开源库也很多，截止目前真正在运维中使用AIOPS的才5%，清华大学裴丹教授总结说“智能运维落地的核心挑战是：从工业界的角度，我们有数据、有应用，但是缺乏一些算法和经验；从学术界的角度，我们有不少理论算法，但是缺乏实际的数据以支持科学研究，也不熟悉运维的场景。”基于这个思路，业内一部分同仁通过收集了很多的数据，数据的形式有KPI时间序列、日志等。假如打开首页的响应时间是我们的KPI，当首屏时间不理想、不满意时，我们希望能够找出哪些条件的组合导致了首屏时间不理想。这个方案有三个困难，第一打开首页设计的网络节点的日志必须全部收集，不仅仅是主机的，还包括相关网络设备等。第二，每个节点采集的指标要和“首页打开”这个动作相关，比如存储空间使用率监控指标对“首页打开”没有意义的，实际情况是做AI的人并不知道感知给他的数据是否决定了现在的现象，为了避免依赖不得不扩大指标采集范围，这不仅增加了成本，还会因为指标相关性引起模型训练困难。第三，机械学习需要很长实际的数据积累，这里不仅包括监控数据，还要包括故障数据——告诉机器什么情况是故障，这需要大量的数据标记工作。由于数据不足等原因，我了解的情况是自动化运维系统更多的是在预测、分析、告警信息筛选等方面，直接对服务器执行重启、扩容等动作的很少。&lt;/p&gt;  &lt;p&gt;当你成功在运维场景和人工智能之间找到结合点的时候，你会发现拓扑结构、系统架构、操作系统特性、中间件特性、数据库等都会对AIOPS的决策产生本质的影响。更为困窘的是市场变化导致IT服务的频繁变化，以实施敏捷、DevOps的团队为例，软件发布周期普遍在几天到几周之间，而这种变化要经过一定的时间、一定的数据积累才能反馈到模型中去。&lt;/p&gt;  &lt;p&gt;这让我想起裴丹教授说一般有“前景光明”、“前途光明”这些词的时候，下面跟着的就是“道路曲折”。实际上，智能运维是一个门槛很高的工作。实施AIOPS需要对银行系统、运维知识、机器学习都有深入的了解，才能取得成效。幸运的是，这三方面技能在中国银行软件中心都能找到。对银行系统的了解，首推总体部，总体部架构师队伍设计了中行系统，并且通过接口管理系统记录银行业务系统之间的调用关系；软件中心有专门的运维队伍，他们运维经验丰富，不仅熟悉系统状况，而且详细记录了各个产品部署关系；随着DevOps的实施，部署关系已经很好的管理起来，提供了精确的部署系统，而且为自动化处置提供了必要的条件。这些软件中心独特的有利条件，使我们不需要从零开始，而是结合决策树、知识图谱等已知的知识进行模型训练。主要工作思路如下：&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;建立运维知识图谱&lt;/li&gt;&lt;/ol&gt;  &lt;ol&gt;   &lt;li&gt;选图的节点： 服务/接口   进程   容器   VM  模块  产品。&lt;/li&gt;   &lt;li&gt;设置属性：给每个节点设置属性。比如CPU数量；历史TPS峰值等&lt;/li&gt;   &lt;li&gt;建立图中节点关系：首先建立静态关系，通过自动部署中的CMDB内容，建立节点/节点组中的关系。通过总体部接口管理文档建立调用关系图，此时已经标记了可能路径。静态关系有可以分为启动依赖关系；运行时依赖关系；分类关系（见扩展分类）。&lt;/li&gt;   &lt;li&gt;建立动态引用关系：通过监控发现的彼此之间的调用关系，这里主要指全路径跟踪发现的关系。&lt;/li&gt;   &lt;li&gt;路径学习：由于负载均衡、SOA等策略，一些路径可能不存在，但随时会建立。这部分需要机器学习自动识别补充&lt;/li&gt;&lt;/ol&gt;  &lt;ol&gt;   &lt;li&gt;选择典型场景应用AIOPS&lt;/li&gt;&lt;/ol&gt;  &lt;p&gt;AIOPS运维场景有十几种，由于篇幅所限，本次主要分享根本原因分析这个运维场景。在中国银行的IT系统中，完成一个业务交易需要经历多个产品、多个节点、多个进程、会调用多个接口。一旦交易链路上某个节点异常，会导致上游所有节点表现异常，此时监控系统上会表现为多个产品同时告警。理想状态下故障点及之前的节点交易堵塞，故障点之后的节点没有请求，此时只需将交易路径上的负载及情况可视化就能够快速定位问题。不过实际情况是一个接口往往被很多交易使用；一个节点上一般提供多个接口服务；负载均衡器等分流设备后有多个同类型节点。这些因素构成了一个复杂的IT服务网络，在复杂网络下，几笔交易异常，很难在监控系统上引起明显特征。这种场景适合应用人工智能，人工智能系统能够很好的捕捉这个特征，在故障发生且还没有引起大规模业务堵塞的时候通知处置系统采取动作。这不仅降低了运维人员应急处置压力，更为重要的是提升了系统连续性。基于运维知识图谱选取数据，训练模型可以有效降低模型训练时数据范围，去除服务网络上无关数据干扰，使模型训练更快速准确。在训练根本原因分析模型时，主要是找出交易异常发生位置与交易路径上各种监控指标的关系。&lt;/p&gt;  &lt;p&gt;大道至简，模型训练过程方法很好说清楚，但实际训练过程很复杂，如特征选择、降维、共线性等都需要非常专业的数据处理知识。笔者仅以此抛砖引玉，借助软件中心架构师队伍的知识理论降低人工智能在运维场景的应用难度。谬误之处，请批评指点！&lt;/p&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>geek</category>
      <guid isPermaLink="true">https://itindex.net/detail/59418-ai-%E8%BF%90%E7%BB%B4-%E5%BA%94%E7%94%A8</guid>
      <pubDate>Wed, 03 Apr 2019 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>人脸识别经典算法二：LBP方法</title>
      <link>https://itindex.net/detail/59397-%E4%BA%BA%E8%84%B8%E8%AF%86%E5%88%AB-%E7%BB%8F%E5%85%B8-%E7%AE%97%E6%B3%95</link>
      <description>&lt;div&gt;  &lt;p&gt;与   &lt;a href="http://blog.csdn.net/smartempire/article/details/21406005" rel="nofollow"&gt;第一篇博文特征脸方法&lt;/a&gt;不同，LBP（Local
 Binary Patterns，局部二值模式）是提取局部特征作为判别依据的。LBP方法显著的优点是对光照不敏感，但是依然没有解决姿态和表情的问题。不过相比于特征脸方法，LBP的识别率已经有了很大的提升。在[1]的文章里，有些人脸库的识别率已经达到了98%+。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;1、LBP特征提取&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;最初的LBP是定义在像素3x3邻域内的，以邻域中心像素为阈值，将相邻的8个像素的灰度值与其进行比较，若周围像素值大于中心像素值，则该像素点的位置被标记为1，否则为0。这样，3x3邻域内的8个点经比较可产生8位二进制数（通常转换为十进制数即LBP码，共256种），即得到该邻域中心像素点的LBP值，并用这个值来反映该区域的纹理信息。如下图所示：&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" src="https://img-blog.csdn.net/20140409100451171"&gt;&lt;/img&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;用比较正式的公式来定义的话：&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" src="https://img-blog.csdn.net/20140409102028328"&gt;&lt;/img&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;其中   &lt;img alt="" src="https://img-blog.csdn.net/20140409102051484"&gt;&lt;/img&gt;代表3x3邻域的中心元素，它的像素值为ic，ip代表邻域内其他像素的值。s(x)是符号函数，定义如下：   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" src="https://img-blog.csdn.net/20140409102057984"&gt;&lt;/img&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;LBP的改进版本&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;（1）圆形LBP算子&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;基本的 LBP算子的最大缺陷在于它只覆盖了一个固定半径范围内的小区域，这显然不能满足不同尺寸和频率纹理的需要。为了适应不同尺度的纹理特征，并达到灰度和旋转不变性的要求，Ojala等对 LBP 算子进行了改进，将 3×3邻域扩展到任意邻域，并用圆形邻域代替了正方形邻域，改进后的 LBP 算子允许在半径为 R 的圆形邻域内有任意多个像素点。从而得到了诸如半径为R的圆形区域内含有P个采样点的LBP算子。比如下图定了一个5x5的邻域：&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" src="https://img-blog.csdn.net/20140409101523156"&gt;&lt;/img&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;上图内有八个黑色的采样点，每个采样点的值可以通过下式计算：&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" src="https://img-blog.csdn.net/20140409102819171"&gt;&lt;/img&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;其中   &lt;img alt="" src="https://img-blog.csdn.net/20140409102904234"&gt;&lt;/img&gt;为邻域中心点，   &lt;img alt="" src="https://img-blog.csdn.net/20140409102917984"&gt;&lt;/img&gt;为某个采样点。通过上式可以计算任意个采样点的坐标，但是计算得到的坐标未必完全是整数，所以可以通过双线性插值来得到该采样点的像素值：&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" src="https://img-blog.csdn.net/20140409103242671"&gt;&lt;/img&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;（2）LBP等价模式&lt;/strong&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;&lt;/p&gt;  &lt;p&gt;一个LBP算子可以产生不同的二进制模式，对于半径为R的圆形区域内含有P个采样点的LBP算子将会产生2^P种模式。很显然，随着邻域集内采样点数的增加，二进制模式的种类是急剧增加的。例如：5×5邻域内20个采样点，有2   &lt;sup&gt;20&lt;/sup&gt;＝1,048,576种二进制模式。如此多的二值模式无论对于纹理的提取还是对于纹理的识别、分类及信息的存取都是不利的。同时，过多的模式种类对于纹理的表达是不利的。例如，将LBP算子用于纹理分类或人脸识别时，常采用LBP模式的统计直方图来表达图像的信息，而较多的模式种类将使得数据量过大，且直方图过于稀疏。因此，需要对原始的LBP模式进行降维，使得数据量减少的情况下能最好的代表图像的信息。&lt;/p&gt;  &lt;p&gt;        为了解决二进制模式过多的问题，提高统计性，Ojala提出了采用一种“等价模式”（Uniform Pattern）来对LBP算子的模式种类进行降维。Ojala等认为，在实际图像中，绝大多数LBP模式最多只包含两次从1到0或从0到1的跳变。因此，Ojala将“等价模式”定义为：当某个LBP所对应的循环二进制数从0到1或从1到0最多有两次跳变时，该LBP所对应的二进制就称为一个等价模式类。如00000000（0次跳变），00000111（只含一次从0到1的跳变），10001111（先由1跳到0，再由0跳到1，共两次跳变）都是等价模式类。除等价模式类以外的模式都归为另一类，称为混合模式类，例如10010111（共四次跳变）。比如下图给出了几种等价模式的示意图。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" src="https://img-blog.csdn.net/20140409104010500"&gt;&lt;/img&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;&lt;/p&gt;  &lt;p&gt;       通过这样的改进，二进制模式的种类大大减少，而不会丢失任何信息。模式数量由原来的2   &lt;sup&gt;P&lt;/sup&gt;种减少为 P ( P-1)+2种，其中P表示邻域集内的采样点数。对于3×3邻域内8个采样点来说，二进制模式由原始的256种减少为58种，这使得特征向量的维数更少，并且可以减少高频噪声带来的影响。这几段摘自[2]。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;通过上述方法，每个像素都会根据邻域信息得到一个LBP值，如果以图像的形式显示出来可以得到下图，明显LBP对光照有较强的鲁棒性。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" src="https://img-blog.csdn.net/20140409104518703"&gt;&lt;/img&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;2、LBP特征匹配&lt;/strong&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;如果将以上得到的LBP值直接用于人脸识别，其实和不提取LBP特征没什么区别，会造成计算量准确率等一系列问题。文献[1]中，将一副人脸图像分为7x7的子区域（如下图），并在子区域内根据LBP值统计其直方图，以直方图作为其判别特征。这样做的好处是在一定范围内避免图像没完全对准的情况，同时也对LBP特征做了降维处理。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" src="https://img-blog.csdn.net/20140409105549078"&gt;&lt;/img&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;对于得到的直方图特征，有多种方法可以判别其相似性，假设已知人脸直方图为M   &lt;sub&gt;i&lt;/sub&gt;​，待匹配人脸直方图为S   &lt;sub&gt;i&lt;/sub&gt;，那么可以通过:&lt;/p&gt;  &lt;p&gt;(1)直方图交叉核方法&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" src="https://img-blog.csdn.net/20140409110315812"&gt;&lt;/img&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;该方法的介绍在博文：   &lt;a href="http://blog.csdn.net/smartempire/article/details/23168945" rel="nofollow"&gt;Histogram
 intersection(直方图交叉核,Pyramid Match Kernel)&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;(2)卡方统计方法&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" src="https://img-blog.csdn.net/20140409110426046"&gt;&lt;/img&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;该方法的介绍在博文：   &lt;a href="http://blog.csdn.net/smartempire/article/details/23203183" rel="nofollow"&gt;卡方检验（Chi
 square statistic）&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;参考文献：&lt;/p&gt;  &lt;p&gt;[1]Timo Ahonen, Abdenour Hadid：Face Recognition with Local Binary Patterns&lt;/p&gt;  &lt;p&gt;[2]目标检测的图像特征提取之（二）LBP特征&lt;/p&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>geek</category>
      <guid isPermaLink="true">https://itindex.net/detail/59397-%E4%BA%BA%E8%84%B8%E8%AF%86%E5%88%AB-%E7%BB%8F%E5%85%B8-%E7%AE%97%E6%B3%95</guid>
      <pubDate>Tue, 26 Mar 2019 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>[OpenCV实战]5 基于深度学习的文本检测</title>
      <link>https://itindex.net/detail/59344-opencv-%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0-%E6%96%87%E6%9C%AC</link>
      <description>&lt;div&gt;  &lt;p&gt;   &lt;strong&gt;目录&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;   &lt;a href="http://itindex.net/relian#1%20%E7%BD%91%E7%BB%9C%E5%8A%A0%E8%BD%BD" rel="nofollow"&gt;1 网络加载&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;   &lt;a href="http://itindex.net/relian#2%20%E8%AF%BB%E5%8F%96%E5%9B%BE%E5%83%8F" rel="nofollow"&gt;2 读取图像&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;   &lt;a href="http://itindex.net/relian#3%20%E5%89%8D%E5%90%91%E4%BC%A0%E6%92%AD" rel="nofollow"&gt;3 前向传播&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;   &lt;a href="http://itindex.net/relian#4%20%E5%A4%84%E7%90%86%E8%BE%93%E5%87%BA" rel="nofollow"&gt;4 处理输出&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;   &lt;a href="http://itindex.net/relian#3%E7%BB%93%E6%9E%9C%E5%92%8C%E4%BB%A3%E7%A0%81" rel="nofollow"&gt;3结果和代码&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;   &lt;a href="http://itindex.net/relian#3.1%E7%BB%93%E6%9E%9C" rel="nofollow"&gt;3.1结果&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;   &lt;a href="http://itindex.net/relian#3.2%20%E4%BB%A3%E7%A0%81" rel="nofollow"&gt;3.2 代码&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;   &lt;a href="http://itindex.net/relian#%E5%8F%82%E8%80%83" rel="nofollow"&gt;参考&lt;/a&gt;&lt;/p&gt;  &lt;hr&gt;&lt;/hr&gt;  &lt;p&gt;在这篇文章中，我们将逐字逐句地尝试找到图片中的单词！基于最近的一篇论文进行文字检测。&lt;/p&gt;  &lt;p&gt;EAST: An Efficient and Accurate Scene Text Detector.&lt;/p&gt;  &lt;p&gt;   &lt;u&gt;    &lt;a href="https://arxiv.org/abs/1704.03155v2" rel="nofollow"&gt;https://arxiv.org/abs/1704.03155v2&lt;/a&gt;&lt;/u&gt;&lt;/p&gt;  &lt;p&gt;   &lt;u&gt;    &lt;a href="https://github.com/argman/EAST" rel="nofollow"&gt;https://github.com/argman/EAST&lt;/a&gt;&lt;/u&gt;&lt;/p&gt;  &lt;p&gt;应该注意，文本检测不同于文本识别。在文本检测中，我们只检测文本周围的边界框。但是，在文本识别中，我们实际上找到了框中所写的内容。例如，在下面给出的图像中，文本检测将为您提供单词周围的边界框，文本识别将告诉您该框包含单词STOP。本文只进行文本检测。&lt;/p&gt;  &lt;p&gt;本文基于tensorflow模型，基于OpenCV调用tensorflow模型。我们将逐步讨论算法是如何工作的。您将需要OpenCV3.4.3以上版本来运行代码。其他opencv DNN模型读取也类似这样步骤。&lt;/p&gt;  &lt;p&gt;涉及的步骤如下：&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;下载EAST模型&lt;/li&gt;   &lt;li&gt;将模型加载到内存中&lt;/li&gt;   &lt;li&gt;准备输入图像&lt;/li&gt;   &lt;li&gt;正向传递blob通过网络&lt;/li&gt;   &lt;li&gt;处理输出&lt;/li&gt;&lt;/ol&gt;  &lt;h1&gt;   &lt;strong&gt;1&lt;/strong&gt;   &lt;strong&gt;网络加载&lt;/strong&gt;&lt;/h1&gt;  &lt;p&gt;我们将使用cv :: dnn :: readnet(C++版本)或cv2.dnn.ReadNet(python版本)函数将网络加载到内存中。它会根据指定的文件名自动检测配置和框架。在我们的例子中，它是一个pb文件，因此，它将假定要加载Tensorflow网络。和加载图像不大一样，没有模型结构描述文件。&lt;/p&gt;  &lt;p&gt;C++&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;Net net = readNet(model);&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;Python&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;net = cv.dnn.readNet(model)&lt;/code&gt;&lt;/pre&gt;  &lt;h1&gt;   &lt;strong&gt;2&lt;/strong&gt;   &lt;strong&gt;读取图像&lt;/strong&gt;&lt;/h1&gt;  &lt;p&gt;我们需要创建一个4-D输入blob，用于将图像输送到网络。这是使用blobFromImage函数完成的。&lt;/p&gt;  &lt;p&gt;C++&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;blobFromImage(frame, blob, 1.0, Size(inpWidth, inpHeight), Scalar(123.68, 116.78, 103.94), true, false);&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;Python&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;blob = cv.dnn.blobFromImage(frame, 1.0, (inpWidth, inpHeight), (123.68, 116.78, 103.94), True, False)&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;我们需要为此函数指定一些参数。它们如下：&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;第一个参数是图像本身。&lt;/li&gt;   &lt;li&gt;第二个参数指定每个像素值的缩放。在这种情况下，它不是必需的。因此我们将其保持为1。&lt;/li&gt;   &lt;li&gt;第三个参数是设定网络的默认输入为320×320。因此，我们需要在创建blob时指定它。最好和网络输入一致。&lt;/li&gt;   &lt;li&gt;第四个参数是训练时候设定的模型均值。需要减去模型均值。&lt;/li&gt;   &lt;li&gt;第五个参数是我们是否要交换R和B通道。这是必需的，因为OpenCV使用BGR格式，Tensorflow使用RGB格式，caffe模型使用BGR格式。&lt;/li&gt;   &lt;li&gt;最后一个参数是我们是否要裁剪图像并采取中心裁剪。在这种情况下我们指定False。&lt;/li&gt;&lt;/ol&gt;  &lt;h1&gt;   &lt;strong&gt;3&lt;/strong&gt;   &lt;strong&gt;前向传播&lt;/strong&gt;&lt;/h1&gt;  &lt;p&gt;现在我们已准备好输入，我们将通过网络传递它。网络有两个输出。一个指定文本框的位置，另一个指定检测到的框的置信度分数。两个输出层如下：&lt;/p&gt;  &lt;p&gt;feature_fusion/concat_3&lt;/p&gt;  &lt;p&gt;feature_fusion/Conv_7/Sigmoid&lt;/p&gt;  &lt;p&gt;这两个输出可以直接用netron这个软件打开pb模型，看到最后输出结果。Netron是一个模型结构可视化神器，支持tf, caffe, keras,mxnet等多种框架。Netron下载地址：&lt;/p&gt;  &lt;p&gt;   &lt;u&gt;    &lt;a href="https://github.com/lutzroeder/Netron" rel="nofollow"&gt;https://github.com/lutzroeder/Netron&lt;/a&gt;&lt;/u&gt;&lt;/p&gt;  &lt;p&gt;c++读取输出代码如下：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;std::vector&amp;lt;String&amp;gt; outputLayers(2);

outputLayers[0] = &amp;quot;feature_fusion/Conv_7/Sigmoid&amp;quot;;

outputLayers[1] = &amp;quot;feature_fusion/concat_3&amp;quot;;&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;python读取输出代码如下：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;outputLayers = []

outputLayers.append(&amp;quot;feature_fusion/Conv_7/Sigmoid&amp;quot;)

outputLayers.append(&amp;quot;feature_fusion/concat_3&amp;quot;)&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;接下来，我们通过将输入图像传递到网络来获得输出。如前所述，输出由两部分组成：置信度和位置。&lt;/p&gt;  &lt;p&gt;C++&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;std::vector&amp;lt;Mat&amp;gt; output;

net.setInput(blob);

net.forward(output, outputLayers);

Mat scores = output[0];

Mat geometry = output[1];&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;python:&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;net.setInput(blob)

output = net.forward(outputLayers)

scores = output[0]

geometry = output[1]&lt;/code&gt;&lt;/pre&gt;  &lt;h1&gt;   &lt;strong&gt;4&lt;/strong&gt;   &lt;strong&gt;处理输出&lt;/strong&gt;&lt;/h1&gt;  &lt;p&gt;如前所述，我们将使用两个层的输出并解码文本框的位置及其方向。我们可能会得到许多文本框。因此，我们需要从该批次中筛选出看起来最好的文本框。这是使用非极大值抑制算法完成的。&lt;/p&gt;  &lt;p&gt;非极大值抑制算法在目标检测中应用很广泛，具体可以参考&lt;/p&gt;  &lt;p&gt;   &lt;u&gt;http://www.it610.com/article/5215825.htm&lt;/u&gt;&lt;/p&gt;  &lt;p&gt;   &lt;a href="https://blog.csdn.net/qq_14845119/article/details/52064928" rel="nofollow"&gt;    &lt;u&gt;https://blog.csdn.net/qq_14845119/article/details/5206492&lt;/u&gt;8&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;1 解码&lt;/p&gt;  &lt;p&gt;C++:&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;std::vector&amp;lt;RotatedRect&amp;gt; boxes;

std::vector&amp;lt;float&amp;gt; confidences;

decode(scores, geometry, confThreshold, boxes, confidences);&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;python:&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;[boxes, confidences] = decode(scores, geometry, confThreshold)&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;2 非极大值抑制&lt;/p&gt;  &lt;p&gt;我们使用OpenCV函数NMSBoxes（C ++）或NMSBoxesRotated（Python）来过滤掉误报并获得最终预测。&lt;/p&gt;  &lt;p&gt;C++:&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;std::vector&amp;lt;int&amp;gt; indices;

NMSBoxes(boxes, confidences, confThreshold, nmsThreshold, indices);&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;Python:&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;indices = cv.dnn.NMSBoxesRotated(boxes, confidences, confThreshold, nmsThreshold)&lt;/code&gt;&lt;/pre&gt;  &lt;h1&gt;3结果和代码&lt;/h1&gt;  &lt;h2&gt;3.1结果&lt;/h2&gt;  &lt;p&gt;在VS2017下运行了C++代码，其中OpenCV版本至少要3.4.5以上。不然模型读取会有问题。模型文件太大，见下载链接：&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;如果没有积分（系统自动设定资源分数）看看参考链接。我搬运过来的，大修改没有。&lt;/p&gt;  &lt;p&gt;或者梯子直接下载模型：&lt;/p&gt;  &lt;p&gt;   &lt;u&gt;    &lt;a href="https://www.dropbox.com/s/r2ingd0l3zt8hxs/frozen_east_text_detection.tar.gz?dl=1" rel="nofollow"&gt;https://www.dropbox.com/s/r2ingd0l3zt8hxs/frozen_east_text_detection.tar.gz?dl=1&lt;/a&gt;&lt;/u&gt;&lt;/p&gt;  &lt;p&gt;结果如下，效果还不错，速度也还好。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="https://www.learnopencv.com/wp-content/uploads/2019/01/east-out-stop2.jpg" height="370" src="https://www.learnopencv.com/wp-content/uploads/2019/01/east-out-stop2.jpg" width="554"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;   &lt;img alt="https://www.learnopencv.com/wp-content/uploads/2019/01/east-out-stop1.jpg" height="369" src="https://www.learnopencv.com/wp-content/uploads/2019/01/east-out-stop1.jpg" width="554"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;   &lt;img alt="https://www.learnopencv.com/wp-content/uploads/2019/01/east-out-cards.jpg" height="415" src="https://www.learnopencv.com/wp-content/uploads/2019/01/east-out-cards.jpg" width="554"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;h2&gt;3.2 代码&lt;/h2&gt;  &lt;p&gt;C++代码有所更改，python没有。对文本检测不熟悉，注释较少。&lt;/p&gt;  &lt;p&gt;C++代码：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;// text_detection.cpp : 此文件包含 &amp;quot;main&amp;quot; 函数。程序执行将在此处开始并结束。
//

#include &amp;quot;pch.h&amp;quot;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;opencv2/opencv.hpp&amp;gt;

using namespace std;
using namespace cv;
using namespace cv::dnn;

//解码
void decode(const Mat &amp;amp;scores, const Mat &amp;amp;geometry, float scoreThresh,
	std::vector&amp;lt;RotatedRect&amp;gt; &amp;amp;detections, std::vector&amp;lt;float&amp;gt; &amp;amp;confidences);

/**
 * @brief
 *
 * @param srcImg 检测图像
 * @param inpWidth 深度学习图像输入宽
 * @param inpHeight 深度学习图像输入高
 * @param confThreshold 置信度
 * @param nmsThreshold 非极大值抑制算法阈值
 * @param net
 * @return Mat
 */
Mat text_detect(Mat srcImg, int inpWidth, int inpHeight, float confThreshold, float nmsThreshold, Net net)
{
	//输出
	std::vector&amp;lt;Mat&amp;gt; output;
	std::vector&amp;lt;String&amp;gt; outputLayers(2);
	outputLayers[0] = &amp;quot;feature_fusion/Conv_7/Sigmoid&amp;quot;;
	outputLayers[1] = &amp;quot;feature_fusion/concat_3&amp;quot;;

	//检测图像
	Mat frame, blob;
	frame = srcImg.clone();
	//获取深度学习模型的输入
	blobFromImage(frame, blob, 1.0, Size(inpWidth, inpHeight), Scalar(123.68, 116.78, 103.94), true, false);
	net.setInput(blob);
	//输出结果
	net.forward(output, outputLayers);

	//置信度
	Mat scores = output[0];
	//位置参数
	Mat geometry = output[1];

	// Decode predicted bounding boxes， 对检测框进行解码，获取文本框位置方向
	//文本框位置参数
	std::vector&amp;lt;RotatedRect&amp;gt; boxes;
	//文本框置信度
	std::vector&amp;lt;float&amp;gt; confidences;
	decode(scores, geometry, confThreshold, boxes, confidences);

	// Apply non-maximum suppression procedure， 应用非极大性抑制算法
	//符合要求的文本框
	std::vector&amp;lt;int&amp;gt; indices;
	NMSBoxes(boxes, confidences, confThreshold, nmsThreshold, indices);

	// Render detections. 输出预测
	//缩放比例
	Point2f ratio((float)frame.cols / inpWidth, (float)frame.rows / inpHeight);
	for (size_t i = 0; i &amp;lt; indices.size(); ++i)
	{
		RotatedRect &amp;amp;box = boxes[indices[i]];

		Point2f vertices[4];
		box.points(vertices);
		//还原坐标点
		for (int j = 0; j &amp;lt; 4; ++j)
		{
			vertices[j].x *= ratio.x;
			vertices[j].y *= ratio.y;
		}
		//画框
		for (int j = 0; j &amp;lt; 4; ++j)
		{
			line(frame, vertices[j], vertices[(j + 1) % 4], Scalar(0, 255, 0), 2, LINE_AA);
		}
	}

	// Put efficiency information. 时间
	std::vector&amp;lt;double&amp;gt; layersTimes;
	double freq = getTickFrequency() / 1000;
	double t = net.getPerfProfile(layersTimes) / freq;
	std::string label = format(&amp;quot;Inference time: %.2f ms&amp;quot;, t);
	putText(frame, label, Point(0, 15), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 255, 0));

	return frame;
}

//模型地址
auto model = &amp;quot;./model/frozen_east_text_detection.pb&amp;quot;;
//检测图像
auto detect_image = &amp;quot;./image/patient.jpg&amp;quot;;
//输入框尺寸
auto inpWidth = 320;
auto inpHeight = 320;
//置信度阈值
auto confThreshold = 0.5;
//非极大值抑制算法阈值
auto nmsThreshold = 0.4;

int main()
{
	//读取模型
	Net net = readNet(model);
	//读取检测图像
	Mat srcImg = imread(detect_image);
	if (!srcImg.empty())
	{
		cout &amp;lt;&amp;lt; &amp;quot;read image success!&amp;quot; &amp;lt;&amp;lt; endl;
	}
	Mat resultImg = text_detect(srcImg, inpWidth, inpHeight, confThreshold, nmsThreshold, net);
	imshow(&amp;quot;result&amp;quot;, resultImg);
	waitKey();
	return 0;
}

/**
 * @brief 输出检测到的文本框相关信息
 *
 * @param scores 置信度
 * @param geometry 位置信息
 * @param scoreThresh 置信度阈值
 * @param detections 位置
 * @param confidences 分类概率
 */
void decode(const Mat &amp;amp;scores, const Mat &amp;amp;geometry, float scoreThresh,
	std::vector&amp;lt;RotatedRect&amp;gt; &amp;amp;detections, std::vector&amp;lt;float&amp;gt; &amp;amp;confidences)
{
	detections.clear();
	//判断是不是符合提取要求
	CV_Assert(scores.dims == 4);
	CV_Assert(geometry.dims == 4);
	CV_Assert(scores.size[0] == 1);
	CV_Assert(geometry.size[0] == 1);
	CV_Assert(scores.size[1] == 1);
	CV_Assert(geometry.size[1] == 5);
	CV_Assert(scores.size[2] == geometry.size[2]);
	CV_Assert(scores.size[3] == geometry.size[3]);

	const int height = scores.size[2];
	const int width = scores.size[3];
	for (int y = 0; y &amp;lt; height; ++y)
	{
		//识别概率
		const float *scoresData = scores.ptr&amp;lt;float&amp;gt;(0, 0, y);
		//文本框坐标
		const float *x0_data = geometry.ptr&amp;lt;float&amp;gt;(0, 0, y);
		const float *x1_data = geometry.ptr&amp;lt;float&amp;gt;(0, 1, y);
		const float *x2_data = geometry.ptr&amp;lt;float&amp;gt;(0, 2, y);
		const float *x3_data = geometry.ptr&amp;lt;float&amp;gt;(0, 3, y);
		//文本框角度
		const float *anglesData = geometry.ptr&amp;lt;float&amp;gt;(0, 4, y);
		//遍历所有检测到的检测框
		for (int x = 0; x &amp;lt; width; ++x)
		{
			float score = scoresData[x];
			//低于阈值忽略该检测框
			if (score &amp;lt; scoreThresh)
			{
				continue;
			}

			// Decode a prediction.
			// Multiple by 4 because feature maps are 4 time less than input image.
			float offsetX = x * 4.0f, offsetY = y * 4.0f;
			//角度及相关正余弦计算
			float angle = anglesData[x];
			float cosA = std::cos(angle);
			float sinA = std::sin(angle);
			float h = x0_data[x] + x2_data[x];
			float w = x1_data[x] + x3_data[x];

			Point2f offset(offsetX + cosA * x1_data[x] + sinA * x2_data[x],
				offsetY - sinA * x1_data[x] + cosA * x2_data[x]);
			Point2f p1 = Point2f(-sinA * h, -cosA * h) + offset;
			Point2f p3 = Point2f(-cosA * w, sinA * w) + offset;
			//旋转矩形，分别输入中心点坐标，图像宽高，角度
			RotatedRect r(0.5f * (p1 + p3), Size2f(w, h), -angle * 180.0f / (float)CV_PI);
			//保存检测框
			detections.push_back(r);
			//保存检测框的置信度
			confidences.push_back(score);
		}
	}
}&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;Python代码：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;# Import required modules
import cv2 as cv
import math
import argparse

parser = argparse.ArgumentParser(description=&amp;apos;Use this script to run text detection deep learning networks using OpenCV.&amp;apos;)
# Input argument
parser.add_argument(&amp;apos;--input&amp;apos;, help=&amp;apos;Path to input image or video file. Skip this argument to capture frames from a camera.&amp;apos;)
# Model argument
parser.add_argument(&amp;apos;--model&amp;apos;, default=&amp;quot;./model/frozen_east_text_detection.pb&amp;quot;,
                    help=&amp;apos;Path to a binary .pb file of model contains trained weights.&amp;apos;
                    )
# Width argument
parser.add_argument(&amp;apos;--width&amp;apos;, type=int, default=320,
                    help=&amp;apos;Preprocess input image by resizing to a specific width. It should be multiple by 32.&amp;apos;
                   )
# Height argument
parser.add_argument(&amp;apos;--height&amp;apos;,type=int, default=320,
                    help=&amp;apos;Preprocess input image by resizing to a specific height. It should be multiple by 32.&amp;apos;
                   )
# Confidence threshold
parser.add_argument(&amp;apos;--thr&amp;apos;,type=float, default=0.5,
                    help=&amp;apos;Confidence threshold.&amp;apos;
                   )
# Non-maximum suppression threshold
parser.add_argument(&amp;apos;--nms&amp;apos;,type=float, default=0.4,
                    help=&amp;apos;Non-maximum suppression threshold.&amp;apos;
                   )

args = parser.parse_args()


############ Utility functions ############
def decode(scores, geometry, scoreThresh):
    detections = []
    confidences = []

    ############ CHECK DIMENSIONS AND SHAPES OF geometry AND scores ############
    assert len(scores.shape) == 4, &amp;quot;Incorrect dimensions of scores&amp;quot;
    assert len(geometry.shape) == 4, &amp;quot;Incorrect dimensions of geometry&amp;quot;
    assert scores.shape[0] == 1, &amp;quot;Invalid dimensions of scores&amp;quot;
    assert geometry.shape[0] == 1, &amp;quot;Invalid dimensions of geometry&amp;quot;
    assert scores.shape[1] == 1, &amp;quot;Invalid dimensions of scores&amp;quot;
    assert geometry.shape[1] == 5, &amp;quot;Invalid dimensions of geometry&amp;quot;
    assert scores.shape[2] == geometry.shape[2], &amp;quot;Invalid dimensions of scores and geometry&amp;quot;
    assert scores.shape[3] == geometry.shape[3], &amp;quot;Invalid dimensions of scores and geometry&amp;quot;
    height = scores.shape[2]
    width = scores.shape[3]
    for y in range(0, height):

        # Extract data from scores
        scoresData = scores[0][0][y]
        x0_data = geometry[0][0][y]
        x1_data = geometry[0][1][y]
        x2_data = geometry[0][2][y]
        x3_data = geometry[0][3][y]
        anglesData = geometry[0][4][y]
        for x in range(0, width):
            score = scoresData[x]

            # If score is lower than threshold score, move to next x
            if(score &amp;lt; scoreThresh):
                continue

            # Calculate offset
            offsetX = x * 4.0
            offsetY = y * 4.0
            angle = anglesData[x]

            # Calculate cos and sin of angle
            cosA = math.cos(angle)
            sinA = math.sin(angle)
            h = x0_data[x] + x2_data[x]
            w = x1_data[x] + x3_data[x]

            # Calculate offset
            offset = ([offsetX + cosA * x1_data[x] + sinA * x2_data[x], offsetY - sinA * x1_data[x] + cosA * x2_data[x]])

            # Find points for rectangle
            p1 = (-sinA * h + offset[0], -cosA * h + offset[1])
            p3 = (-cosA * w + offset[0],  sinA * w + offset[1])
            center = (0.5*(p1[0]+p3[0]), 0.5*(p1[1]+p3[1]))
            detections.append((center, (w,h), -1*angle * 180.0 / math.pi))
            confidences.append(float(score))

    # Return detections and confidences
    return [detections, confidences]

if __name__ == &amp;quot;__main__&amp;quot;:
    # Read and store arguments
    confThreshold = args.thr
    nmsThreshold = args.nms
    inpWidth = args.width
    inpHeight = args.height
    model = args.model

    # Load network
    net = cv.dnn.readNet(model)

    # Create a new named window
    kWinName = &amp;quot;EAST: An Efficient and Accurate Scene Text Detector&amp;quot;
    outputLayers = []
    outputLayers.append(&amp;quot;feature_fusion/Conv_7/Sigmoid&amp;quot;)
    outputLayers.append(&amp;quot;feature_fusion/concat_3&amp;quot;)

    # Read frame
    frame = cv.imread(&amp;quot;./image/stop1.jpg&amp;quot;)


    # Get frame height and width
    height_ = frame.shape[0]
    width_ = frame.shape[1]
    rW = width_ / float(inpWidth)
    rH = height_ / float(inpHeight)

    # Create a 4D blob from frame.
    blob = cv.dnn.blobFromImage(frame, 1.0, (inpWidth, inpHeight), (123.68, 116.78, 103.94), True, False)

    # Run the model
    net.setInput(blob)
    output = net.forward(outputLayers)
    t, _ = net.getPerfProfile()
    label = &amp;apos;Inference time: %.2f ms&amp;apos; % (t * 1000.0 / cv.getTickFrequency())

    # Get scores and geometry
    scores = output[0]
    geometry = output[1]
    [boxes, confidences] = decode(scores, geometry, confThreshold)
    # Apply NMS
    indices = cv.dnn.NMSBoxesRotated(boxes, confidences, confThreshold,nmsThreshold)
    for i in indices:
        # get 4 corners of the rotated rect
        vertices = cv.boxPoints(boxes[i[0]])
        # scale the bounding box coordinates based on the respective ratios
        for j in range(4):
            vertices[j][0] *= rW
            vertices[j][1] *= rH
        for j in range(4):
            p1 = (vertices[j][0], vertices[j][1])
            p2 = (vertices[(j + 1) % 4][0], vertices[(j + 1) % 4][1])
            cv.line(frame, p1, p2, (0, 255, 0), 2, cv.LINE_AA);
            # cv.putText(frame, &amp;quot;{:.3f}&amp;quot;.format(confidences[i[0]]), (vertices[0][0], vertices[0][1]), cv.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 1, cv.LINE_AA)

    # Put efficiency information
    cv.putText(frame, label, (0, 15), cv.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255))

    # Display the frame
    cv.imshow(&amp;quot;result&amp;quot;,frame)
    cv.waitKey(0)&lt;/code&gt;&lt;/pre&gt;  &lt;h1&gt;参考&lt;/h1&gt;  &lt;p&gt;   &lt;u&gt;    &lt;a href="https://www.learnopencv.com/deep-learning-based-text-detection-using-opencv-c-python/" rel="nofollow"&gt;https://www.learnopencv.com/deep-learning-based-text-detection-using-opencv-c-python/&lt;/a&gt;&lt;/u&gt;&lt;/p&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>geek</category>
      <guid isPermaLink="true">https://itindex.net/detail/59344-opencv-%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0-%E6%96%87%E6%9C%AC</guid>
      <pubDate>Thu, 07 Mar 2019 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>深度残差网络ResNet初探</title>
      <link>https://itindex.net/detail/59338-%E6%B7%B1%E5%BA%A6-%E7%BD%91%E7%BB%9C-resnet</link>
      <description>&lt;div&gt;  &lt;p&gt;微软亚洲研究院 Kaiming He 博士在 2015 年凭借深度残差网络 Deep Residual Network (DRN) 在 Imagenet 比赛的识别、检测和定位三个任务、以及 COCO 比赛的检测和分割任务上都获得了冠军。论文《   &lt;a href="https://arxiv.org/abs/1512.03385" rel="nofollow"&gt;Deep Residual Learning for Image Recognition&lt;/a&gt;》获得 2016 CVPR best paper，ResNet因此声名大噪，很大程度上引发了 deep network 的革命。&lt;/p&gt;  &lt;p&gt;   &lt;a&gt;&lt;/a&gt;&lt;/p&gt;  &lt;h2&gt;   &lt;strong&gt;问题提出&lt;/strong&gt;&lt;/h2&gt;  &lt;p&gt;　　现有的深度学习思想可能认为深层的网络一般会比浅层的网络效果好，如果要进一步地提升模型的准确率，最直接的方法就是把网络设计得越深越好，这样模型的准确率也就会越来越准确。例如在图像处理任务中，CNN 能够提取 low / mid / high-level 的特征，网络的层数越多，意味着能够提取到不同 level 的特征越丰富。越深的网络提取的特征越抽象，越具有语义信息。   &lt;br /&gt;　　Kaiming 博士在论文中做了这样一组实验：在 CIFAR-10 数据集上分别训练了一个 20 层和 56 层的 plain network (卷积、池化、全连接构成的传统 CNN )，发现 56 层网络的训练误差和测试误差都大于 20 层网络的训练误差，即网络层数加深时，模型效果却越来越差，在训练集上的准确率甚至下降了，因此这个显然不是由于 overfitting 导致的，因为 overfitting 应该表现为在训练集上效果更好才对。   &lt;br /&gt;   &lt;img alt="1" src="http://changingfond.oss-cn-hangzhou.aliyuncs.com/18-7-13/43257140.jpg"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;h2&gt;   &lt;strong&gt;分析思考&lt;/strong&gt;&lt;/h2&gt;  &lt;p&gt;1.为什么不能直接简单地增加层数？   &lt;br /&gt;　　神经网络的深度加深，一个众所周知的问题就是梯度的消失和爆炸 (gradients vanishing / gradients exploding)，它会导致深层的网络参数得不到有效的校正信号或使得训练难以收敛，通过正则化初始化或者中间的正则化层 (Batch Normalization) 方法可以得到有效的缓解，但并不能解决这里提出的问题。&lt;/p&gt;  &lt;p&gt;2.为什么网络层数加深时，网络的性能反而下降？   &lt;br /&gt;　　我们假设现在有一个浅层 (假设层数为 n) 的神经网络plain network A ，具有比较理想的输出结果，现在在这个神经网络的后边再加 m 层得到一个新的神经网络 B，我们发现输出结果的准确度反而下降了。这是不合理的，因为如果后边加上的那 m 层是对前 n 层的输出结果做恒等映射 (identity mapping)，至少 B 也能和 A 的性能持平才对。但是实验的结果表明现在的求解方法并不能得到理想的结果，这说明 B 网络在学习恒等映射的时候出了问题，也就是传统网络 (plain networks) 很难去学习恒等映射，这就是所谓的退化 (degradation) 现象。&lt;/p&gt;  &lt;h2&gt;   &lt;strong&gt;核心思想&lt;/strong&gt;&lt;/h2&gt;  &lt;p&gt;　　如果深层网络的后面那些层是恒等映射，那么模型就退化为一个浅层网络，现在要解决的就是如何学习恒等映射函数。但是直接让一些层去拟合一个潜在的恒等映射函数 H(x) = x 是很困难的，但是如果使用残差函数 H(x) = F(x) + x，F(x) = H(x) - x，如果能使 F(x) = 0，H(x) 就是恒等映射。&lt;/p&gt;  &lt;p&gt;　　网络输入是 x，网络的输出是 F(x)，网络要拟合的目标是 H(x)，传统网络的训练目标是 F(x) = H(x)。&lt;/p&gt;  &lt;p&gt;　　残差网络，则是把传统网络的输出 F(x) 处理一下，加上输入 x，变成 F(x) + x 作为最终的输出，训练目标是 F(x) = H(x) - x。&lt;/p&gt;  &lt;p&gt;　　现在我们要训练一个深层的网络，它可能过深，假设存在一个性能最强的完美网络 N，与它相比我们的网络中必定有一些层是多余的，那么这些多余的层的训练目标是恒等变换，只有达到这个目标我们的网络性能才能跟 N 一样。对于这些需要实现恒等变换的多余的层，要拟合的目标就成了 H(x) = x，在传统网络中，网络的输出目标是 F(x) = x，这比较困难，而在残差网络中，拟合的目标成了 x - x = 0，网络的输出目标为 F(x) = 0，这比前者要容易得多。&lt;/p&gt;  &lt;p&gt;　　这里的 F(x) + x 为什么是 x 而不是其他值？因为多余的层的目标是恒等变换，即 F(x) + x = x，那 F(x) 的训练目标就是 0，比较容易。如果是其他，比如 x/2 ，那 F(x) 的训练目标就是 x/2，是一个非 0 的值，比 0 难实现。Kaiming 博士的另一篇文章[2]中探讨了这个问题，对6种结构的残差结构进行实验比较证明 F(x) 加上输入值 x 的效果最好。&lt;/p&gt;  &lt;h3&gt;   &lt;strong&gt;Residual Block&lt;/strong&gt;&lt;/h3&gt;  &lt;p&gt;   &lt;img alt="resnet" src="http://changingfond.oss-cn-hangzhou.aliyuncs.com/18-7-13/19251915.jpg"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;在上图的残差网络结构图中，通过“shortcut connections (捷径连接)”的方式，直接把输入x传到输出作为初始结果，输出结果为 H(x) = F(x) + x，当 F(x) = 0 时，那么 H(x) = x，也就是上面所提到的恒等映射。于是，ResNet相当于将学习目标改变了，不再是学习一个完整的输出，而是目标值H(X)和x的差值，即所谓的残差F(x) = H(x) - x，因此，后面的训练目标就是要将残差结果逼近于 0，使得随着网络加深，准确率不下降。&lt;/p&gt;  &lt;p&gt;它有二层，如下表达式，其中 σσ 代表非线性函数ReLU：&lt;/p&gt;  &lt;p&gt;F=W2σ(W1x)F=W2σ(W1x)&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;然后通过一个 shortcut connection，和第 2 个 ReLU，获得输出 y：&lt;/p&gt;  &lt;p&gt;y=F(x,Wi)+x.y=F(x,Wi)+x.&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;F(x) 与 x 相加就是逐元素相加，但是如果两者维度不同，需要给 x 执行一个线性变换来匹配维度，如下式：&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;y=F(x,Wi)+Wsx.y=F(x,Wi)+Wsx.&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;实验证明，这个残差块往往需要两层以上，单单一层的残差块 y=W1x+xy=W1x+x 并不能起到提升作用。&lt;/p&gt;  &lt;p&gt;这种残差跳跃式的结构，打破了传统的神经网络 n - 1 层的输出只能给 n 层作为输入的惯例，使某一层的输出可以直接跨过几层作为后面某一层的输入，其意义在于为叠加多层网络而使得整个学习模型的错误率不降反升的难题提供了新的方向 (后来的 DenseNet)。至此，神经网络的层数可以超越之前的约束，达到几十层、上百层甚至千层，为高级语义特征提取和分类提供了可行性。&lt;/p&gt;  &lt;h3&gt;   &lt;strong&gt;Model Structure&lt;/strong&gt;&lt;/h3&gt;  &lt;p&gt;　　作者由 VGG19 设计出了 plain network 和 Resnet-34，如下图中部和右侧网络。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" src="http://changingfond.oss-cn-hangzhou.aliyuncs.com/18-7-13/54395655.jpg"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;对于输出 feature map 大小相同的层，有相同数量的 filters，即 channel 数相同；&lt;/li&gt;   &lt;li&gt;当 feature map 大小减半时（pooling），filters数量翻倍。&lt;/li&gt;   &lt;li&gt;对于残差网络，维度匹配的shortcut连接为实线，反之为虚线。维度不匹配时，同等映射有两种可选方案：    &lt;ul&gt;     &lt;li&gt;直接通过 zero padding 来增加维度（channel）。&lt;/li&gt;     &lt;li&gt;乘以 W 矩阵投影到新的空间。实现是用 1 x 1 卷积实现的，直接改变 1 x 1 卷积的 filters 数目。这种会增加参数。&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ol&gt;  &lt;p&gt;　　下图是Resnet对应于ImageNet的结构框架。中括号中为残差块的参数，多个残差块进行堆叠。下采样由 stride 为 2 的 conv3_1、conv4_1 和 conv5_1 来实现。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" src="http://changingfond.oss-cn-hangzhou.aliyuncs.com/18-7-13/99454670.jpg"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;h3&gt;   &lt;strong&gt;Bottle neck&lt;/strong&gt;&lt;/h3&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;   &lt;img alt="" src="http://changingfond.oss-cn-hangzhou.aliyuncs.com/18-7-13/91444611.jpg"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;　　考虑到时间花费和降低参数的数目，将原来的 Residual Block (残差学习结构) 改为 Bottleneck 结构，如上图。首端和末端的 1 x 1 卷积用来削减和恢复维度，相比于原本结构，只有中间 3 x 3 成为瓶颈部分。两种结构分别针对 ResNet-34 （左图）和 ResNet-50/ 101 / 152（右图）。&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;　　左图是两个 3 x 3 x 256的卷积，参数数目: 3 x 3 x 256 x 256 x 2 = 1179648；右图是第一个 1 x 1 的卷积把 256 维通道降到 64 维，然后在最后通过 1 x 1 卷积恢复，整体上用的参数数目：1 x 1 x 256 x 64 + 3 x 3 x 64 x 64 + 1 x 1 x 64 x 256 = 69632，右图的参数量比左图减少了 16.94 倍。对于常规的ResNet，可以用于34层或者更少的网络中（左图），对于更深的网络（如50 / 101 / 152层），则使用右图，其目的是减少计算和参数量。&lt;/p&gt;  &lt;h2&gt;   &lt;strong&gt;TensorFlow实现&lt;/strong&gt;&lt;/h2&gt;  &lt;ol&gt;   &lt;li&gt;    &lt;a href="https://github.com/KaimingHe/deep-residual-networks" rel="nofollow"&gt;KaimingHe/deep-residual-networks&lt;/a&gt;&lt;/li&gt;   &lt;li&gt;    &lt;a href="https://github.com/wenxinxu/resnet-in-tensorflow" rel="nofollow"&gt;wenxinxu/resnet-in-tensorflow&lt;/a&gt;&lt;/li&gt;   &lt;li&gt;    &lt;a href="https://github.com/tensorpack/tensorpack/tree/master/examples/ResNet" rel="nofollow"&gt;tensorpack/examples/ResNet&lt;/a&gt;&lt;/li&gt;   &lt;li&gt;    &lt;a href="https://github.com/ry/tensorflow-resnet" rel="nofollow"&gt;ry/tensorflow-resnet&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;  &lt;h2&gt;   &lt;strong&gt;Python示例&lt;/strong&gt;&lt;/h2&gt;  &lt;table cellspacing="0"&gt;   &lt;tr&gt;    &lt;td&gt;     &lt;pre&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33&lt;/pre&gt;&lt;/td&gt;    &lt;td&gt;     &lt;pre&gt;defresidual_block(x, out_channels, down_sample, projection=False):in_channels = x.get_shape().as_list()[3]ifdown_sample:
        x = max_pool(x)

    output = conv2d_with_batch_norm(x, [3,3, in_channels, out_channels],1)
    output = conv2d_with_batch_norm(output, [3,3, out_channels, out_channels],1)ifin_channels != out_channels:ifprojection:# projection shortcutinput_ = conv2d(x, [1,1, in_channels, out_channels],2)else:# zero-paddinginput_ = tf.pad(x, [[0,0], [0,0], [0,0], [0, out_channels - in_channels]])else:

        input_ = xreturnoutput + input_defresidual_group(name,x,num_block,out_channels):assertnum_block&amp;gt;=1,&amp;apos;num_block must greater than 1&amp;apos;withtf.variable_scope(&amp;apos;%s_head&amp;apos;%name):
        output = residual_block(x, out_channels,True)foriinrange (num_block-1):withtf.variable_scope(&amp;apos;%s_%d&amp;apos;% (name,i+1)):
            output = residual_block(output,out_channels,False)returnoutput&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>geek</category>
      <guid isPermaLink="true">https://itindex.net/detail/59338-%E6%B7%B1%E5%BA%A6-%E7%BD%91%E7%BB%9C-resnet</guid>
      <pubDate>Tue, 05 Mar 2019 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>Arduino开发板使用NRF24L01进行无线通信</title>
      <link>https://itindex.net/detail/59149-arduino-%E5%BC%80%E5%8F%91-nrf24l01</link>
      <description>&lt;div&gt;  &lt;p&gt;在本篇文章中，我们将学习如何使用NRF24L01收发器模块在两个Arduino开发板之间进行无线通信。为了说明无线通信，我们将举两个例子，第一个是从一个Arduino开发板向另一个发送简单的“Hello World”消息，在第二个例子中，我们将在Arduino开发板之间进行双向通信，其中我们使用在第一个Arduino开发板的操纵杆，控制在第二个Arduino开发板的伺服电机，反之亦然，使用第二个Arduino开发板的按钮，我们将控制第一个Arduino开发板的LED灯。&lt;/p&gt;  &lt;h2&gt;   &lt;a&gt;&lt;/a&gt;NRF24L01收发器模块&lt;/h2&gt;  &lt;p&gt;让我们来看看NRF24L01收发器模块。它使用2.4 GHz频段，可以在250 kbps到2 Mbps的波特率下运行。如果在开放空间中使用且波特率较低，其范围可达100米。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="&amp;#22312;&amp;#36825;&amp;#37324;&amp;#25554;&amp;#20837;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://www.yiboard.com/data/attachment/forum/201811/11/210533e62uokhrwuudn2ui.png"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;该模块可以使用125个不同的通道，可以在一个地方拥有125个独立工作的调制解调器网络。每个通道最多可以有6个地址，或者每个单元可以同时与多达6个其他单元通信。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="&amp;#22312;&amp;#36825;&amp;#37324;&amp;#25554;&amp;#20837;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://www.yiboard.com/data/attachment/forum/201811/11/210501c3p2pluvq42vppb2.jpg"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;在传输过程中，该模块的功耗仅为12mA左右，甚至低于单个LED。该模块的工作电压范围为1.9至3.6V，但好处是其他引脚可以容忍5V逻辑，因此我们可以轻松地将其连接到Arduino而无需使用任何逻辑电平转换器。&lt;/p&gt;  &lt;p&gt;更多内容请参考以下链接：   &lt;a href="https://www.yiboard.com/thread-994-1-1.html" rel="nofollow"&gt;https://www.yiboard.com/thread-994-1-1.html&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>geek</category>
      <guid isPermaLink="true">https://itindex.net/detail/59149-arduino-%E5%BC%80%E5%8F%91-nrf24l01</guid>
      <pubDate>Sat, 29 Dec 2018 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>Facebook开源移动端深度学习加速框架，比TensorFlow Lite快一倍</title>
      <link>https://itindex.net/detail/59148-facebook-%E5%BC%80%E6%BA%90-%E7%A7%BB%E5%8A%A8</link>
      <description>&lt;div&gt;  &lt;h5&gt;方栗子 发自 凹非寺   &lt;br /&gt;量子位 出品 | 公众号 QbitAI&lt;/h5&gt;  &lt;p&gt;   &lt;img alt="640?wx_fmt=png" src="https://ss.csdn.net/p?https://mmbiz.qpic.cn/mmbiz_png/YicUhk5aAGtDJHn5uYxdtRfjBQkg0AqzDcswQ4Go3g55CJETjEsqoxViaLO1GnpQ8NQ9SIjr7MHJBW2KKdgiabgWw/640?wx_fmt=png"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;Facebook发布了一个开源框架，叫   &lt;strong&gt;QNNPACK&lt;/strong&gt;，是手机端神经网络计算的加速包。&lt;/p&gt;  &lt;p&gt;官方表示，它可以   &lt;strong&gt;成倍提升&lt;/strong&gt;神经网络的推理效率，几乎比   &lt;strong&gt;TensorFlow Lite&lt;/strong&gt;快一倍。&lt;/p&gt;  &lt;p&gt;这个框架，能够为很多运算加速，比如   &lt;strong&gt;DW卷积&lt;/strong&gt; (Depthwise Convolution) ，许多先进的架构里面都用得到。&lt;/p&gt;  &lt;p&gt;目前，QNNPACK已经是   &lt;strong&gt;PyTorch 1.0&lt;/strong&gt;的一部分，在Caffe2里就能直接使用。&lt;/p&gt;  &lt;p&gt;其实，Facebook手机应用里面，已经部署了这个QNNPACK。也就是说，数以亿计的手机都在用了。&lt;/p&gt;  &lt;h1&gt;从哪个角度加速？&lt;/h1&gt;  &lt;p&gt;QNNPACK，这个名字眼熟么？&lt;/p&gt;  &lt;p&gt;两年前，Facebook就推出过一个加速包，叫做   &lt;strong&gt;NNPACK&lt;/strong&gt;，Caffe2Go用的就是它。&lt;/p&gt;  &lt;p&gt;基于Winograd变换和傅里叶变换，有效减少卷积计算里的乘加运算 (   &lt;strong&gt;Multiply-Add&lt;/strong&gt;) 。这样一来，3x3卷积可以只比1x1慢一倍，而不会慢8倍。&lt;/p&gt;  &lt;p&gt;不过，世界变化很快。现在的计算机视觉 (CV) 神经网络里，用到的很多卷积类型，   &lt;strong&gt;已经沾不到NNPACK的光&lt;/strong&gt;：&lt;/p&gt;  &lt;p&gt;比如，1x1卷积，分组卷积 (Grouped Convolution) ，Strided Convolution，扩张卷积 (Dilated Convolution) ，DW卷积 (DepthWise Convolution) ，适用于精度/存储带宽受到限制的 (移动端等) 场景。&lt;/p&gt;  &lt;p&gt;而CV神经网络，大部分推理时间，都花在卷积和全连接运算上。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="640?wx_fmt=png" src="https://ss.csdn.net/p?https://mmbiz.qpic.cn/mmbiz_png/YicUhk5aAGtDJHn5uYxdtRfjBQkg0AqzDXHFibpZPBC7AslYibqoAVzd1XY4qQs4yHmicdqQE2icJk3EbCau9AEyIJA/640?wx_fmt=png"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;这样的运算，和   &lt;strong&gt;矩阵乘法&lt;/strong&gt;密切相关：&lt;/p&gt;  &lt;p&gt;大内核的卷积，可以分解成im2col和一个矩阵乘法。&lt;/p&gt;  &lt;p&gt;所以，有高效的矩阵乘法，才能有高效的卷积网络。&lt;/p&gt;  &lt;p&gt;于是，QNNPACK出世了。&lt;/p&gt;  &lt;h1&gt;怎样加速矩阵乘法？&lt;/h1&gt;  &lt;p&gt;   &lt;img alt="640?wx_fmt=jpeg" src="https://ss.csdn.net/p?https://mmbiz.qpic.cn/mmbiz_jpg/YicUhk5aAGtDJHn5uYxdtRfjBQkg0AqzD5EIKnlJk6rZ4PNyrGHxU7ZJQMicibjDhzYF0VQzORASA9pmJQbvth6hg/640?wx_fmt=jpeg"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;矩阵乘法，A x B = C。C里面的每一个元素，都可以看成   &lt;strong&gt;A中某行&lt;/strong&gt;和   &lt;strong&gt;B中某列&lt;/strong&gt;的点乘。   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;但直接在点乘基础上计算的话，一点也不快，会受到存储带宽的限制。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="640?wx_fmt=png" src="https://ss.csdn.net/p?https://mmbiz.qpic.cn/mmbiz_png/YicUhk5aAGtDJHn5uYxdtRfjBQkg0AqzDOIdv5TDPSG93ceBYv35XqBjD8pzN1AMwL7jFgU9OO4odzzWHxaAGJw/640?wx_fmt=png"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;如果，能同时计算   &lt;strong&gt;A中多行&lt;/strong&gt;和   &lt;strong&gt;B中多列&lt;/strong&gt;的点乘，即   &lt;strong&gt;MRxNR&lt;/strong&gt;，就能给运算速度带来猛烈的提升。&lt;/p&gt;  &lt;p&gt;不需要太多，这样细小的改变就够了。&lt;/p&gt;  &lt;h1&gt;节省内存和缓存&lt;/h1&gt;  &lt;p&gt;模型训练，可能更需要高精度。但在训练完成后，   &lt;strong&gt;推理&lt;/strong&gt;部分对精度的需求可能就没有那么高了。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;低精度&lt;/strong&gt;的计算，有时对推理的   &lt;strong&gt;准确性&lt;/strong&gt;不会产生明显的影响。&lt;/p&gt;  &lt;p&gt;而这样的运算，不需要太大存储，并节省能源，有助于把AI部署在移动端。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;QNNPACK&lt;/strong&gt;用的线性量化 (Linear Quantization) 方案，与安卓的神经网络API兼容。&lt;/p&gt;  &lt;p&gt;它假设量化值q[i]是用8比特的无符号整数 (Unsigned Integers) 来表示的，以及q[i]与真实值r[i]相关，关系如下：&lt;/p&gt;  &lt;p&gt;   &lt;img alt="640?wx_fmt=png" src="https://ss.csdn.net/p?https://mmbiz.qpic.cn/mmbiz_png/YicUhk5aAGtDJHn5uYxdtRfjBQkg0AqzDJy0icmBcEEXSanLjAF2HvJNV172GHshvhx75twl8vWL1xeNUJx7VZicw/640?wx_fmt=png"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;与其他库不同，QNNPACK把矩阵A、B都放进一级缓存 (L1 Cache) ，目标是把所有对运算过程并不非常必要的内存转换 (Memory Transformations) 都删掉。   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;QNNPACK可以在一次微内核调用 (Microkernel Call) 里，处理A和B。&lt;/p&gt;  &lt;p&gt;不需要在微内核之外，累积32位的中间结果，QNNPACK把32位的中间值融合进微内核，然后写出8位值，节省了存储带宽和缓存。&lt;/p&gt;  &lt;h1&gt;赢了TensorFlow Lite&lt;/h1&gt;  &lt;p&gt;开发团队用谷歌的视觉框架MobileNetV2里面的   &lt;strong&gt;图像分类模型&lt;/strong&gt;来测试。&lt;/p&gt;  &lt;p&gt;拿   &lt;strong&gt;TensorFlow Lite&lt;/strong&gt;做对手，和   &lt;strong&gt;QNNPACK&lt;/strong&gt;比了一场。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="640?wx_fmt=png" src="https://ss.csdn.net/p?https://mmbiz.qpic.cn/mmbiz_png/YicUhk5aAGtDJHn5uYxdtRfjBQkg0AqzDposnvGiclLy0Wib1MHdIfH9zB7Wics5SqKbw1PUDELHsKlqeqia7M0Mpow/640?wx_fmt=png"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;结果是，QNNPACK比TensorFlow Lite几乎快一倍，不论是在高端智能机，还是普通智能机身上。   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;所以，各位也去试一下？&lt;/p&gt;  &lt;p&gt;GitHub项目页：   &lt;br /&gt;https://github.com/pytorch/QNNPACK&lt;/p&gt;  &lt;p&gt;博客原文：   &lt;br /&gt;https://code.fb.com/ml-applications/qnnpack/&lt;/p&gt;  &lt;p&gt;—   &lt;strong&gt;完&lt;/strong&gt;—&lt;/p&gt;  &lt;p&gt;加入社群&lt;/p&gt;  &lt;p&gt;量子位AI社群开始招募啦，欢迎对AI感兴趣的同学，在量子位公众号（QbitAI）对话界面回复关键字“交流群”，获取入群方式；&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;此外，量子位专业细分群(   &lt;strong&gt;自动驾驶、CV、NLP、机器学习&lt;/strong&gt;等)正在招募，面向正在从事相关领域的工程师及研究人员。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;进专业群请在量子位公众号（QbitAI）对话界面回复关键字“专业群”，获取入群方式。（专业群审核较严，敬请谅解）&lt;/p&gt;  &lt;p&gt;活动策划招聘&lt;/p&gt;  &lt;p&gt;量子位正在招聘活动策划，将负责不同领域维度的线上线下相关活动策划、执行。欢迎聪明靠谱的小伙伴加入，并希望你能有一些活动策划或运营的相关经验。相关细节，请在量子位公众号(QbitAI)对话界面，回复“招聘”两个字。&lt;/p&gt;  &lt;img alt="640?wx_fmt=jpeg" src="https://ss.csdn.net/p?https://mmbiz.qpic.cn/mmbiz_jpg/YicUhk5aAGtD6icOLibmGwA8xhOBNP3VHq1kfd0UNvfxCHgGzCA4byN6GiaN2UBwJFpl7z8CEn5sjPcbLEianPKjiabw/640?wx_fmt=jpeg"&gt;&lt;/img&gt;  &lt;p&gt;   &lt;strong&gt;量子位 &lt;/strong&gt;QbitAI · 头条号签约作者&lt;/p&gt;  &lt;p&gt;վ&amp;apos;ᴗ&amp;apos; ի 追踪AI技术和产品新动态&lt;/p&gt;  &lt;br /&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>geek</category>
      <guid isPermaLink="true">https://itindex.net/detail/59148-facebook-%E5%BC%80%E6%BA%90-%E7%A7%BB%E5%8A%A8</guid>
      <pubDate>Sat, 29 Dec 2018 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>U-boot引导内核流程分析</title>
      <link>https://itindex.net/detail/59122-boot-%E5%BC%95%E5%AF%BC-%E5%86%85%E6%A0%B8</link>
      <description>&lt;div&gt;  &lt;p&gt;U-boot引导内核流程分析   &lt;br /&gt;1. 加载内核   &lt;br /&gt;当U-boot完成重定位和初始化外设后，它将正式进入工作状态，可以加载内核镜像到DDR的链接地址中了，具体的地址也可以通过bootcmd这个环境变量来指定，内核镜像有两种加载方式：&lt;/p&gt;  &lt;p&gt;一种是通过tftp将镜像文件直接引导入DDR中内核的链接地址（对于s5pv210来说是30008000），这种方法很适合调试   &lt;br /&gt;另一种是从存储介质中的特定扇区读取，这个扇区可以通过分区表来确定（关于分区表以后再续）   &lt;br /&gt;2.判断镜像   &lt;br /&gt;加载内核到DDR的链接地址后，U-boo将读取镜像的头信息，然后在头信息的特定位置找到MAGIC_NUM，由此来判断镜像的种类。这里就要说一下内核镜像的种类了，内核镜像主要分为zImage和uImage    &lt;br /&gt;zImage：当内核编译完成之后首先生成的是一个elf文件，此文件类似于windows下的exe，是可以在操作系统下运行的，故里面有许多的信息；可是我们的kernel是个裸机程序，不需要在操作系统下运行，删除这些信息后体积便能缩为1/10，这就成了我们熟悉的.bin文件，但是因为一些原因这个文件名字起成了Image…而不是Image.bin….；其实这个Image文件已经能用了，但是由于一些历史原因，Image一般要经过压缩变成zImage再使用，这个zImage文件比Image文件更小，它是自解压的，它的头部是一段信息和解压程序，解压程序可以把后面真正的代码自动解压出来，并不需要U-boot的协助   &lt;br /&gt;uImage：uImage是U-boot自己发明的一种内核镜像格式，是在zImage的基础上在加上一段信息和一段校验头，共64字节。那么将zImage加工为uImage的工具在哪呢，在U-boot根目录下/tools中的mkimage程序，只需将其复制到/usr/local/bin/，编译内核时输入make uImage，然后就能产生uImage了   &lt;br /&gt;判断完镜像种类后U-boot将对镜像头进行校验，然后再次读取头信息，从头信息的特定位置找到这个镜像的各种信息（镜像长度，镜像种类，入口地址）   &lt;br /&gt;4.相关环境变量   &lt;br /&gt;引导内核有关的环境变量有bootcmd和bootargs。之所以要有这两个环境变量其实是为了灵活，为了在U-boot不重新编译的情况下可以用不同的方式启动&lt;/p&gt;  &lt;p&gt;关于bootcmd，其实是U-boot内部好几个命令组成的命令集，负责引导内核的操作，以x210板载的bootcmd为例，bootcmd=movi read kernel 30008000；bootm 30008000；这其实是两句命令，第一句的意思是从inand的kernel分区读取内容到DDR内的地址30008000，第二句的意思是使用bootm启动命令到DDR的地址30008000处执行内核。其中，这个30008000是由内核的链接地址所确定的   &lt;br /&gt;关于bootargs，其实是U-boot传递给内核的参数，以x210板载的bootargs为例，bootargs=console=ttySAC2,115200 root=dev/mmcblk0p2 rw init=/linuxrc rootfstype=ext3，其中，console=ttySAC2,115200意思是引导后的控制台使用串口2，波特率115200；root=/dev/mmcblk0p2 rw意思是根文件系统在SD卡端口0设备（inand）第二分区，并且可读可写；init=/linuxrc意思是linux的进程1（init）进程的路径；rootfstype=ext3 意思是根文件系统的类型是ext3   &lt;br /&gt;5.传参及引导   &lt;br /&gt;最后把入口地址（ep）转化为一个函数指针theKernel = (void (*)(int, int,    &lt;br /&gt;uint))ep，然后通过函数指针去执行镜像（即执行内核的第一句代码）。至此一去不复返，U-boot彻底结束了它的使命   &lt;br /&gt;    void    (*theKernel)(int zero, int arch, uint params);//定义了一个函数指针   &lt;br /&gt;    //中间代码略过   &lt;br /&gt;    theKernel = (void (*)(int, int, uint))ep;//把入口地址赋给函数指针   &lt;br /&gt;    //中间代码略过   &lt;br /&gt;    theKernel (0, machid, bd-&amp;gt;bi_boot_params);//跳到内核入口执行内核，再也不返回&lt;/p&gt;  &lt;p&gt;    void    (*theKernel)(int zero, int arch, uint params);//定义了一个函数指针   &lt;br /&gt;    //中间代码略过   &lt;br /&gt;    theKernel = (void (*)(int, int, uint))ep;//把入口地址赋给函数指针   &lt;br /&gt;    //中间代码略过   &lt;br /&gt;    theKernel (0, machid, bd-&amp;gt;bi_boot_params);//跳到内核入口执行内核，再也不返回&lt;/p&gt;  &lt;p&gt;---------------------    &lt;br /&gt;作者：XiaoBaWu    &lt;br /&gt;来源：CSDN    &lt;br /&gt;原文：https://blog.csdn.net/qq_28992301/article/details/51873201    &lt;br /&gt;版权声明：本文为博主原创文章，转载请附上博文链接！&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;执行前传递给内核三个参数：&lt;/p&gt;  &lt;p&gt;0：这个参数固定为0   &lt;br /&gt;机器码：只有uboot的机器码和内核镜像中的机器码相同时，内核才能被正确启动，机器码的值第一顺序备选是环境变量machid，如果环境变量里面没有，则第二顺序备选是gd-&amp;gt;bd-&amp;gt;bi_arch_num（x210_sd.h中硬编码配置的）   &lt;br /&gt;bd-&amp;gt;bi_boot_params：这是全局变量结构体gd中的硬件信息结构体bd中的变量bi_boot_params，它的值为U-boot传给kernel的众多参数数据结构——tag的首地址。各个tag里面分别有许多有用的信息，比如环境变量bootargs的各种参数就在tag里，一般来说有关tag的代码是不用动的，只要考虑那些用来创建tag的宏（x210.h内）就行了   &lt;br /&gt;6.补充：新版uboot与内核的传参与引导   &lt;br /&gt;近年来的内核在启动时还需要dts文件，于是比较新的uboot都实现了传递dtb的功能，为了使能设备树，需要在编译U-boot的时候在config文件中加入：#define CONFIG_OF_LIBFDT&lt;/p&gt;  &lt;p&gt;一般分为三种情况：&lt;/p&gt;  &lt;p&gt;利用U-boot的命令，在引导kernel时将dts传入。这种方式需要将dtb的地址写到uboot中（一般是环境变量），比如：首先将kernel载入内存，然后用fdt addr ${fdtaddr}命令将dtb载入内存，最后使用bootz ${loadaddr} ${initrdaddr} ${fdtaddr}来引导内核，（其中initrd是临时文件系统，嵌入式中用得极少）实际使用时用“-”代替：bootz ${loadaddr} - ${fdtaddr}。总之，U-boot中的命令和环境变量是很灵活的，可以随意组合&lt;/p&gt;  &lt;p&gt;将dts和kernel打包为pImage。这种方式无需将dtb的地址写到uboot中（但uboot中要实现读pImage头部的功能），uboot可以去pImage的头部信息处读取到dtb的地址，然后传给传递给kernel&lt;/p&gt;  &lt;p&gt;启用kernel中”ARM_APPENDED_DTB”选项，该选项的意思是将dtb和kernel打包在一起，如此一来kernel启动时会去紧挨着它的地方寻找dtb，这样就不需要uboot来传递dtb地址了   &lt;br /&gt;7.引导时可能出现的问题   &lt;br /&gt;最后强调一下，如果U-boot确认无误可以启动起来，而kernel的启动却出现了问题，那么一般是三种情况&lt;/p&gt;  &lt;p&gt;对于老版本的U-boot，有大概率是U-boot传给kernel参数的时候出了问题，着重注意一下创建tag的宏有没有正常定义，如CONFIG_CMDLINE_TAG、CONFIG_MTDPARTITION等   &lt;br /&gt;kernel的链接地址与加载地址不符，链接地址可以通过kernel的head.S获知，详见kernel启动汇编阶段分析   &lt;br /&gt;kernel的自解压地址与链接地址不符，zImage这类kernel格式必须把自己解压到自己的链接地址，自解压地址在kernel源码目录arch/arm/mach-xxxx/Makefile.boot文件中   &lt;br /&gt;环境变量未被正常设置   &lt;br /&gt;---------------------    &lt;br /&gt;作者：XiaoBaWu    &lt;br /&gt;来源：CSDN    &lt;br /&gt;原文：https://blog.csdn.net/qq_28992301/article/details/51873201    &lt;br /&gt;版权声明：本文为博主原创文章，转载请附上博文链接！&lt;/p&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>geek</category>
      <guid isPermaLink="true">https://itindex.net/detail/59122-boot-%E5%BC%95%E5%AF%BC-%E5%86%85%E6%A0%B8</guid>
      <pubDate>Sun, 23 Dec 2018 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>不可不知的spark shuffle</title>
      <link>https://itindex.net/detail/59106-spark-shuffle</link>
      <description>&lt;div&gt;  &lt;h2&gt;   &lt;br /&gt;&lt;/h2&gt;  &lt;img alt="640?wx_fmt=png" src="https://ss.csdn.net/p?https://mmbiz.qpic.cn/mmbiz/cZV2hRpuAPgQ1OsgaP0iaM2iaUqyHW5WSDNHVkkrpriboBDrogCqa4GIR4nsNRibTTEue7PRJoL2QPbgWXdSCfuzMQ/640?wx_fmt=png"&gt;&lt;/img&gt;shuffle概览  &lt;p&gt;一个spark的RDD有一组固定的分区组成，每个分区有一系列的记录组成。对于由窄依赖变换（例如map和filter）返回的RDD，会延续父RDD的分区信息，以pipeline的形式计算。每个对象仅依赖于父RDD中的单个对象。诸如coalesce之类的操作可能导致任务处理多个输入分区，但转换仍然被认为是窄依赖的，因为一个父RDD的分区只会被一个子RDD分区继承。   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;Spark还支持宽依赖的转换，例如groupByKey和reduceByKey。在这些依赖项中，计算单个分区中的记录所需的数据可以来自于父数据集的许多分区中。要执行这些转换，具有相同key的所有元组必须最终位于同一分区中，由同一任务处理。为了满足这一要求，Spark产生一个shuffle，它在集群内部传输数据，并产生一个带有一组新分区的新stage。&lt;/p&gt;  &lt;p&gt;可以看下面的代码片段：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;sc.textFile(&amp;quot;someFile.txt&amp;quot;).map(mapFunc).flatMap(flatMapFunc).filter(filterFunc).count()&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;上面的代码片段只有一个action操作，count，从输入textfile到action经过了三个转换操作。这段代码只会在一个stage中运行，因为，三个转换操作没有shuffle，也即是三个转换操作的每个分区都是只依赖于它的父RDD的单个分区。&lt;/p&gt;  &lt;p&gt;但是，下面的单词统计就跟上面有很大区别：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;val tokenized = sc.textFile(args(0)).flatMap(_.split(&amp;apos; &amp;apos;))    &lt;br /&gt;val wordCounts = tokenized.map((_,1)).reduceByKey(_ + _)    &lt;br /&gt;val filtered = wordCounts.filter(_._2 &amp;gt;=1000)    &lt;br /&gt;val charCounts = filtered.flatMap(_._1.toCharArray).map((_,1)).reduceByKey(_ + _)    &lt;br /&gt;charCounts.collect()&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;这段代码里有两个reducebykey操作，三个stage。&lt;/p&gt;  &lt;p&gt;下面图更复杂，因为有一个join操作：&lt;/p&gt;  &lt;p&gt;   &lt;img alt="640?wx_fmt=png" src="https://ss.csdn.net/p?https://mmbiz.qpic.cn/mmbiz_png/adI0ApTVBFXLR2aCavYPeg10VKpodVk79oQvfh0uCP1xsrDQOhw5kvCwficznDv2IOPuLtQKlTsVHjAyacRXbXg/640?wx_fmt=png"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;粉框圈住的就是整个DAG的stage划分。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="640?wx_fmt=png" src="https://ss.csdn.net/p?https://mmbiz.qpic.cn/mmbiz_png/adI0ApTVBFXLR2aCavYPeg10VKpodVk7pSdJCsTwJ8JTtOt9yhWn9HIJjkkRwS14ibAocQIGTcn1q82l4YZQ42w/640?wx_fmt=png"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;在每个stage的边界，父stage的task会将数据写入磁盘，子stage的task会将数据通过网络读取。由于它们会导致很高的磁盘和网络IO，所以shuffle代价相当高，应该尽量避免。父stage的数据分区往往和子stage的分区数不同。触发shuffle的操作算子往往可以指定分区数的，也即是numPartitions代表下个stage会有多少个分区。就像mr任务中reducer的数据是非常重要的一个参数一样，shuffle的时候指定分区数也将在很大程度上决定一个应用程序的性能。&lt;/p&gt;  &lt;img alt="640?wx_fmt=png" src="https://ss.csdn.net/p?https://mmbiz.qpic.cn/mmbiz/cZV2hRpuAPgQ1OsgaP0iaM2iaUqyHW5WSDNHVkkrpriboBDrogCqa4GIR4nsNRibTTEue7PRJoL2QPbgWXdSCfuzMQ/640?wx_fmt=png"&gt;&lt;/img&gt;优化shuffle  &lt;p&gt;通常情况可以选择使用产生相同结果的action和transform相互替换。但是并不是产生相同结果的算子就会有相同的性能。通常避免常见的陷阱并选择正确的算子可以显著提高应用程序的性能。   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;当选择转换操作的时候，应最小化shuffle次数和shuffle的数据量。shuffle是非常消耗性能的操作。所有的shuffle数据都会被写入磁盘，然后通过网络传输。repartition , join, cogroup, 和  *By 或者 *ByKey 类型的操作都会产生shuffle。我们可以对一下几个操作算子进行优化：&lt;/p&gt;  &lt;p&gt;1. groupByKey某些情况下可以被reducebykey代替。&lt;/p&gt;  &lt;p&gt;2. reduceByKey某些情况下可以被 aggregatebykey代替。&lt;/p&gt;  &lt;p&gt;3. flatMap-join-groupBy某些情况下可以被cgroup代替。&lt;/p&gt;  &lt;p&gt;具体细节，知识星球球友可以点击   &lt;strong&gt;阅读    &lt;strong&gt;原文&lt;/strong&gt;&lt;/strong&gt;进入知识星球阅读。&lt;/p&gt;  &lt;img alt="640?wx_fmt=png" src="https://ss.csdn.net/p?https://mmbiz.qpic.cn/mmbiz/cZV2hRpuAPgQ1OsgaP0iaM2iaUqyHW5WSDNHVkkrpriboBDrogCqa4GIR4nsNRibTTEue7PRJoL2QPbgWXdSCfuzMQ/640?wx_fmt=png"&gt;&lt;/img&gt;no shuffle  &lt;p&gt;在某些情况下，前面描述的转换操作不会导致shuffle。当先前的转换操作已经使用了和shuffle相同的分区器分区数据的时候，spark就不会产生shuffle。   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;举个例子：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;rdd1= someRdd.reduceByKey(...)    &lt;br /&gt;    &lt;br /&gt;rdd2= someOtherRdd.reduceByKey(...)    &lt;br /&gt;    &lt;br /&gt;rdd3= rdd1.join(rdd2)&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;由于使用redcuebykey的时候没有指定分区器，所以都是使用的默认分区器，会导致rdd1和rdd2都采用的是hash分区器。两个reducebykey操作会产生两个shuffle过程。如果，数据集有相同的分区数，执行join操作的时候就不需要进行额外的shuffle。由于数据集的分区相同，因此rdd1的任何单个分区中的key集合只能出现在rdd2的单个分区中。 因此，rdd3的任何单个输出分区的内容仅取决于rdd1中单个分区的内容和rdd2中的单个分区，并且不需要第三个shuffle。&lt;/p&gt;  &lt;p&gt;例如，如果someRdd有四个分区，someOtherRdd有两个分区，而reduceByKeys都使用三个分区，运行的任务集如下所示：&lt;/p&gt;  &lt;p&gt;   &lt;img alt="640?wx_fmt=png" src="https://ss.csdn.net/p?https://mmbiz.qpic.cn/mmbiz_png/adI0ApTVBFXLR2aCavYPeg10VKpodVk73ia7ibXp7eV2R3KAcwIZd4zcyyvmUF209s8SwgIjiaHQqictq3nqKUzt6A/640?wx_fmt=png"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;如果rdd1和rdd2使用不同的分区器或者相同的分区器不同的分区数，仅仅一个数据集在join的过程中需要重新shuffle&lt;/p&gt;  &lt;p&gt;   &lt;img alt="640?wx_fmt=png" src="https://ss.csdn.net/p?https://mmbiz.qpic.cn/mmbiz_png/adI0ApTVBFXLR2aCavYPeg10VKpodVk7JQ3hlH2JfGklz8wPqQOFibqNtFu8q3RBnMBAvhroYn1WHcTzz13d8Vw/640?wx_fmt=png"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;在join的过程中为了避免shuffle，可以使用广播变量。当executor内存可以存储数据集，在driver端可以将其加载到一个hash表中，然后广播到executor。然后，map转换可以引用哈希表来执行查找。&lt;/p&gt;  &lt;img alt="640?wx_fmt=png" src="https://ss.csdn.net/p?https://mmbiz.qpic.cn/mmbiz/cZV2hRpuAPgQ1OsgaP0iaM2iaUqyHW5WSDNHVkkrpriboBDrogCqa4GIR4nsNRibTTEue7PRJoL2QPbgWXdSCfuzMQ/640?wx_fmt=png"&gt;&lt;/img&gt;增加shuffle  &lt;p&gt;有时候需要打破最小化shuffle次数的规则。   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;当增加并行度的时候，额外的shuffle是有利的。例如，数据中有一些文件是不可分割的，那么该大文件对应的分区就会有大量的记录，而不是说将数据分散到尽可能多的分区内部来使用所有已经申请cpu。在这种情况下，使用reparition重新产生更多的分区数，以满足后面转换算子所需的并行度，这会提升很大性能。&lt;/p&gt;  &lt;p&gt;使用reduce和aggregate操作将数据聚合到driver端，也是修改区数的很好的例子。&lt;/p&gt;  &lt;p&gt;在对大量分区执行聚合的时候，在driver的单线程中聚合会成为瓶颈。要减driver的负载，可以首先使用reducebykey或者aggregatebykey执行一轮分布式聚合，同时将结果数据集分区数减少。实际思路是首先在每个分区内部进行初步聚合，同时减少分区数，然后再将聚合的结果发到driver端实现最终聚合。典型的操作是treeReduce 和 treeAggregate。&lt;/p&gt;  &lt;p&gt;当聚合已经按照key进行分组时，此方法特别适用。例如，假如一个程序计算语料库中每个单词出现的次数，并将结果使用map返回到driver。一种方法是可以使用聚合操作完成在每个分区计算局部map，然后在driver中合并map。可以用aggregateByKey以完全分布的方式进行统计，然后简单的用collectAsMap将结果返回到driver。&lt;/p&gt;  &lt;p&gt;更多spark技巧，大数据技巧，欢迎点击   &lt;strong&gt;阅读原文&lt;/strong&gt;加入   &lt;strong&gt;知识星球&lt;/strong&gt;。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;推荐阅读：&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;   &lt;a href="http://mp.weixin.qq.com/s?__biz=MzA3MDY0NTMxOQ==&amp;mid=2247485250&amp;idx=1&amp;sn=f9a8a27d6b966fb53725505072f50f8f&amp;chksm=9f38e46aa84f6d7ca50bec7c593f73ddc064af6e598cde4ea9ed85b91aef89e0f55065c4bad2&amp;scene=21#wechat_redirect" rel="nofollow"&gt;经验|如何设置Spark资源&lt;/a&gt;   &lt;strong&gt;    &lt;br /&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;   &lt;a href="http://mp.weixin.qq.com/s?__biz=MzA3MDY0NTMxOQ==&amp;mid=2247485198&amp;idx=1&amp;sn=2ab4a6297443d0a6822d193d6ada0823&amp;chksm=9f38e426a84f6d300a283df4a1d360bc8fc0b9b0e7965af46e039f34f46f6b9753d30d95a4d2&amp;scene=21#wechat_redirect" rel="nofollow"&gt;戳破 | hive on spark 调优点&lt;/a&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;img alt="640?wx_fmt=png" src="https://ss.csdn.net/p?https://mmbiz.qpic.cn/mmbiz_png/adI0ApTVBFWF1rkKibTzeA8PicbicYXBsH26a9PXg2HNnlEt1thHBFxUtEjicACeaSlRWictpPziaMdibXmYq34dWfQ9w/640?wx_fmt=png"&gt;&lt;/img&gt;&lt;/p&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>geek</category>
      <guid isPermaLink="true">https://itindex.net/detail/59106-spark-shuffle</guid>
      <pubDate>Fri, 21 Dec 2018 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>NLP----关键词提取算法（TextRank,TF/IDF）</title>
      <link>https://itindex.net/detail/59063-nlp-%E5%85%B3%E9%94%AE%E8%AF%8D-%E7%AE%97%E6%B3%95</link>
      <description>&lt;div&gt;  &lt;p&gt;参考书目：python自然语言处理实战——核心技术与算法&lt;/p&gt;  &lt;h1&gt;TF/IDF&lt;/h1&gt;  &lt;p&gt;基本思想：TF是计算一个词在一篇文档中出现的频率，IDF是一个词在多少篇文档中出现过，显然TF越高证明这个词在这篇文章中的代表性就越强，而INF越低则证明这个词在具有越强的区分能力。因此中和这两个数，就能较好地算出文档的关键词。&lt;/p&gt;  &lt;p&gt;关键公式&lt;/p&gt;  &lt;p&gt;   &lt;img alt="tf*idf(i,j)=tf_{ij}*idf_i=\frac{n_{ij}}{\sum _kn_{kj}}*log(\frac{|D|}{1+|D_i|})" src="https://private.codecogs.com/gif.latex?tf*idf%28i%2Cj%29%3Dtf_%7Bij%7D*idf_i%3D%5Cfrac%7Bn_%7Bij%7D%7D%7B%5Csum%20_kn_%7Bkj%7D%7D*log%28%5Cfrac%7B%7CD%7C%7D%7B1&amp;plus;%7CD_i%7C%7D%29"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;|D_i|是文档中出现词i的文档数量，|D|是文档数&lt;/p&gt;  &lt;p&gt;附上书上抄来的代码&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;import jieba
import jieba.posseg as psg
import math
import functools

# 停用词表加载方法


def get_stopword_list():
    # 停用词表存储路径，每一行为一个词，按行读取进行加载
    # 进行编码转换确保匹配准确率
    stop_word_path = &amp;apos;./data/stopword.txt&amp;apos;
    stopword_list = [sw.replace(&amp;apos;\n&amp;apos;, &amp;apos;&amp;apos;)
                     for sw in open(stop_word_path,encoding = &amp;apos;utf-8&amp;apos;).readlines()]
    return stopword_list

# 分词方法，调用结巴接口


def seg_to_list(sentence, pos=False):
    if not pos:
        # 不进行词性标注的分词方法
        seg_list = jieba.cut(sentence)
    else:
        # 进行词性标注的分词方法
        seg_list = psg.cut(sentence)
    return seg_list

# 去干扰词


def word_filter(seg_list, pos=False):
    stopword_list = get_stopword_list()
    filter_list = []
    # 根据POS参数选择是否词性过滤
    # 不进行词性过滤，则将词性都标记为n，表示全部保留
    for seg in seg_list:
        if not pos:
            word = seg
            flag = &amp;apos;n&amp;apos;
        else:
            word = seg.word
            flag = seg.flag
        if not flag.startswith(&amp;apos;n&amp;apos;):
            continue
        # 过滤高停用词表中的词，以及长度&amp;lt;2的词
        if not word in stopword_list and len(word) &amp;gt; 1:
            filter_list.append(word)

    return filter_list

# 数据加载，pos为是否词性标注的参数，corpus_path为数据集路径


def load_data(pos=False, corpus_path=&amp;apos;./data/corpus.txt&amp;apos;):
    # 调用上面方法对数据集进行处理，处理后的每条数据仅保留非干扰词
    doc_list = []
    for line in open(corpus_path, &amp;apos;r&amp;apos;,encoding = &amp;apos;utf-8&amp;apos;):
        content = line.strip()
        seg_list = seg_to_list(content, pos)
        filter_list = word_filter(seg_list, pos)
        doc_list.append(filter_list)
    return doc_list


def train_idf(doc_list):
    idf_dic = {}
    #总文档数
    tt_count = len(doc_list)
    
    #每个词出现的文档数
    for doc in doc_list:
        for word in set(doc):
            idf_dic[word] = idf_dic.get(word,0.0)+1.0

    #按公示转换为idf值，分母加一进行平滑处理
    for k,v in idf_dic.items():
        idf_dic[k]=math.log(tt_count/(1.0+v))
    
    #对于没有在字典中的词，默认其仅在一个文档中出现，得到默认idf值
    default_idf = math.log(tt_count/1.0)
    return idf_dic,default_idf

def cmp(e1,e2):
    import numpy as np
    res = np.sign(e1[1]-e2[1])
    if res != 0:
        return res
    else:
        a = e1[0]+e2[0]
        b = e2[0]+e1[0]
        if a&amp;gt;b:
            return 1
        elif a == b:
            return 0
        else:
            return -1

class TfIdf(object):
    #统计tf值
    def get_tf_dic(self):
        tf_dic = {}
        for word in self.word_list:
            tf_dic[word] = tf_dic.get(word,0.0)+1.0
        
        tt_count = len(self.word_list)
        for k,v in tf_dic.items():
            tf_dic[k] = float(v)/tt_count

        return tf_dic
    
    #四个参数分别是：训练好的idf字典，默认idf值，处理后的待提取文本，关键词数量
    def __init__(self,idf_dic,default_idf,word_list,keyword_num):
        self.word_list = word_list
        self.idf_dic,self.default_idf = idf_dic,default_idf
        self.tf_dic  = self.get_tf_dic()
        self.keyword_num = keyword_num

    #按公式计算tf_idf
    def get_tfidf(self):
        tfidf_dic = {}
        for word in self.word_list:
            idf = self.idf_dic.get(word,self.default_idf)
            tf = self.tf_dic.get(word,0)

            tfidf  = tf*idf
            tfidf_dic[word] = tfidf

            #根据tf_idf排序，取排名前keyword_num的词作为关键词
        for k ,v in sorted(tfidf_dic.items(),key=functools.cmp_to_key(cmp),reverse=True)[:self.keyword_num]:
            print(k+&amp;quot;/&amp;quot;,end=&amp;apos;&amp;apos;)
        print()

def tfidf_extract(word_list, pos=False, keyword_num=10):
    doc_list = load_data(pos)
    idf_dic, default_idf = train_idf(doc_list)
    tfidf_model = TfIdf(idf_dic, default_idf, word_list, keyword_num)
    tfidf_model.get_tfidf()

if __name__ == &amp;apos;__main__&amp;apos;:
    text = &amp;apos;6月19日,《2012年度“中国爱心城市”公益活动新闻发布会》在京举行。&amp;apos; + \
           &amp;apos;中华社会救助基金会理事长许嘉璐到会讲话。基金会高级顾问朱发忠,全国老龄&amp;apos; + \
           &amp;apos;办副主任朱勇,民政部社会救助司助理巡视员周萍,中华社会救助基金会副理事长耿志远,&amp;apos; + \
           &amp;apos;重庆市民政局巡视员谭明政。晋江市人大常委会主任陈健倩,以及10余个省、市、自治区民政局&amp;apos; + \
           &amp;apos;领导及四十多家媒体参加了发布会。中华社会救助基金会秘书长时正新介绍本年度“中国爱心城&amp;apos; + \
           &amp;apos;市”公益活动将以“爱心城市宣传、孤老关爱救助项目及第二届中国爱心城市大会”为主要内容,重庆市&amp;apos; + \
           &amp;apos;、呼和浩特市、长沙市、太原市、蚌埠市、南昌市、汕头市、沧州市、晋江市及遵化市将会积极参加&amp;apos; + \
           &amp;apos;这一公益活动。中国雅虎副总编张银生和凤凰网城市频道总监赵耀分别以各自媒体优势介绍了活动&amp;apos; + \
           &amp;apos;的宣传方案。会上,中华社会救助基金会与“第二届中国爱心城市大会”承办方晋江市签约,许嘉璐理&amp;apos; + \
           &amp;apos;事长接受晋江市参与“百万孤老关爱行动”向国家重点扶贫地区捐赠的价值400万元的款物。晋江市人大&amp;apos; + \
           &amp;apos;常委会主任陈健倩介绍了大会的筹备情况。&amp;apos;

    pos = True
    seg_list = seg_to_list(text, pos)
    filter_list = word_filter(seg_list, pos)

    print(&amp;apos;TF-IDF模型结果：&amp;apos;)
    tfidf_extract(filter_list)&lt;/code&gt;&lt;/pre&gt;  &lt;h1&gt;TextRank&lt;/h1&gt;  &lt;p&gt;基本思路：每个词将自己的分数平均投给附近的词，迭代至收敛或指定次数即可，初始分可以打1&lt;/p&gt;  &lt;p&gt;附上代码&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;def get_stopword_list():
    path = &amp;apos;./data/stop_words.utf8&amp;apos;
    stopword_list = [sw.replace(&amp;apos;\n&amp;apos;,&amp;apos;&amp;apos;) for sw in open(path,&amp;apos;r&amp;apos;,encoding=&amp;apos;utf8&amp;apos;).readlines()]
    return stopword_list

def seg2list(text):
    import jieba
    return jieba.cut(text)

def word_filter(seg_list):
    stopword_list = get_stopword_list()
    filter_list = []
    for w in seg_list:
        if not w in stopword_list and len(w)&amp;gt;1:
            filter_list.append(w)
    return filter_list


str = &amp;apos;6月19日,《2012年度“中国爱心城市”公益活动新闻发布会》在京举行。&amp;apos; + \
           &amp;apos;中华社会救助基金会理事长许嘉璐到会讲话。基金会高级顾问朱发忠,全国老龄&amp;apos; + \
           &amp;apos;办副主任朱勇,民政部社会救助司助理巡视员周萍,中华社会救助基金会副理事长耿志远,&amp;apos; + \
           &amp;apos;重庆市民政局巡视员谭明政。晋江市人大常委会主任陈健倩,以及10余个省、市、自治区民政局&amp;apos; + \
           &amp;apos;领导及四十多家媒体参加了发布会。中华社会救助基金会秘书长时正新介绍本年度“中国爱心城&amp;apos; + \
           &amp;apos;市”公益活动将以“爱心城市宣传、孤老关爱救助项目及第二届中国爱心城市大会”为主要内容,重庆市&amp;apos; + \
           &amp;apos;、呼和浩特市、长沙市、太原市、蚌埠市、南昌市、汕头市、沧州市、晋江市及遵化市将会积极参加&amp;apos; + \
           &amp;apos;这一公益活动。中国雅虎副总编张银生和凤凰网城市频道总监赵耀分别以各自媒体优势介绍了活动&amp;apos; + \
           &amp;apos;的宣传方案。会上,中华社会救助基金会与“第二届中国爱心城市大会”承办方晋江市签约,许嘉璐理&amp;apos; + \
           &amp;apos;事长接受晋江市参与“百万孤老关爱行动”向国家重点扶贫地区捐赠的价值400万元的款物。晋江市人大&amp;apos; + \
           &amp;apos;常委会主任陈健倩介绍了大会的筹备情况。&amp;apos;
win={}
seg_list = seg2list(str)
filter_list = word_filter(seg_list)
#构建投分表，根据窗口
for i in range(len(filter_list)):
    if filter_list[i] not in win.keys():
        win[filter_list[i]]=set()
    if i-5 &amp;lt; 0:
        lindex = 0
    else:
        lindex = i-5
    for j in filter_list[lindex:i+5]:
        win[filter_list[i]].add(j)

# 投票
time = 0
score = {w:1.0 for w in filter_list}
while(time&amp;lt;50):
    for k,v in win.items():
        s = score[k]/len(v)
        score[k] = 0
        for i in v:
            score[i]+=s
    time+=1

l = sorted(score.items(), key=lambda score:score[1],reverse=True)
print(l)&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt; &lt;/p&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>geek</category>
      <guid isPermaLink="true">https://itindex.net/detail/59063-nlp-%E5%85%B3%E9%94%AE%E8%AF%8D-%E7%AE%97%E6%B3%95</guid>
      <pubDate>Tue, 11 Dec 2018 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>15分钟入门NLP神器—Gensim</title>
      <link>https://itindex.net/detail/59049-nlp-%E7%A5%9E%E5%99%A8-gensim</link>
      <description>&lt;div&gt;  &lt;p&gt;   作者：李雪冬           &lt;/p&gt;  &lt;p&gt;编辑：李雪冬           &lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;前  言&lt;/strong&gt;&lt;/p&gt;  &lt;br /&gt;  &lt;p&gt;作为自然语言处理爱好者，大家都应该听说过或使用过大名鼎鼎的Gensim吧，这是一款具备多种功能的神器。    &lt;br /&gt;Gensim是一款开源的第三方Python工具包，用于从原始的非结构化的文本中，无监督地学习到文本隐层的主题向量表达。    &lt;br /&gt;它支持包括TF-IDF，LSA，LDA，和word2vec在内的多种主题模型算法，    &lt;br /&gt;支持流式训练，并提供了诸如相似度计算，信息检索等一些常用任务的API接口&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;strong&gt;1&lt;/strong&gt;  &lt;p&gt;基本概念&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;    &lt;p&gt;语料（Corpus）：一组原始文本的集合，用于无监督地训练文本主题的隐层结构。语料中不需要人工标注的附加信息。在Gensim中，Corpus通常是一个可迭代的对象（比如列表）。每一次迭代返回一个可用于表达文本对象的稀疏向量。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;向量（Vector）：由一组文本特征构成的列表。是一段文本在Gensim中的内部表达。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;稀疏向量（SparseVector）：通常，我们可以略去向量中多余的0元素。此时，向量中的每一个元素是一个(key, value)的元组&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;模型（Model）：是一个抽象的术语。定义了两个向量空间的变换（即从文本的一种向量表达变换为另一种向量表达）。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;  &lt;strong&gt;2&lt;/strong&gt;  &lt;p&gt;步骤一：训练语料的预处理&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;&lt;/p&gt;  &lt;p&gt;由于Gensim使用python语言开发的，为了减少安装中的繁琐，直接使用anaconda工具进行集中安装，    &lt;br /&gt;输入：pip install gensim，这里不再赘述。&lt;/p&gt;  &lt;p&gt;训练语料的预处理指的是将文档中原始的字符文本转换成Gensim模型所能理解的稀疏向量的过程。&lt;/p&gt;  &lt;p&gt;通常，我们要处理的原生语料是一堆文档的集合，每一篇文档又是一些原生字符的集合。在交给Gensim的模型训练之前，我们需要将这些原生字符解析成Gensim能处理的稀疏向量的格式。由于语言和应用的多样性，我们需要先对原始的文本进行分词、去除停用词等操作，得到每一篇文档的特征列表。例如，在词袋模型中，文档的特征就是其包含的word：&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;texts = [[&amp;apos;human&amp;apos;,&amp;apos;interface&amp;apos;,&amp;apos;computer&amp;apos;],    &lt;br /&gt;[&amp;apos;survey&amp;apos;,&amp;apos;user&amp;apos;,&amp;apos;computer&amp;apos;,&amp;apos;system&amp;apos;,&amp;apos;response&amp;apos;,&amp;apos;time&amp;apos;],    &lt;br /&gt;[&amp;apos;eps&amp;apos;,&amp;apos;user&amp;apos;,&amp;apos;interface&amp;apos;,&amp;apos;system&amp;apos;],    &lt;br /&gt;[&amp;apos;system&amp;apos;,&amp;apos;human&amp;apos;,&amp;apos;system&amp;apos;,&amp;apos;eps&amp;apos;],    &lt;br /&gt;[&amp;apos;user&amp;apos;,&amp;apos;response&amp;apos;,&amp;apos;time&amp;apos;],    &lt;br /&gt;[&amp;apos;trees&amp;apos;],    &lt;br /&gt;[&amp;apos;graph&amp;apos;,&amp;apos;trees&amp;apos;],    &lt;br /&gt;[&amp;apos;graph&amp;apos;,&amp;apos;minors&amp;apos;,&amp;apos;trees&amp;apos;],    &lt;br /&gt;[&amp;apos;graph&amp;apos;,&amp;apos;minors&amp;apos;,&amp;apos;survey&amp;apos;]]&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;其中，corpus的每一个元素对应一篇文档。&lt;/p&gt;  &lt;p&gt;接下来，我们可以调用Gensim提供的API建立语料特征（此处即是word）的索引字典，并将文本特征的原始表达转化成词袋模型对应的稀疏向量的表达。依然以词袋模型为例：&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;from gensimimportcorpora    &lt;br /&gt;dictionary = corpora.Dictionary(texts)    &lt;br /&gt;corpus = [dictionary.doc2bow(text)fortext in texts]    &lt;br /&gt;print corpus[0] # [(0,1), (1,1), (2,1)]&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;到这里，训练语料的预处理工作就完成了。我们得到了语料中每一篇文档对应的稀疏向量（这里是bow向量）；向量的每一个元素代表了一个word在这篇文档中出现的次数。值得注意的是，虽然词袋模型是很多主题模型的基本假设，这里介绍的doc2bow函数并不是将文本转化成稀疏向量的唯一途径。在下一小节里我们将介绍更多的向量变换函数。&lt;/p&gt;  &lt;p&gt;最后，出于内存优化的考虑，Gensim支持文档的流式处理。我们需要做的，只是将上面的列表封装成一个Python迭代器；每一次迭代都返回一个稀疏向量即可。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;classMyCorpus(object):    &lt;br /&gt;def __iter__(self):    &lt;br /&gt;   forline inopen(&amp;apos;mycorpus.txt&amp;apos;):    &lt;br /&gt;       # assume there&amp;apos;s one document per line, tokens                   separated by whitespace    &lt;br /&gt;       yield dictionary.doc2bow(line.lower().split())&lt;/code&gt;&lt;/pre&gt;  &lt;strong&gt;3&lt;/strong&gt;  &lt;p&gt;步骤二：主题向量的变换&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;对文本向量的变换是Gensim的核心。通过挖掘语料中隐藏的语义结构特征，我们最终可以变换出一个简洁高效的文本向量。&lt;/p&gt;  &lt;p&gt;&lt;/p&gt;  &lt;p&gt;在Gensim中，每一个向量变换的操作都对应着一个主题模型，例如上一小节提到的对应着词袋模型的doc2bow变换。每一个模型又都是一个标准的Python对象。下面以TF-IDF模型为例，介绍Gensim模型的一般使用方法。&lt;/p&gt;  &lt;p&gt;首先是模型对象的初始化。通常，Gensim模型都接受一段训练语料（注意在Gensim中，语料对应着一个稀疏向量的迭代器）作为初始化的参数。显然，越复杂的模型需要配置的参数越多。&lt;/p&gt;  &lt;p&gt;&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;from gensimimportmodels    &lt;br /&gt;tfidf = models.TfidfModel(corpus)&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;其中，corpus是一个返回bow向量的迭代器。这两行代码将完成对corpus中出现的每一个特征的IDF值的统计工作。&lt;/p&gt;  &lt;p&gt;接下来，我们可以调用这个模型将任意一段语料（依然是bow向量的迭代器）转化成TFIDF向量（的迭代器）。需要注意的是，这里的bow向量必须与训练语料的bow向量共享同一个特征字典（即共享同一个向量空间）。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;doc_bow = [(0,1), (1,1)]    &lt;br /&gt;print tfidf[doc_bow] # [(0,0.70710678), (1,0.70710678)]&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;注意，同样是出于内存的考虑，model[corpus]方法返回的是一个迭代器。如果要多次访问model[corpus]的返回结果，可以先将结果向量序列化到磁盘上。&lt;/p&gt;  &lt;p&gt;我们也可以将训练好的模型持久化到磁盘上，以便下一次使用：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;&lt;/code&gt;&lt;/pre&gt;  &lt;pre&gt;   &lt;code&gt;    &lt;code&gt;tfidf.save(&amp;quot;./model.tfidf&amp;quot;)     &lt;br /&gt;tfidf = models.TfidfModel.load(&amp;quot;./model.tfidf&amp;quot;)&lt;/code&gt;&lt;/code&gt;&lt;/pre&gt;  &lt;code&gt;&lt;/code&gt;  &lt;p&gt;Gensim内置了多种主题模型的向量变换，包括LDA，LSI，RP，HDP等。这些模型通常以bow向量或tfidf向量的语料为输入，生成相应的主题向量。所有的模型都支持流式计算。关于Gensim模型更多的介绍，可以参考这里：API Reference（https://radimrehurek.com/gensim/apiref.html）&lt;/p&gt;  &lt;strong&gt;4&lt;/strong&gt;  &lt;p&gt;步骤三：文档相似度的计算&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;&lt;/p&gt;  &lt;p&gt;在得到每一篇文档对应的主题向量后，我们就可以计算文档之间的相似度，进而完成如文本聚类、信息检索之类的任务。在Gensim中，也提供了这一类任务的API接口。&lt;/p&gt;  &lt;p&gt;以信息检索为例。对于一篇待检索的query，我们的目标是从文本集合中检索出主题相似度最高的文档。&lt;/p&gt;  &lt;p&gt;首先，我们需要将待检索的query和文本放在同一个向量空间里进行表达（以LSI向量空间为例）：&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;# 构造LSI模型并将待检索的query和文本转化为LSI主题向量    &lt;br /&gt;# 转换之前的corpus和query均是BOW向量    &lt;br /&gt;lsi_model = models.LsiModel(corpus, id2word=dictionary,          num_topics=2)    &lt;br /&gt;documents = lsi_model[corpus]    &lt;br /&gt;query_vec = lsi_model[query]&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;接下来，我们用待检索的文档向量初始化一个相似度计算的对象：&lt;/p&gt;  &lt;p&gt;&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;index = similarities.MatrixSimilarity(documents)&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;我们也可以通过save()和load()方法持久化这个相似度矩阵：&lt;/p&gt;  &lt;p&gt;&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;index.save(&amp;apos;/tmp/test.index&amp;apos;)    &lt;br /&gt;index = similarities.MatrixSimilarity.load(&amp;apos;/tmp/test.index&amp;apos;)&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;注意，如果待检索的目标文档过多，使用similarities.MatrixSimilarity类往往会带来内存不够用的问题。此时，可以改用similarities.Similarity类。二者的接口基本保持一致。&lt;/p&gt;  &lt;p&gt;最后，我们借助index对象计算任意一段query和所有文档的（余弦）相似度：&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;sims = index[query_vec]    &lt;br /&gt;#返回一个元组类型的迭代器：(idx, sim)&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;5&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;补充&lt;/p&gt;  &lt;h3&gt;TF-IDF&lt;/h3&gt;  &lt;p&gt;TF-IDF（注意：这里不是减号）是一种统计方法，用以评估一字词对于一个文件集或一个语料库中的其中一份文件的重要程度。    &lt;br /&gt;字词的重要性随着它在文件中出现的次数成正比增加，但同时会随着它在语料库中出现的频率成反比下降。TF-IDF加权的各种形式常被搜索引擎应用，作为文件与用户查询之间相关程度的度量或评级。    &lt;br /&gt;1. 一个词预测主题能力越强，权重就越大，反之，权重就越小。我们在网页中看到“原子能”这个词，或多或少地能了解网页的主题。我们看到“应用”一次，对主题基本上还是一无所知。因此，“原子能“的权重就应该比应用大。    &lt;br /&gt;2. 应删除词的权重应该是零。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;&lt;/p&gt;  &lt;h3&gt;LDA文档主题生成模型&lt;/h3&gt;  &lt;p&gt;LDA是一种文档主题生成模型，包含词、主题和文档三层结构。&lt;/p&gt;  &lt;p&gt;所谓生成模型，就是说，我们认为一篇文章的每个词都是通过“以一定概率选择了某个主题，并从这个主题中以一定概率选择某个词语”这样一个过程得到。文档到主题服从多项式分布，主题到词服从多项式分布。&lt;/p&gt;  &lt;p&gt;LDA是一种非监督机器学习技术，可以用来识别大规模文档集或语料库中潜藏的主题信息。它采用了词袋的方法，这种方法将每一篇文档视为一个词频向量，从而将文本信息转化为了易于建模的数字信息。&lt;/p&gt;  &lt;p&gt;但是词袋方法没有考虑词与词之间的顺序，这简化了问题的复杂性，同时也为模型的改进提供了契机。每一篇文档代表了一些主题所构成的一个概率分布，而每一个主题又代表了很多单词所构成的一个概率分布。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;小结  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;gensim作为一款强大且开源的工具包非常值得我们花时间学习，如果对搜索引擎和自然语言处理感兴趣，更需要深入学习。在学习过程中建议大家多关注一些牛人博客，并进行归纳。这里只是抛砖引玉，想了解更多机器学习相关知识，请关注我们公众号：机器学习算法全栈工程师&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;参考资料  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;公子天：https://www.cnblogs.com/iloveai/p/gensim_tutorial.html    &lt;br /&gt;官网：https://radimrehurek.com/gensim/&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;end&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;机器学习算法全栈工程师&lt;/strong&gt;&lt;/p&gt;  &lt;hr&gt;&lt;/hr&gt;  &lt;p&gt;                            一个用心的公众号   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;img alt="640?wx_fmt=jpeg" src="https://ss.csdn.net/p?https://mmbiz.qpic.cn/mmbiz_jpg/iaTa8ut6HiawDdZMYspr4Sg6JgNEHRRRaZ7Bjjv4zo9GabzO4PkUILEGkyC7odlWMVEl6rsbfkr9PduYMbnQFZEA/640?wx_fmt=jpeg"&gt;&lt;/img&gt;&lt;/p&gt;长按，识别，加关注  &lt;br /&gt;  &lt;p&gt;进群，学习，得帮助&lt;/p&gt;  &lt;p&gt;你的关注，我们的热度，&lt;/p&gt;  &lt;p&gt;我们一定给你学习最大的帮助&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>geek</category>
      <guid isPermaLink="true">https://itindex.net/detail/59049-nlp-%E7%A5%9E%E5%99%A8-gensim</guid>
      <pubDate>Sat, 08 Dec 2018 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>是做通用型的AGI，还是垂直AI，不妨看看这篇文章</title>
      <link>https://itindex.net/detail/59047-%E9%80%9A%E7%94%A8-agi-%E5%9E%82%E7%9B%B4</link>
      <description>&lt;div&gt;  &lt;p&gt;   &lt;img alt="640?wx_fmt=gif&amp;wxfrom=5&amp;wx_lazy=1" src="https://ss.csdn.net/p?https://mmbiz.qpic.cn/mmbiz_gif/SWSB2QESy8hPiaicZriatKzHQ2ApgTYWuAOjnHzAJPyONoIBTZLdUVWKutHTiaX5csZYuhM6UibbbQPUAxdNmjTQqxQ/640?wx_fmt=gif&amp;wxfrom=5&amp;wx_lazy=1"&gt;&lt;/img&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;智造观点&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;随着技术的进步以及更多人的认可，机器学习面临的最大困境不是如何跨过前往通用人工智能（AGI）路上的障碍，而是如何将现有的机器学习技术对更多企业开放，并让其更具实用性。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;随着技术的进步以及更多人的认可，机器学习面临的最大困境不是如何跨过前往通用人工智能（AGI）路上的障碍，而是如何将现有的机器学习技术对更多企业开放，并让其更具实用性。了解到这一点，科技巨头们纷纷投资于大众化的人工智能，以便使其工具和服务可以得到更广泛的使用，然而，在这个过程中，机器学习用户的体验（UX）被忽视了。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;对此，即使是那些没有大量数据或者人才基础的公司，也可以对基于机器学习的应用程序进行大规模的改进，比如，通过构建一个优秀的UI（User Interface）来弥补数据的缺乏，等等。当我们将人工智能作为一种工具，并意识到其可用性对于未来广泛采纳的重要性时，增强现有人工智能技术的机会便会出现，而这一切与人类级别的智能机器或者通过人工智能毫无关系。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;AGI或许会上头条，但AI工具才能带来利益&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;虽然与谷歌更为普通的人工智能应用（如搜索）相比，像DeepMind或者Google Brain这样华而不实的项目更可能成为头条新闻，吸引到人们的注意力，但总的来说，前者才是一项利润丰厚的业务。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="640?wx_fmt=jpeg" src="https://ss.csdn.net/p?https://mmbiz.qpic.cn/mmbiz_jpg/SWSB2QESy8hPiaicZriatKzHQ2ApgTYWuAOiaGIfvvKNdzqSYeAcNrXV5jL3NmRH9CaXQA7sbpoqByOnRmeXLhOAicg/640?wx_fmt=jpeg"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;根据MarketWatch最近的一篇文章，谷歌目前已经“在人工智能和机器学习上投入了近数十亿美元的押注”，或许谷歌的押注是针对人工智能是否有“冬季”这一问题的对冲，换句话说，人们对人工智能的兴趣是不是会有降低的那一天。&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;最近，纽约大学的Gary Marcus撰写了一篇关于深度学习的评论文章，不仅在WIRED和MIT Technology Review等技术出版物上出现，在主流媒体上也得到了很多相关报道。在文中，Marcus警告了关于过度炒作人工智能的危险。&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;今年2月，英国《金融时报》发表了一篇题为《为什么我们处于高估人工智能的危险中》（Why We Are in Danger of Overestimating AI）的文章，指出目前人工智能系统存在严重的问题，比如它们可以轻易被愚弄并缺乏常识。&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;而在最近一部长达78分钟的纪录片中，对人工智能的炒作是关于通用人工智能的，而不是AI工具（AI-as-a-tool）。如果前者可以平静一段时间，那可能再好不过了。一方面，这不会影响我们对已投入应用的机器学习技术的使用，如搜索，翻译，内容推荐，对象分类等众多用途。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;另一方面，我们可以通过将服务提供给那些不能像谷歌或亚马逊一样雇佣大量人才的公司来增加价值。为了实现这一目标，这两家企业现在都提供各种平台和服务，甚至是机器学习模型即服务（已经接受大量数据培训），当然，对象都是缺乏专业知识的公司。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;这不是关于向人类级别的智能发展，而是要让现有技术得到更广泛的使用。微软和IBM也在大力投资这种所谓的大众化人工智能。但是，除了让现有的技术可供更多人使用之外，还有各种方式可以使该技术变得更加有用。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;不确定性是一个用户体验问题&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;机器学习的一个基本方面涉及从一组“训练数据”中学习，以便能够对新数据作出预测，因此该预测是不确定，不准确的。它仅仅是研究人员根据提供给系统的数据以数学方式得出的概率。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="640?wx_fmt=png" src="https://ss.csdn.net/p?https://mmbiz.qpic.cn/mmbiz_png/SWSB2QESy8hPiaicZriatKzHQ2ApgTYWuAOjpPjKtmLsgr6RRHjwbetvWBwRbDxhHibSZRwDeVU9cB2HZvZUicI2aRA/640?wx_fmt=png"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;当然，机器学习系统预测中固有的不确定性是不会消失的，所以我们必须把它处理掉，至少在基于预测采取的行动比针对网络内容或广告更严重的情况下，我们应该这么做。要知道，在某些情况下，机器学习预测所提供的决策可能会造成非常严重的后果。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;因此，我们面临的挑战是如何让用户对这种不确定性更加满意。在某种程度上，我们可以将可解释性问题视为可用性问题来对待。毕竟，一个可解释的预测比没有解释的预测更容易被信任和利用。&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;一旦意识到某些用户体验问题，我们便可以通过标准的工具和流程（可用性研究等）来寻找解决方案。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;UI如何提高机器学习的准确度&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;在文章开头部分提到了通过构建一个优秀的UI来弥补数据的缺乏，这是一种叫做“HitL”（human-in-the-Loop）的机器学习，这仅仅意味着任何机器学习系统的训练都会涉及到人类的工作。&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;像Figure Eight和Mighty AI等公司就是在引领该领域的众包方式。Mighty AI有一款应用，可以让任何拥有智能手机的人通过在图像上标注交通灯、行人、停着的汽车等标记来赚取几美分，而这些图像是该公司用于训练其自动驾驶汽车的数据。&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;但HitL不仅仅是通过众包来给所有的训练数据贴标签。我们可以创造性地使用像少量学习（Few-Shot Learning）这样的技术，在这个过程中，一个系统可以从几个标记的例子中学习如何进行分类，以及转移学习，即将学习从一个任务应用到另一个任务，以解决没有标签的训练数据可用的问题。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="640?wx_fmt=jpeg" src="https://ss.csdn.net/p?https://mmbiz.qpic.cn/mmbiz_jpg/SWSB2QESy8hPiaicZriatKzHQ2ApgTYWuAOfcXMGMCXibfR5ubWjKl5H9L1NzSe3bEcP9nKXHYRE6yHALm9bq6216A/640?wx_fmt=jpeg"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;迁移学习，换句话说，是学习丰富的数据代表，通常与少量学习相结合，因为它可以从几个例子中展开学习，丰富表达。如今，引入“少量投射”（Few Shots），便可以从没有标记数据到拥有强大的分类器成为可能。这里确实有很多需要探索的内容，包括如何更好地向人们展示最有效的标签示例，其中，如何充分利用HitL，对分类器的准确性至关重要。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;会话式AI：它只是软件&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;也许，用户体验最关键的地方是对话界面的设计。这里并不是在讨论HitL，而是人类作为应用程序的最终用户。会话式AI是通用人工智能与人工智能为工具的区别里的一个关键领域。不管是基于语音的AI还是会话式AI，其最初的目标可能是研发出一种可以进行智能的、开放式对话的产品。但是，事实证明，这很难做到，很明显，因为在缺乏生活经验或者常识的情况下，真正的对话是不可能实现的。&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;目前，OpenAI团队正在研究解决这个问题的一种特殊方法，即让机器在环境中使用语言来完成目标，但这在很大程度上还处于起步阶段。另一个方法便是其Cyc Project，目的是通过在一个庞大的数据库中存储事实和推理规则来给机器注入常识知识。实际上，这个项目始于20世纪80年代，几十年过去了，还是没有实现。&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;苹果Siri研究公司SRI International的Bill Mark在最近接受采访时表示，“我们不是为了通过图灵测试而构建这些系统。”他指出，在设计基于语音的系统时，需要务实并承认缺乏理解能力，以便解决限制问题并设计出真正有用的东西。&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;这是基于语音的人工智能系统的新目标，它需要的技能超出了人工智能研究员的能力。总的来说，它要求软件工程师和用户体验设计师与自然语言处理专家合作，来创造一些可以不那么聪明但却要很有用的东西。&lt;/p&gt;  &lt;img alt="640?wx_fmt=png" src="https://ss.csdn.net/p?https://mmbiz.qpic.cn/mmbiz_png/SWSB2QESy8hPiaicZriatKzHQ2ApgTYWuAOsnE3S0BLe6EibSNNsYzXY332EcEIeCywEyqp57tlz7UKCOFD5FZKsiaA/640?wx_fmt=png"&gt;&lt;/img&gt;  &lt;p&gt;   &lt;strong&gt;投稿、约访、合作，联系邮箱aiobservation@qq.com&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;添加微信xiaozhijun，更多交流或进AI观察团&lt;/strong&gt;&lt;/p&gt;  &lt;img alt="640?wx_fmt=gif" src="https://ss.csdn.net/p?https://mmbiz.qpic.cn/mmbiz_gif/SWSB2QESy8hPiaicZriatKzHQ2ApgTYWuAOWnmtHRVicA9mTn5NsZkibAYzh2iafFbXb9tQhUZnzb45qAicuhiaiaNgOF7Q/640?wx_fmt=gif"&gt;&lt;/img&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>geek</category>
      <guid isPermaLink="true">https://itindex.net/detail/59047-%E9%80%9A%E7%94%A8-agi-%E5%9E%82%E7%9B%B4</guid>
      <pubDate>Fri, 07 Dec 2018 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>新一代数据库TiDB在美团的实践</title>
      <link>https://itindex.net/detail/58996-%E4%BB%A3%E6%95%B0-tidb-%E7%BE%8E%E5%9B%A2</link>
      <description>&lt;div&gt;  &lt;h1&gt;   &lt;a&gt;&lt;/a&gt;1. 背景和现状&lt;/h1&gt;  &lt;p&gt;近几年，基于MySQL构建的传统关系型数据库服务，已经很难支撑美团业务的爆发式增长，这就促使我们去探索更合理的数据存储方案和实践新的运维方式。而随着分布式数据库大放异彩，美团DBA团队联合基础架构存储团队，于 2018 年初启动了分布式数据库项目。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" src="https://user-gold-cdn.xitu.io/2018/11/23/1673f209486d9e92?w=1064&amp;h=658&amp;f=png&amp;s=391477"&gt;&lt;/img&gt;图 1 美团点评产品展示图&lt;/p&gt;  &lt;p&gt;在立项之初，我们进行了大量解决方案的对比，深入了解了业界的 scale-out（横向扩展）、scale-up（纵向扩展）等解决方案。但考虑到技术架构的前瞻性、发展潜力、社区活跃度以及服务本身与 MySQL 的兼容性，我们最终敲定了基于   &lt;a href="https://www.pingcap.com/" rel="nofollow"&gt;TiDB&lt;/a&gt;数据库进行二次开发的整体方案，并与 PingCAP 官方和开源社区进行深入合作的开发模式。&lt;/p&gt;  &lt;p&gt;美团业务线众多，我们根据业务特点及重要程度逐步推进上线，到截稿为止，已经上线了 10 个集群，近 200 个物理节点，大部分是 OLTP 类型的应用，除了上线初期遇到了一些小问题，目前均已稳定运行。初期上线的集群，已经分别服务于配送、出行、闪付、酒旅等业务。虽然 TiDB 的架构分层相对比较清晰，服务也是比较平稳和流畅，但在美团当前的数据量规模和已有稳定的存储体系的基础上，推广新的存储服务体系，需要对周边工具和系统进行一系列改造和适配，从初期探索到整合落地，仍然还需要走很远的路。下面将从以下几个方面分别进行介绍：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;从 0 到 1 的突破，重点考虑做哪些事情。&lt;/li&gt;   &lt;li&gt;如何规划实施不同业务场景的接入和已有业务的迁移。&lt;/li&gt;   &lt;li&gt;上线后遇到的一些典型问题介绍。&lt;/li&gt;   &lt;li&gt;后续规划和对未来的展望。&lt;/li&gt;&lt;/ul&gt;  &lt;h1&gt;   &lt;a&gt;&lt;/a&gt;2. 前期调研测试&lt;/h1&gt;  &lt;h2&gt;   &lt;a&gt;&lt;/a&gt;2.1  对 TiDB 的定位&lt;/h2&gt;  &lt;p&gt;我们对于 TiDB 的定位，前期在于重点解决 MySQL 的单机性能和容量无法线性和灵活扩展的问题，与 MySQL 形成互补。业界分布式方案很多，我们为何选择了 TiDB 呢？考虑到公司业务规模的快速增长，以及公司内关系数据库以 MySQL 为主的现状，因此我们在调研阶段，对以下技术特性进行了重点考虑：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;协议兼容 MySQL：这个是必要项。&lt;/li&gt;   &lt;li&gt;可在线扩展：数据通常要有分片，分片要支持分裂和自动迁移，并且迁移过程要尽量对业务无感知。&lt;/li&gt;   &lt;li&gt;强一致的分布式事务：事务可以跨分片、跨节点执行，并且强一致。&lt;/li&gt;   &lt;li&gt;支持二级索引：为兼容 MySQL 的业务，这个是必须的。&lt;/li&gt;   &lt;li&gt;性能：MySQL 的业务特性，高并发的 OLTP 性能必须满足。&lt;/li&gt;   &lt;li&gt;跨机房服务：需要保证任何一个机房宕机，服务能自动切换。&lt;/li&gt;   &lt;li&gt;跨机房双写：支持跨机房双写是数据库领域一大难题，是我们对分布式数据库的一个重要期待，也是美团下一阶段重要的需求。&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;业界的一些传统方案虽然支持分片，但无法自动分裂、迁移，不支持分布式事务，还有一些在传统 MySQL 上开发一致性协议的方案，但它无法实现线性扩展，最终我们选择了与我们的需求最为接近的 TiDB。与 MySQL 语法和特性高度兼容，具有灵活的在线扩容缩容特性，支持 ACID 的强一致性事务，可以跨机房部署实现跨机房容灾，支持多节点写入，对业务又能像单机 MySQL 一样使用。&lt;/p&gt;  &lt;h2&gt;   &lt;a&gt;&lt;/a&gt;2.2  测试&lt;/h2&gt;  &lt;p&gt;针对官方声称的以上优点，我们进行了大量的研究、测试和验证。&lt;/p&gt;  &lt;p&gt;首先，我们需要知道扩容、Region 分裂转移的细节、Schema 到 KV 的映射、分布式事务的实现原理。而 TiDB 的方案，参考了较多的 Google 论文，我们进行了阅读，这有助于我们理解 TiDB 的存储结构、事务算法、安全性等，包括：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Spanner: Google’s Globally-Distributed Database&lt;/li&gt;   &lt;li&gt;Large-scale Incremental Processing Using Distributed Transactions and Notifications&lt;/li&gt;   &lt;li&gt;In Search of an Understandable Consensus Algorithm&lt;/li&gt;   &lt;li&gt;Online, Asynchronous Schema Change in F1&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;我们也进行了常规的性能和功能测试，用来与 MySQL 的指标进行对比，其中一个比较特别的测试，是证明 3 副本跨机房部署，确实能保证每个机房分布一个副本，从而保证任何一个机房宕机不会导致丢失超过半数副本。我们从以下几个点进行了测试：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Raft 扩容时是否支持 Learner 节点，从而保证单机房宕机不会丢失 2/3 的副本。&lt;/li&gt;   &lt;li&gt;TiKV 上的标签优先级是否可靠，保证当机房的机器不平均时，能否保证每个机房的副本数依然是绝对平均的。&lt;/li&gt;   &lt;li&gt;实际测试，单机房宕机，TiDB 在高并发下，QPS、响应时间、报错数量，以及最终数据是否有丢失。&lt;/li&gt;   &lt;li&gt;手动 Balance 一个 Region 到其他机房，是否会自动回来。&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;从测试结果来看，一切都符合我们的预期。&lt;/p&gt;  &lt;h1&gt;   &lt;a&gt;&lt;/a&gt;3. 存储生态建设&lt;/h1&gt;  &lt;p&gt;美团的产品线丰富，业务体量也比较大，业务对在线存储的服务质量要求也非常高。因此，从早期做好服务体系的规划非常重要。下面从业务接入层、监控报警、服务部署等维度，来分别介绍一下我们所做的工作。&lt;/p&gt;  &lt;h2&gt;   &lt;a&gt;&lt;/a&gt;3.1  业务接入层&lt;/h2&gt;  &lt;p&gt;当前 MySQL 的业务接入方式主要有两种，DNS 接入和 Zebra 客户端接入。在前期调研阶段，我们选择了 DNS + 负载均衡组件的接入方式，TiDB-Server 节点宕机，15s 可以被负载均衡识别到，简单且有效。业务架构如下图所示：&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" src="https://user-gold-cdn.xitu.io/2018/11/23/1673f21313209095?w=1994&amp;h=738&amp;f=jpeg&amp;s=147499"&gt;&lt;/img&gt;图 2 业务架构图&lt;/p&gt;  &lt;p&gt;后面，我们会逐渐过渡到当前大量使用的 Zebra 接入方式来访问 TiDB，从而保持与访问 MySQL 的方式一致，一方面减少业务改造的成本，另一方面尽量实现从 MySQL 到 TiDB 的透明迁移。&lt;/p&gt;  &lt;h2&gt;   &lt;a&gt;&lt;/a&gt;3.2  监控报警&lt;/h2&gt;  &lt;p&gt;美团目前使用 Mt-Falcon 平台负责监控报警，通过在 Mt-Falcon 上配置不同的插件，可以实现对多种组件的自定义监控。另外也会结合 Puppet 识别不同用户的权限、文件的下发。只要我们编写好插件脚本、需要的文件，装机和权限控制就可以完成了。监控架构如下图所示：&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" src="https://user-gold-cdn.xitu.io/2018/11/23/1673f21a63ef836c?w=1363&amp;h=838&amp;f=png&amp;s=91161"&gt;&lt;/img&gt;图 3 监控架构图&lt;/p&gt;  &lt;p&gt;而 TiDB 有丰富的监控指标，使用流行的 Prometheus + Grafana，一套集群有 700+ 的 Metric。从官方的架构图可以看出，每个组件会推送自己的 Metric 给 PushGateWay，Prometheus 会直接到 PushGateWay 去抓数据。&lt;/p&gt;  &lt;p&gt;由于我们需要组件收敛，原生的 TiDB 每个集群一套 Prometheus 的方式不利于监控的汇总、分析、配置，而报警已经在 Mt-Falcon 上实现的比较好了，在 AlertManager 上再造一个也没有必要。因此我们需要想办法把监控和报警汇总到 Mt-Falcon 上面，包括如下几种方式：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;方案一：修改源代码，将 Metric 直接推送到 Falcon，由于 Metric 散落在代码的不同位置，而且 TiDB 代码迭代太快，把精力消耗在不停调整监控埋点上不太合适。&lt;/li&gt;   &lt;li&gt;方案二：在 PushGateWay 是汇总后的，可以直接抓取，但 PushGateWay 是个单点，不好维护。&lt;/li&gt;   &lt;li&gt;方案三：通过各个组件（TiDB、PD、TiKV）的本地 API 直接抓取，优点是组件宕机不会影响其他组件，实现也比较简单。&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;我们最终选择了方案三。该方案的难点是需要把 Prometheus 的数据格式转化为 Mt-Falcon 可识别的格式，因为 Prometheus 支持 Counter、Gauge、Histogram、Summary 四种数据类型，而 Mt-Falcon 只支持基本的 Counter 和 Gauge，同时 Mt-Falcon 的计算表达式比较少，因此需要在监控脚本中进行转换和计算。&lt;/p&gt;  &lt;h2&gt;   &lt;a&gt;&lt;/a&gt;3.3  批量部署&lt;/h2&gt;  &lt;p&gt;TiDB 使用 Ansible 实现自动化部署。迭代快，是 TiDB 的一个特点，有问题能快速进行解决，但也造成 Ansible 工程、TiDB 版本更新过快，我们对 Ansible 的改动，也只会增加新的代码，不会改动已有的代码。因此线上可能同时需要部署、维护多个版本的集群。如果每个集群一个 Ansible 目录，造成空间的浪费。&lt;/p&gt;  &lt;p&gt;我们采用的维护方式是，在中控机中，每个版本一个 Ansible 目录，每个版本中通过不同 inventory 文件来维护。这里需要跟 PingCAP 提出的是，Ansible 只考虑了单集群部署，大量部署会有些麻烦，像一些依赖的配置文件，都不能根据集群单独配置（咨询官方得知，PingCAP 目前正在基于 Cloud TiDB 打造一站式 HTAP 平台，会提供批量部署、多租户等功能，后续会比较好地解决这个问题）。&lt;/p&gt;  &lt;h2&gt;   &lt;a&gt;&lt;/a&gt;3.4  自动化运维平台&lt;/h2&gt;  &lt;p&gt;随着线上集群数量的增加，打造运维平台提上了日程，而美团对 TiDB 和 MySQL 的使用方式基本相同，因此 MySQL 平台上具有的大部分组件，TiDB 平台也需要建设。典型的底层组件和方案：SQL 审核模块、DTS、数据备份方案等。自动化运维平台展示如下图所示：&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" src="https://user-gold-cdn.xitu.io/2018/11/23/1673f21f4557c0b5?w=837&amp;h=757&amp;f=jpeg&amp;s=100131"&gt;&lt;/img&gt;图 4 自动化运维平台展示图&lt;/p&gt;  &lt;h2&gt;   &lt;a&gt;&lt;/a&gt;3.5  上下游异构数据同步&lt;/h2&gt;  &lt;p&gt;TiDB 是在线存储体系中的一环，它同时也需要融入到公司现有的数据流中，因此需要一些工具来做衔接。PingCAP 官方标配了相关的组件。&lt;/p&gt;  &lt;p&gt;公司目前 MySQL 和 Hive 结合的比较重，而 TiDB 要代替 MySQL 的部分功能，需要解决 2 个问题：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;MySQL to TiDB    &lt;ul&gt;     &lt;li&gt;MySQL 到 TiDB 的迁移，需要解决数据迁移以及增量的实时同步，也就是 DTS，Mydumper + Loader 解决存量数据的同步，官方提供了 DM 工具可以很好的解决增量同步问题。&lt;/li&gt;     &lt;li&gt;MySQL 大量使用了自增 ID 作为主键。分库分表 MySQL 合并到 TiDB 时，需要解决自增 ID 冲突的问题。这个通过在 TiDB 端去掉自增 ID 建立自己的唯一主键来解决。新版 DM 也提供分表合并过程主键自动处理的功能。&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;   &lt;li&gt;Hive to TiDB &amp;amp; TiDB to Hive    &lt;ul&gt;     &lt;li&gt;Hive to TiDB 比较好解决，这体现了 TiDB 和 MySQL 高度兼容的好处，insert 语句可以不用调整，基于 Hive to MySQL 简单改造即可。&lt;/li&gt;     &lt;li&gt;TiDB to Hive 则需要基于官方 Pump + Drainer 组件，Drainer 可以消费到 Kafka、MySQL、TiDB，我们初步考虑用图 5 中的方案通过使用 Drainer 的 Kafka 输出模式同步到 Hive。&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;   &lt;img alt="" src="https://user-gold-cdn.xitu.io/2018/11/23/1673f225f998c749?w=1452&amp;h=528&amp;f=jpeg&amp;s=74860"&gt;&lt;/img&gt;图 5 TiDB to Hive 方案图&lt;/p&gt;  &lt;h1&gt;   &lt;a&gt;&lt;/a&gt;4. 线上使用磨合&lt;/h1&gt;  &lt;p&gt;对于初期上线的业务，我们比较谨慎，基本的原则是：离线业务 -&amp;gt; 非核心业务 -&amp;gt; 核心业务。TiDB 已经发布两年多，且前期经历了大量的测试，我们也深入了解了其它公司的测试和使用情况，可以预期的是 TiDB 上线会比较稳定，但依然遇到了一些小问题。总体来看，在安全性、数据一致性等关键点上没有出现问题。其他一些性能抖动问题，参数调优的问题，也都得到了快速妥善的解决。这里给 PingCAP 的同学点个大大的赞，问题响应速度非常快，与我们美团内部研发的合作也非常融洽。&lt;/p&gt;  &lt;h2&gt;   &lt;a&gt;&lt;/a&gt;4.1  写入量大、读 QPS 高的离线业务&lt;/h2&gt;  &lt;p&gt;我们上线的最大的一个业务，每天有数百 G 的写入量，在前期，我们也遇到了较多的问题。&lt;/p&gt;  &lt;p&gt;业务场景：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;稳定的写入，每个事务操作 100~200 行不等，每秒 6W 的数据写入。&lt;/li&gt;   &lt;li&gt;每天的写入量超过 500G，以后会逐步提量到每天 3T。&lt;/li&gt;   &lt;li&gt;每 15 分钟的定时读 Job，5000 QPS（高频量小）。&lt;/li&gt;   &lt;li&gt;不定时的查询（低频量大）。&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;之前使用 MySQL 作为存储，但 MySQL 到达了容量和性能瓶颈，而业务的容量未来会 10 倍的增长。初期调研测试了 ClickHouse，满足了容量的需求，测试发现运行低频 SQL 没有问题，但高频 SQL 的大并发查询无法满足需求，只在 ClickHouse 跑全量的低频 SQL 又会 overkill，最终选择使用 TiDB。&lt;/p&gt;  &lt;p&gt;测试期间模拟写入了一天的真实数据，非常稳定，高频低频两种查询也都满足需求，定向优化后 OLAP 的 SQL 比 MySQL 性能提高四倍。但上线后，陆续发现了一些问题，典型的如下：&lt;/p&gt;  &lt;h3&gt;   &lt;a&gt;&lt;/a&gt;4.1.1  TiKV 发生 Write Stall&lt;/h3&gt;  &lt;p&gt;TiKV 底层有 2 个 RocksDB 作为存储。新写的数据写入 L0 层，当 RocksDB 的 L0 层数量达到一定数量，就会发生减速，更高则发生 Stall，用来自我保护。TiKV 的默认配置：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;level0-slowdown-writes-trigger = 20&lt;/li&gt;   &lt;li&gt;level0-stop-writes-trigger = 36&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;遇到过的，发生 L0 文件过多可能的原因有 2 个：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;写入量大，Compact 完不成。&lt;/li&gt;   &lt;li&gt;Snapshot 一直创建不完，导致堆积的副本一下释放，RocksDB-Raft 创建大量的 L0 文件，监控展示如下图所示：&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;   &lt;img alt="" src="https://user-gold-cdn.xitu.io/2018/11/23/1673f22ddfff9db2?w=1383&amp;h=596&amp;f=jpeg&amp;s=84802"&gt;&lt;/img&gt;   &lt;br /&gt;图 6 TiKV 发生 Write Stall 监控展示图&lt;/p&gt;  &lt;p&gt;我们通过以下措施，解决了 Write Stall 的问题：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;减缓 Raft Log Compact 频率（增大 raft-log-gc-size-limit、raft-log-gc-count-limit）&lt;/li&gt;   &lt;li&gt;加快 Snapshot 速度（整体性能、包括硬件性能）&lt;/li&gt;   &lt;li&gt;max-sub-compactions 调整为 3&lt;/li&gt;   &lt;li&gt;max-background-jobs 调整为 12&lt;/li&gt;   &lt;li&gt;level 0 的 3 个 Trigger 调整为 16、32、64&lt;/li&gt;&lt;/ul&gt;  &lt;h3&gt;   &lt;a&gt;&lt;/a&gt;4.1.2  Delete 大量数据，GC 跟不上&lt;/h3&gt;  &lt;p&gt;现在 TiDB 的 GC 对于每个 kv-instance 是单线程的，当业务删除数据的量非常大时，会导致 GC 速度较慢，很可能 GC 的速度跟不上写入。&lt;/p&gt;  &lt;p&gt;目前可以通过增多 TiKV 个数来解决，长期需要靠 GC 改为多线程执行，官方对此已经实现，即将发布。&lt;/p&gt;  &lt;h3&gt;   &lt;a&gt;&lt;/a&gt;4.1.3  Insert 响应时间越来越慢&lt;/h3&gt;  &lt;p&gt;业务上线初期，insert 的响应时间 80 线（Duration 80 By Instance）在 20ms 左右，随着运行时间增加，发现响应时间逐步增加到 200ms+。期间排查了多种可能原因，定位在由于 Region 数量快速上涨，Raftstore 里面要做的事情变多了，而它又是单线程工作，每个 Region 定期都要 heartbeat，带来了性能消耗。tikv-raft propose wait duration 指标持续增长。&lt;/p&gt;  &lt;p&gt;解决问题的办法：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;临时解决。&lt;/li&gt;   &lt;li&gt;增加 Heartbeat 的周期，从 1s 改为 2s，效果比较明显，监控展示如下图所示：&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;   &lt;img alt="" src="https://user-gold-cdn.xitu.io/2018/11/23/1673f2327537f19e?w=1920&amp;h=428&amp;f=jpeg&amp;s=103123"&gt;&lt;/img&gt;图 7 insert 响应时间优化前后对比图&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;彻底解决。&lt;/li&gt;   &lt;li&gt;需要减少 Region 个数，Merge 掉空 Region，官方在 2.1 版本中已经实现了 Region Merge 功能，我们在升级到 2.1 后，得到了彻底解决。&lt;/li&gt;   &lt;li&gt;另外，等待 Raftstore 改为多线程，能进一步优化。（官方回复相关开发已基本接近尾声，将于 2.1 的下一个版本发布。）&lt;/li&gt;&lt;/ul&gt;  &lt;h3&gt;   &lt;a&gt;&lt;/a&gt;4.1.4  Truncate Table 空间无法完全回收&lt;/h3&gt;  &lt;p&gt;DBA Truncate 一张大表后，发现 2 个现象，一是空间回收较慢，二是最终也没有完全回收。&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;由于底层 RocksDB 的机制，很多数据落在 Level 6 上，有可能清不掉。这个需要打开 cdynamic-level-bytes 会优化 Compaction 的策略，提高 Compact 回收空间的速度。&lt;/li&gt;   &lt;li&gt;由于 Truncate 使用 delete_files_in_range 接口，发给 TiKV 去删 SST 文件，这里只删除不相交的部分，而之前判断是否相交的粒度是 Region，因此导致了大量 SST 无法及时删除掉。&lt;/li&gt;   &lt;li&gt;考虑 Region 独立 SST 可以解决交叉问题，但是随之带来的是磁盘占用问题和 Split 延时问题。&lt;/li&gt;   &lt;li&gt;考虑使用 RocksDB 的 DeleteRange 接口，但需要等该接口稳定。&lt;/li&gt;   &lt;li&gt;目前最新的 2.1 版本优化为直接使用 DeleteFilesInRange 接口删除整个表占用的空间，然后清理少量残留数据，目前已经解决。&lt;/li&gt;&lt;/ul&gt;  &lt;h3&gt;   &lt;a&gt;&lt;/a&gt;4.1.5  开启 Region Merge 功能&lt;/h3&gt;  &lt;p&gt;为了解决 region 过多的问题，我们在升级 2.1 版本后，开启了 region merge 功能，但是 TiDB 的响应时间 80 线（Duration 80 By Instance）依然没有恢复到当初，保持在 50ms 左右，排查发现 KV 层返回的响应时间还很快，和最初接近，那么就定位了问题出现在 TiDB 层。研发人员和 PingCAP 定位在产生执行计划时行为和 2.0 版本不一致了，目前已经优化。&lt;/p&gt;  &lt;h2&gt;   &lt;a&gt;&lt;/a&gt;4.2  在线 OLTP，对响应时间敏感的业务&lt;/h2&gt;  &lt;p&gt;除了分析查询量大的离线业务场景，美团还有很多分库分表的场景，虽然业界有很多分库分表的方案，解决了单机性能、存储瓶颈，但是对于业务还是有些不友好的地方：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;业务无法友好的执行分布式事务。&lt;/li&gt;   &lt;li&gt;跨库的查询，需要在中间层上组合，是比较重的方案。&lt;/li&gt;   &lt;li&gt;单库如果容量不足，需要再次拆分，无论怎样做，都很痛苦。&lt;/li&gt;   &lt;li&gt;业务需要关注数据分布的规则，即使用了中间层，业务心里还是没底。&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;因此很多分库分表的业务，以及即将无法在单机承载而正在设计分库分表方案的业务，主动找到了我们，这和我们对于 TiDB 的定位是相符的。这些业务的特点是 SQL 语句小而频繁，对一致性要求高，通常部分数据有时间属性。在测试及上线后也遇到了一些问题，不过目前基本都有了解决办法。&lt;/p&gt;  &lt;h3&gt;   &lt;a&gt;&lt;/a&gt;4.2.1  SQL  执行超时后，JDBC 报错&lt;/h3&gt;  &lt;p&gt;业务偶尔报出 privilege check fail。&lt;/p&gt;  &lt;p&gt;是由于业务在 JDBC 设置了 QueryTimeout，SQL 运行超过这个时间，会发行一个   &lt;code&gt;kill query&lt;/code&gt;命令，而 TiDB 执行这个命令需要 Super 权限，业务是没有权限的。其实 kill 自己的查询，并不需要额外的权限，目前已经解决了这个问题：   &lt;br /&gt;   &lt;a href="https://github.com/pingcap/tidb/pull/7003" rel="nofollow"&gt;https://github.com/pingcap/tidb/pull/7003&lt;/a&gt;，不再需要 Super 权限，已在 2.0.5 上线。&lt;/p&gt;  &lt;h3&gt;   &lt;a&gt;&lt;/a&gt;4.2.2  执行计划偶尔不准&lt;/h3&gt;  &lt;p&gt;TiDB 的物理优化阶段需要依靠统计信息。在 2.0 版本统计信息的收集从手动执行，优化为在达到一定条件时可以自动触发：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;数据修改比例达到 tidb_auto_analyze_ratio。&lt;/li&gt;   &lt;li&gt;表一分钟没有变更（目前版本已经去掉这个条件）。&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;但是在没有达到这些条件之前统计信息是不准的，这样就会导致物理优化出现偏差，在测试阶段（2.0 版本）就出现了这样一个案例：业务数据是有时间属性的，业务的查询有 2 个条件，比如：时间+商家 ID，但每天上午统计信息可能不准，当天的数据已经有了，但统计信息认为没有。这时优化器就会建议使用时间列的索引，但实际上商家 ID 列的索引更优化。这个问题可以通过增加 Hint 解决。&lt;/p&gt;  &lt;p&gt;在 2.1 版本对统计信息和执行计划的计算做了大量的优化，也稳定了基于 Query Feedback 更新统计信息，也用于更新直方图和 Count-Min Sketch，非常期待 2.1 的 GA。&lt;/p&gt;  &lt;h1&gt;   &lt;a&gt;&lt;/a&gt;5. 总结展望&lt;/h1&gt;  &lt;p&gt;经过前期的测试、各方的沟通协调，以及近半年对 TiDB 的使用，我们看好 TiDB 的发展，也对未来基于 TiDB 的合作充满信心。&lt;/p&gt;  &lt;p&gt;接下来，我们会加速推进 TiDB 在更多业务系统中的使用，同时也将 TiDB 纳入了美团新一代数据库的战略选型中。当前，我们已经全职投入了 3 位 DBA 同学和多位存储计算专家，从底层的存储，中间层的计算，业务层的接入，再到存储方案的选型和布道，进行全方位和更深入的合作。&lt;/p&gt;  &lt;p&gt;长期来看，结合美团不断增长的业务规模，我们将与 PingCAP 官方合作打造更强大的生态体系：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Titan：Titan 是 TiDB 下一步比较大的动作，也是我们非常期待的下一代存储引擎，它对大 Value 支持会更友好，将解决我们单行大小受限，单机 TiKV 最大支持存储容量的问题，大大提升大规模部署的性价比。&lt;/li&gt;   &lt;li&gt;Cloud TiDB （Based on Docker &amp;amp; K8s）：云计算大势所趋，PingCAP 在这块也布局比较早，今年 8 月份开源了 TiDB Operator，Cloud TiDB 不仅实现了数据库的高度自动化运维，而且基于 Docker 硬件隔离，实现了数据库比较完美的多租户架构。我们和官方同学沟通，目前他们的私有云方案在国内也有重要体量的 POC，这也是美团看重的一个方向。&lt;/li&gt;   &lt;li&gt;TiDB HTAP Platform：PingCAP 在原有 TiDB Server 计算引擎的基础上，还构建 TiSpark 计算引擎，和他们官方沟通，他们在研发了一个基于列的存储引擎，这样就形成了下层行、列两个存储引擎、上层两个计算引擎的完整混合数据库（HTAP），这个架构不仅大大的节省了核心业务数据在整个公司业务周期里的副本数量，还通过收敛技术栈，节省了大量的人力成本、技术成本、机器成本，同时还解决了困扰多年的 OLAP 的实效性。后面我们也会考虑将一些有实时、准实时的分析查询系统接入 TiDB。&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;   &lt;img alt="" src="https://user-gold-cdn.xitu.io/2018/11/23/1673f2399a3943d5?w2700&amp;h=1494&amp;f=jpeg&amp;s=426680"&gt;&lt;/img&gt;图 8 TiDB HTAP Platform 整体架构图&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;后续的物理备份方案，跨机房多写等也是我们接下来逐步推进的场景，总之，我们坚信未来 TiDB 在美团的使用场景会越来越多，发展也会越来越好。&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;目前，TiDB 在业务层面、技术合作层面都已经在美团扬帆起航，美团点评将携手 PingCAP 开启新一代数据库深度实践、探索之旅。后续，还有美团点评架构存储团队针对 TiDB 源码研究和改进的系列文章，敬请期待。&lt;/p&gt;  &lt;h2&gt;   &lt;a&gt;&lt;/a&gt;作者简介&lt;/h2&gt;  &lt;p&gt;应钢，美团点评研究员，数据库专家。曾就职于百度、新浪、去哪儿网等，10年数据库自动化运维开发、数据库性能优化、大规模数据库集群技术保障和架构优化经验。精通主流的SQL与NoSQL系统，现专注于公司业务在NewSQL领域的创新和落地。&lt;/p&gt;  &lt;p&gt;李坤，2018年初加入美团，美团点评数据库专家，多年基于MySQL、Hbase、Oracle的架构设计和维护、自动化开发经验，目前主要负责分布式数据库Blade的推动和落地，以及平台和周边组件的建设&lt;/p&gt;  &lt;p&gt;昌俊，美团点评数据库专家，曾就职于BOCO、去哪儿网，6年MySQL DBA从业经历，积累了丰富的数据库架构设计和性能优化、自动化开发经验。目前专注于TiDB在美团点评业务场景的改造和落地。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" src="https://user-gold-cdn.xitu.io/2018/11/23/1673f23e9eb4a5cb?w=1875&amp;h=835&amp;f=png&amp;s=142461"&gt;&lt;/img&gt;&lt;/p&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>geek</category>
      <guid isPermaLink="true">https://itindex.net/detail/58996-%E4%BB%A3%E6%95%B0-tidb-%E7%BE%8E%E5%9B%A2</guid>
      <pubDate>Fri, 23 Nov 2018 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>谈Elasticsearch下分布式存储的数据分布</title>
      <link>https://itindex.net/detail/58923-elasticsearch-%E5%88%86%E5%B8%83-%E6%95%B0%E6%8D%AE</link>
      <description>&lt;div&gt;  &lt;p&gt;  对于一个分布式存储系统来说，数据是分散存储在多个节点上的。如何让数据均衡的分布在不同节点上，来保证其高可用性？所谓均衡，是指系统中每个节点的负载是均匀的，并且在发现有不均匀的情况或者有节点增加/删除时，能及时进行调整，保持均匀状态。本文将探讨Elasticsearch的数据分布方法，文中所述的背景是Elasticsearch 5.5。   &lt;br /&gt;  在Elasticsearch中，以Shard为最小的数据分配/迁移单位。数据到节点的映射分离为两层：一层是数据到Shard的映射（   &lt;strong&gt;Route&lt;/strong&gt;），另一层是Shard到节点的映射（   &lt;strong&gt;Allocate&lt;/strong&gt;）。&lt;/p&gt;  &lt;div align="center"&gt;   &lt;img src="https://img-blog.csdnimg.cn/20181030105739475.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p3Z2RmdA==,size_30,color_FFFFFF,t_70" width="650"&gt;&lt;/img&gt;&lt;/div&gt;  &lt;p&gt;  一方面，插入一条数据时，ES会根据指定的Key来计算应该落到哪个Shard上。默认Key是自动分配的id，可以自定义，比如在我们的业务中采用CompanyID作为Key。因为Primary Shard的个数是不允许改变的，所以同一个Key每次算出来的Shard是一样的，从而保证了准确定位。&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;shard_num = hash(_routing) % num_primary_shards&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;  另一方面，Master会为每个Shard分配相应的Data节点进行存储，并维护相关元信息。通过Route计算出来的Shard序号，在元信息中找到对应的存储节点，便可完成数据分布。Shard Allocate的映射关系并不是完全不变的，当检测到数据分布不均匀、有新节点加入或者有节点挂掉等情况时就会进行调整，称为   &lt;strong&gt;Relocate&lt;/strong&gt;。那么，Elasticsearch是根据什么规则来为Shard选取节点，从而保证数据均衡分布的？概括来看，主要有三方面的影响：   &lt;strong&gt;节点位置&lt;/strong&gt;、   &lt;strong&gt;磁盘空间&lt;/strong&gt;、   &lt;strong&gt;单个节点上的Index和Shard个数&lt;/strong&gt;。&lt;/p&gt;  &lt;h4&gt;   &lt;a&gt;&lt;/a&gt;节点位置&lt;/h4&gt;  &lt;p&gt;   对于一个ES节点来说，它可能是某台物理机器上的一个VM，而这个物理机器位于某个Zone的某个机架（Rack)上。通过将Primary Shard和Replica Shard分散在不同的物理机器、Rack、Zone，可以尽可能的降低数据丢失和系统不可用的风险，这一点几乎在所有的分布式系统中都会考量。&lt;/p&gt;  &lt;div align="center"&gt;   &lt;img src="https://img-blog.csdnimg.cn/20181030105752197.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p3Z2RmdA==,size_30,color_FFFFFF,t_70" width="600"&gt;&lt;/img&gt;&lt;/div&gt;  &lt;p&gt;   Elasticsearch是通过设置awareness.attribute对集群中的节点进行分组，从而实现Rack和Zone的发现。比如按照下列方式对elasticsearch.yml进行配置，再启动相应的节点，即可实现Zone的区分。&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;// elasticsearch.yml
cluster.routing.allocation.awareness.attributes: zone

// 启动ES
./bin/elasticsearch -Enode.attr.zone=zone_one
./bin/elasticsearch -Enode.attr.zone=zone_two&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;  实践中，如果使用了这样的Awareness机制，应该保证不同分组类的机器个数一致，不会发生倾斜。比如，在Zone Awareness下，如果集群有10台机器，应该保证每个Zone各有5台机器（2个Zone）。&lt;/p&gt;  &lt;h4&gt;   &lt;a&gt;&lt;/a&gt;磁盘空间&lt;/h4&gt;  &lt;p&gt;  磁盘空间是制约存储的硬性条件，单机的可用磁盘空间决定了能否继续往这个节点写入新数据、分配新Shard以及是否需要迁移数据等。在ES中，有三个参数用来控制与此相关的特性，默认每30秒检查一次。&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;cluster.routing.allocation.disk.watermark.low: 默认为85%，超过这个阈值后，就不允许往这个节点分配Shard。&lt;/li&gt;   &lt;li&gt;cluster.routing.allocation.disk.watermark.high：默认为90%，超过这个阈值后，就需要将该节点的Shard迁移出去。&lt;/li&gt;   &lt;li&gt;cluster.routing.allocation.disk.watermark.flood_stage：默认为95%，超过这个阈值后，与该节点上的Shard有关的Index都变成只读，不允许写入数据。&lt;/li&gt;&lt;/ul&gt;  &lt;div align="center"&gt;   &lt;img src="https://img-blog.csdnimg.cn/20181030105759927.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p3Z2RmdA==,size_30,color_FFFFFF,t_70" width="450"&gt;&lt;/img&gt;&lt;/div&gt;  &lt;h4&gt;   &lt;a&gt;&lt;/a&gt;单个节点上的Index和Shard个数&lt;/h4&gt;  &lt;p&gt;  在满足节点位置和磁盘空间的条件后，单个节点上的Index和Shard个数是否均匀，决定了Shard可以分配/迁移到哪个节点。ES通过计算权值来量化这样的分配方式。   &lt;br /&gt;  以检测某个Shard是否需要迁移到其他节点为例，ES会先计算该Shard所在节点（A）的权值，然后依次跟其他节点的权值比较，如果与节点B的差值（Delta-A）超过了阈值，再进一步计算节点A去掉该Shard后的权值与节点B增加该Shard后的权值之间的差值（Delta-B），如果Delta-A大于Delta-B，则表明Shard可以迁移到节点B。   &lt;br /&gt;  这里的权值计算简化如下，其中indexBalance与shardBalance分别由参数控制，而阈值由cluster.routing.allocation.balance.threshold设置，默认为1.0f。当然，这里只描述了核心思想，详细逻辑请阅读   &lt;a href="https://github.com/elastic/elasticsearch/blob/master/server/src/main/java/org/elasticsearch/cluster/routing/allocation/allocator/BalancedShardsAllocator.java" rel="nofollow"&gt;BalancedShardsAllocator.java&lt;/a&gt;中的源码。通过调整三个参数，可以控制策略的松紧。&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;// indexBalance = cluster.routing.allocation.balance.index, default is 0.55f
// shardBalance = cluster.routing.allocation.balance.shard, default is 0.45f

float sum = indexBalance + shardBalance;
float theta0 = shardBalance / sum;
float theta1 = indexBalance / sum;

private float weight(Balancer balancer, ModelNode node, String index, int numAdditionalShards) {
    final float weightShard = node.numShards() + numAdditionalShards - balancer.avgShardsPerNode();
    final float weightIndex = node.numShards(index) + numAdditionalShards - balancer.avgShardsPerNode(index);
    return theta0 * weightShard + theta1 * weightIndex;
}&lt;/code&gt;&lt;/pre&gt;  &lt;h4&gt;   &lt;a&gt;&lt;/a&gt;Primary与Replica分布&lt;/h4&gt;  &lt;p&gt;  最初关注Elasticsearch的数据分布，是因为在性能调优时遇到了一个与Primary/Replica分布有关的问题。背景是这样的，为了能够复用单个节点上的Disk Cache，我们对查询请求进行了限制，只允许其访问Primary Shard。然而总是有那么一两台机器的查询会被Queue住，通过调研发现，这些机器上面的Primary Shard比其他机器多（对某一个Index而言），即下图中左边所示，而我们希望的是右图所示的均匀分布。   &lt;br /&gt;  引起这个问题的根源是，Elasticsearch中的Shard均匀分布是针对Primary+Replica整体而言的，也就是说没法做到只针对Primary Shard单方面做均匀分布，所以才会出现下图左边所示，某个节点上有3个Primary Shard，而另一个节点只有1个。目前尚未发现可以调节的地方。&lt;/p&gt;  &lt;div align="center"&gt;   &lt;img src="https://img-blog.csdnimg.cn/20181030105812916.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p3Z2RmdA==,size_30,color_FFFFFF,t_70" width="500"&gt;&lt;/img&gt;&lt;/div&gt;  &lt;br /&gt;  &lt;p&gt;  本文探讨了Elasticsearch的数据分布方法，其思想对很多其他分布式存储系统是通用的，而了解相关原理是做很多调优工作的前提。&lt;/p&gt;  &lt;br /&gt;  &lt;p&gt;（全文完，本文地址：   &lt;a href="https://blog.csdn.net/zwgdft/article/details/83478241" rel="nofollow"&gt;https://blog.csdn.net/zwgdft/article/details/83478241&lt;/a&gt;）   &lt;br /&gt;（版权声明：本人拒绝不规范转载，所有转载需征得本人同意，并且不得更改文字与图片内容。大家相互尊重，谢谢！）&lt;/p&gt;  &lt;p&gt;Bruce   &lt;br /&gt;2018/10/30 晚&lt;/p&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>geek</category>
      <guid isPermaLink="true">https://itindex.net/detail/58923-elasticsearch-%E5%88%86%E5%B8%83-%E6%95%B0%E6%8D%AE</guid>
      <pubDate>Wed, 31 Oct 2018 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>35 岁的程序员将何去何从——阮一峰</title>
      <link>https://itindex.net/detail/58824-%E7%A8%8B%E5%BA%8F%E5%91%98</link>
      <description>&lt;div&gt;  &lt;blockquote&gt;   &lt;p&gt;作者：阮一峰，IT 技术作家，长期写作个人技术博客。当过高校教师，也当过阿里巴巴集团软件工程师。曾出版译著《黑客与画家》《软件随想录》，技术专著《ES6 标准入门》。&lt;/p&gt;&lt;/blockquote&gt;  &lt;h3&gt;   &lt;a&gt;&lt;/a&gt;（一）&lt;/h3&gt;  &lt;p&gt;2017年初，网上传言华为公司正在清理34岁以上的员工。&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;中国区开始集中清理 34+ 的交付员工，……去向是跟海外服务部门交换今年新毕业的校招员工，也就是进新人，出旧人。&lt;/p&gt;   &lt;p&gt;这些旧人要被输出去海外，实际上就是变相裁员，这些30多岁的老杆子，英语又不好，拖家带口，能去海外安心奋斗的没几个，即使出去了幸存的也不多。&lt;/p&gt;&lt;/blockquote&gt;  &lt;p&gt;华为公司否认该传言。但是，不久以后又有传言称，45岁必须退休。&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;为保持公司年轻化，退休政策即将微调，从“45岁可以退休”改变为“45岁须退休”，想继续工作的，需人力资源部重新审批。&lt;/p&gt;&lt;/blockquote&gt;  &lt;p&gt;一时间，网上议论纷纷：34 岁清理一批，45 岁强制离职，这是什么样的人事政策啊！&lt;/p&gt;  &lt;h3&gt;   &lt;a&gt;&lt;/a&gt;（二）&lt;/h3&gt;  &lt;p&gt;我不讨论这个消息的真假，因为我也不知道。我只指出一个事实：IT 行业是一个年轻人的行业。&lt;/p&gt;  &lt;p&gt;随便哪一家 IT 公司，你去参观，主要员工都是年轻人，40岁以上的很少见，高管往往也是20出头的年轻人。长久以来，一直有人问：“40岁以上的程序员都去哪里了？”&lt;/p&gt;  &lt;p&gt;老员工在这个行业是稀有动物，从 35 岁开始，数量急剧减少，年龄越大越稀有。网上的传言只是从一个侧面验证了大家的这种感觉。&lt;/p&gt;  &lt;h3&gt;   &lt;a&gt;&lt;/a&gt;（三）&lt;/h3&gt;  &lt;p&gt;为什么 IT 公司都是年轻人的天下？我认为主要原因有两个。&lt;/p&gt;  &lt;p&gt;首先，工作强度太大了。IT 公司的加班是家常便饭，业务越忙、加班时间越长。很多团队都采用 “996” 工作制：早上 9 点上班，晚上 9 点下班，每周六天。杭州有一家全世界最著名的电子商务公司，天天半夜 12 点，办公楼灯火通明，门口等着接生意的出租车排成一长队。有一项统计《2016 年 IT 公司加班时间排行榜》，华为排在第一位，平均每个工作日加班 3.96 小时，第二位是腾讯，加班 3.92 小时。&lt;/p&gt;  &lt;p&gt;人的生理和智能的最高峰是 20 岁～30 岁这个年龄段。过了 30 岁，身体就慢慢走下坡路了，思维也不如以前活跃了。年轻的时候，长年累月的加班或许还可以承受，等进入中年，再这样拼，你的身体吃得消吗？加班好比折旧，加班越凶，折旧越快。我见过很多程序员刚过 30岁，但看上去好像40岁，长期缺乏运动，工作压力大，使得他们的身体有着各种疾病，实际上已经不能承担高强度的工作或者 deadline（截止期）的赶工压力了。&lt;/p&gt;  &lt;p&gt;另一方面，即使你可以咬紧牙关撑下去，家里人答应吗？父母和妻儿天天看不到你，他们能受得了？万一父母住院，或者小孩在幼儿园被其他同学打了，你能不闻不问，继续全部心思扑在工作上？&lt;/p&gt;  &lt;h3&gt;   &lt;a&gt;&lt;/a&gt;（四）&lt;/h3&gt;  &lt;p&gt;IT 公司缺少老员工的第二个原因是，这个行业变化太快了，老员工没优势。&lt;/p&gt;  &lt;p&gt;老员工的优势是经验和人脉，可是在 IT 行业，这两样东西都不是特别重要，新事物层出不穷，旧事物没多久就无人问津。最近的行业热点，共享单车、直播、VR、区块链、O2O……都是新事物，史无前例，大家都没经验。谁占领了市场，谁就成了标准。而且，新事物的目标受众往往主要是青年，他们接受新事物的程度最快最高，用“90后”去设计产品、打开市场，可能比使用“70后”有效得多。&lt;/p&gt;  &lt;p&gt;这个行业里面，起决定性作用的是新技术。技术进步的速度，比市场的变化还快，每年都有大量新技术出来。你 22 岁大学毕业入职，等到10年过去了，32 岁时你大学里面学到的东西都没用了，你必须和新人一样从头开始学习新技术。你也许会说，怎么可能都没用呢，难道微积分、统计学、编程原理这些都没用了吗？问题是新人也学过这些啊，而且他们刚刚学，不像你已经忘得差不多了。&lt;/p&gt;  &lt;h3&gt;   &lt;a&gt;&lt;/a&gt;（五）&lt;/h3&gt;  &lt;p&gt;企业发现，新人可以全身心投入工作，连续加班，可塑性高，又比较听话，不像老员工，资格太老而变得油滑，一有不满就公开抱怨，或者上班时间经常到吸烟区吞云吐雾。&lt;/p&gt;  &lt;p&gt;更糟糕的是，老员工的工资比新员工高得多。如果老员工没有能力优势，反而拿着比新人高几倍的工资，对企业来说，应该怎么做，就不言自明了。&lt;/p&gt;  &lt;p&gt;中国的 IT 企业里面，第一线员工干到 34 岁时，大概已经拼搏了 10   &lt;br /&gt;年以上，再拼命有点力不从心了。对企业来说，该员工最能创造价值的巅峰也已经过去了。等他到了 45 岁，如果还没有当上高管，很可能已经创造不了价值了，再留着他反而可能有负作用，让企业变成松松跨跨的养老院。&lt;/p&gt;  &lt;p&gt;这就是为什么大家觉得传言可信，因为符合逻辑。如果员工都是奋斗者，你说谁更容易有奋斗精神，25 岁还是 45 岁？&lt;/p&gt;  &lt;p&gt;好在 IT 行业的工资现在还是不错的，即使 45 岁退休了，生活水平也不会一时下降太多。如果能够拿到公司的股票，而公司又非常成功，那么可能不用等到 45 岁，你自己早早就走了，享受生活不再为人打工了。&lt;/p&gt;  &lt;p&gt;马云在湖畔大学给企业家上课，第一课就说：“小公司的成败在于你聘请什么样的人，大公司的成败在于你开除什么样的人。大公司里有很多老白兔，不干活，并且慢慢会传染更多的人。”可见最成功的企业家早就认可这种做法。&lt;/p&gt;  &lt;h3&gt;   &lt;a&gt;&lt;/a&gt;（六）&lt;/h3&gt;  &lt;p&gt;我估计，34 岁之前晋升到中级，45 岁之前晋升到高管，否则强制退休，会成为 IT 行业的惯例。随着其他行业正在日益变成“互联网 +”，这种做法还有向其他行业扩展的可能。&lt;/p&gt;  &lt;p&gt;这就提出了一个很严峻的问题。如果你没有在期限之前晋升到中级或高管，那么到 45 岁就没工作了，你该怎么办？45 岁还是一个很有活力的年龄，就这样退休，对社会是人力资源浪费，对个人也很残酷。可是，那时你找得到工作吗？或者即使找到工作，你还能拿到原来岗位的那种报酬吗？&lt;/p&gt;  &lt;p&gt;现实是非常残酷的。一个名叫“蓝血研究”的公众号里，贴出过一篇据说是内部员工的文章。首先，作者表示理解公司的做法。&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;年龄大、股票多的员工消耗了华为大量的成本，公司要清理这样的员工为年轻一代释放出更大的空间，这是极其合理的事情。而且大部分的老员工基本已经财务自由，就算没有完全自由，保留的股票也基本可以衣食无忧。&lt;/p&gt;&lt;/blockquote&gt;  &lt;p&gt;但是，现实情况却是“强制退休或者不续约的，都不是年龄大和股票多的高成本员工，反而是在华为兢兢业业十来年，考评普通职级一般，收入和股票都偏低的那一群人”。&lt;/p&gt;  &lt;p&gt;作者认为很多高职级的管理者更应该被裁掉。&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;高管真的是那么不可或缺吗？我真不觉得是这样，很多情况下恰恰相反。当前很多高管其实离业务很远，让他们去做一件基层员工的事情，他们反而是做不了的，自己也没有做什么高大上的战略性的事情，每天就是开会开会开会，分配任务。大部分都貌似很忙，每天都开会到很晚，但是真的对业务有多大帮助呢？大家心里都有一杆秤！一边是我们的基层大龄扎实贡献的员工被裁被退休，一边是管理者的岗位职级嗖地往上涨，公平何在？公理何在？&lt;/p&gt;&lt;/blockquote&gt;  &lt;blockquote&gt;   &lt;p&gt;一个 19 级的管理者的年收入大概是他所管理的 15 级员工的 5 倍，但是 19 级的贡献度真的是 15 级的 5 倍吗？以现在的体制，把 15 级的人扔到 19 级的岗位上，该部门会出大乱子吗？业务就会因此出现毁灭性打击吗？还是说 15 级经过几个月适应，竟然也能干 19 级的岗位。到底是谁的报酬过多？到底是谁应该被退休？&lt;/p&gt;&lt;/blockquote&gt;  &lt;h3&gt;   &lt;a&gt;&lt;/a&gt;（七）&lt;/h3&gt;  &lt;p&gt;我觉得，每个人都应该想一想，你的雇主如果没有你，是不是就会有重大损失？一个新人或更基层的员工接手你的岗位，他能不能上手，而他要求的报酬又会是多少？&lt;/p&gt;  &lt;p&gt;技术的进步让人类活得更长、更健康，但也让我们变得不那么有用了。将来也许每个人都要选择两次自己的人生：一次是大学毕业找工作时，另一次是 45 岁没有工作时。&lt;/p&gt;  &lt;p&gt;   &lt;a href="https://gitbook.cn/gitchat/geekbook/5b4c65b4573703037fc226ea?utm_source=Coder1_07" rel="nofollow"&gt;    &lt;img alt="alt text" src="https://images.gitbook.cn/Fpb7tuftX5LP4UEwpr-J6gOqCfo3"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>geek</category>
      <guid isPermaLink="true">https://itindex.net/detail/58824-%E7%A8%8B%E5%BA%8F%E5%91%98</guid>
      <pubDate>Mon, 01 Oct 2018 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>AI 在携程智能客服的应用</title>
      <link>https://itindex.net/detail/58627-ai-%E6%90%BA%E7%A8%8B-%E6%99%BA%E8%83%BD</link>
      <description>&lt;div&gt;  &lt;p&gt;作为国内 OTA 的领头羊，携程每天都在服务着成千上万的旅行者。为了保障旅行者的出行，庞大的携程客服在其中扮演着十分重要的角色。但在客服的日常工作中，有一部分的行为是重复劳动，这对于客服来说是一种资源浪费。如何通过算法来提升客服效率成为技术一大挑战。&lt;/p&gt;  &lt;p&gt;本场 Chat 将介绍智能算法如何辅助客服工作，并介绍QA问答背后的技术和难题，以及如何用机器学习和深度学习在提升用户体验和客服效率上进行落地。&lt;/p&gt;  &lt;p&gt;本场 Chat 主要内容：&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;QA Chatbot 背后的技术细节和难题；&lt;/li&gt;   &lt;li&gt;精细化运营——AI算法如何提升用户体验；&lt;/li&gt;   &lt;li&gt;文本挖掘算法如何辅助客服工作。&lt;/li&gt;&lt;/ol&gt;  &lt;hr&gt;&lt;/hr&gt;  &lt;p&gt;作者简介：元凌峰，携程平台中心 AI 研发部资深算法工程师，负责携程智能客服算法研发，对 Chatbot 相关的 NLP 算法和推荐排序等算法感兴趣。2015年硕士毕业于上海交通大学图像模式研究所，后加入携程负责实时用户意图和小诗机等项目。&lt;/p&gt;  &lt;h3&gt;概述&lt;/h3&gt;  &lt;p&gt;作为国内 OTA 的领头羊，携程每天都在服务着成千上万的旅行者。为了保障旅行者的出行，庞大的携程客服在其中扮演着十分重要的角色。但在客服的日常工作中，有很大一部分的行为是重复劳动，这对于客服来说是一种资源浪费。如何从客服工作中解放生产力，提高生产效率成为技术需要解决的一大难题。&lt;/p&gt;  &lt;p&gt;随着近几年深度学习算法突破和硬件的升级，人工智能技术在多个领域遍地开花，其中一大应用场景便是智能客服。2017 年初，携程开始大力推进客服智能化的技术研发，目前在酒店售后客服场景，智能客服已经能够解决 70 % 的问题，不仅提升了客服效率，在服务响应方面也有很大提升。&lt;/p&gt;  &lt;p&gt;那么，机器学习或者深度学习在客服领域到底能做什么？怎么做？本文将围绕这两方面介绍携程在智能客服领域中的一些实践经验。&lt;/p&gt;  &lt;p&gt;我们先回答这些算法在客服领域到底能做什么。&lt;/p&gt;  &lt;p&gt;回答这个问题要回到客服聊天工具这个产品本身，用户在使用客服聊天工具时，最希望的是能够第一时间在客服界面上看到自己想咨询的问题，然后直接找到答案。如果第一眼没有看到想要的问题，那就希望在和 “ 客服 ” 交互过程中以最少的交互次数获取到需要的答案。&lt;/p&gt;  &lt;p&gt;在这个过程中，算法不外乎要做的就是两件事：   &lt;strong&gt;猜你所想，答你所问。&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;我们先说猜这件事，类似推荐，在用户还没有做出任何输入时，我们会根据用户的信息、当前上下文信息以及咨询的产品信息来猜测用户进入咨询界面时想问什么问题，从而得到一堆问题的排序展示给用户。&lt;/p&gt;  &lt;p&gt;如果第一步没有猜到用户想要的问题，用户就会通过输入框来简单描述自己的情况和想要咨询的问题，在用户输入的过程中，我们也会结合用户输入的内容通过算法来实时猜测用户可能咨询的问题，并以 input suggestion 的方式给到用户。若上述都无法让用户找到自己想要的答案，那就是答这件事要解决的。&lt;/p&gt;  &lt;p&gt;我们会采用 QA 模型对用户发送的话术内容进行分析和匹配，得到用户可能想问的问题，并反馈给用户。这样就完成了一个对话回合，但其实除了上述提到的几个点以外，还有很多地方需要算法参与，比如问题挖掘、关联问题推荐以及上下文对话等等。&lt;/p&gt;  &lt;p&gt;下面我们就重点介绍几种常用的算法如何发挥作用。&lt;/p&gt;  &lt;h3&gt;Question - Answer Match&lt;/h3&gt;  &lt;p&gt;标准 Q 匹配模型是机器人进行交互的基础模型，对匹配率的要求较高。传统的做法直接根据关键词检索或 BM25 等算法计算相关性排序，但这些方法缺点是需要维护大量的同义词典库和匹配规则。后来发展出潜在语义分析技术（Latent Semantic Analysis，LSA[1,2]），将词句映射到低维连续空间，可在潜在的语义空间上计算相似度。&lt;/p&gt;  &lt;p&gt;接着又有了 PLSA（Probabilistic Latent Semantic Analysis）[3]、LDA（Latent Dirichlet Allocation）[4]等概率模型，形成非常火热的浅层主题模型技术方向。这些算法对文本的语义表示形式简洁，较好地弥补了传统词汇匹配方法的不足。不过从效果上来看，这些技术都无法完全替代基于字词的匹配技术。&lt;/p&gt;  &lt;p&gt;随着深度学习技术兴起后，基于神经网络训练的 Word2vec [5]来进行文本匹配计算引起了广泛的兴趣，而且所得的词语向量表示的语义可计算性进一步加强。但是无监督的 Word2vec 在句子匹配度计算的实用效果上还是存在不足，而且本身没有解决短语、句子的语义表示问题。&lt;/p&gt;  &lt;p&gt;因此，研究者们开始研究句子级别上的神经网络语言模型，例如 Microsoft Research 提出的 DSSM 模型（Deep Structured Semantic Model）[6]，华为实验室也提出了一些新的神经网络匹配模型变体[7,8,9]，如基于二维交互匹配的卷积匹配模型。中科院等研究机构也提出了诸如多视角循环神经网络匹配模型（MV - LSTM）[10]、基于矩阵匹配的的层次化匹配模型 MatchPyramid [11]等更加精致的神经网络文本匹配模型。虽然模型的结构非常多种，但底层结构单元基本以全链接层、LSTM、卷积层、池化层为主（参考论文[12,13,14,15]的做法）。&lt;/p&gt;  &lt;h4&gt;分类和排序&lt;/h4&gt;  &lt;p&gt;在语义模型的训练框架里，大致可以分为两类：分类和排序。&lt;/p&gt;  &lt;p&gt;采用分类的方法，一般最后一层接的是多类别的 softmax，即输入是用户 Q，分类结果是所属的标准 Q 类别。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="enter image description here" src="https://images.gitbook.cn/e659ea70-9ae9-11e8-bfdd-f715bfe07a77"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;h5&gt;   &lt;strong&gt;Figure 2 基于分类的模型结构&lt;/strong&gt;&lt;/h5&gt;  &lt;p&gt;排序学习有三种类型：point - wise，pair - wise 和 list - wise。在QA中我们常用的是 point - wise 和 pair - wise。其中 point - wise 的方法直接把问题转换成二分类，判断当前用户问题是否属于带匹配的问题，最后根据隶属概率值可以得到问题的排序。&lt;/p&gt;  &lt;p&gt;而 pair - wise 学习的是(uq,sq+)和(uq,sq-)两两之间的排序关系，训练目标是最大化正样本对和负样本对的距离：&lt;/p&gt;  &lt;p&gt;max⁡L=||f(uq,q+ )-f(uq,q- )||d&lt;/p&gt;  &lt;p&gt;其中f(·)表示某种距离度量。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="enter image description here" src="https://images.gitbook.cn/eb70b610-9ae9-11e8-bfdd-f715bfe07a77"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;h5&gt;   &lt;strong&gt;Figure 3 基于 point - wise 的模型结构&lt;/strong&gt;&lt;/h5&gt;  &lt;p&gt;   &lt;img alt="enter image description here" src="https://images.gitbook.cn/f1820e50-9ae9-11e8-bfdd-f715bfe07a77"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;h5&gt;   &lt;strong&gt;Figure 4 基于 pair - wise 的模型结构&lt;/strong&gt;&lt;/h5&gt;  &lt;ul&gt;   &lt;li&gt;单轮和上下文&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;一个流畅的会话是需要机器人拥有上下文多轮对话的能力，然而目前大部分 QA 机器人都是基于单轮匹配，如果用户继续上一个问题补充提问，单轮 QA 模型则会断章取义，无法准确识别当前句的准确意图。因此需要把上下文信息进行表示计算，实现多轮匹配模型。&lt;/p&gt;  &lt;p&gt;提到多轮对话场景，大家都会想到 Task 任务式对话。但二者在上下文表示方面还是存在一些差异。Task（goal - driven system）是根据预定义的槽位和状态来表示上下文，并且依照某个业务逻辑的对话管理策略（也可以通过 POMDP [16]的方法来构建策略）来引导用户到想要搜索的内容。而 QA（non - goal - driven system）不是面向槽管理的，而是根据用户会话意图来调整对话过程。&lt;/p&gt;  &lt;p&gt;在 QA 的上下文会话管理方法中，大致可分为两个方向，一个是 Rule - Based [17]的上下文模型；另一个是 Model - Based [18,19]的上下文模型。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="enter image description here" src="https://images.gitbook.cn/be3a4f30-9ae9-11e8-bfdd-f715bfe07a77"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;Rule - Based&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;通过预定义一些先验知识来表示上下文，在会话中不断修改上下文的先验知识并根据上下文记录信息来重排序。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;Model - Based&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;Model - Based 相对于 Rule - Based 的好处就是能够提升泛化能力。&lt;/p&gt;  &lt;p&gt;有研究者[18,19]就利用模型这种特性，把上下文信息表征在向量里，并通过层次化模型来学习和推断。&lt;/p&gt;  &lt;p&gt;该模型主要有三个结构：句子级 encoder 模型、context 级别 encoder 模型以及 response decoder 模型。其中 context encoder 模型是解决上下文信息的关键，整个模型用数学模型表示如下：&lt;/p&gt;  &lt;p&gt;   &lt;img alt="enter image description here" src="https://images.gitbook.cn/b7e5d370-9ae9-11e8-bfdd-f715bfe07a77"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;基于我们的场景对该模型稍作修改并训练后得到效果如下图。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="enter image description here" src="https://images.gitbook.cn/b1febf80-9ae9-11e8-bfdd-f715bfe07a77"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;   &lt;img alt="enter image description here" src="https://images.gitbook.cn/ac303ca0-9ae9-11e8-bfdd-f715bfe07a77"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;h3&gt;Input suggestion&lt;/h3&gt;  &lt;p&gt;输入联想在搜索引擎或者商品搜索中有着十分重要的应用场景，但客服场景的联想提示又区别于搜索，用户的输入是各式各样的，联想结果不一定和用户输入的在词语上完全一致，只需语义上一致即可，而且是能够猜中用户意图。下面聊聊我们在该场景迭代的三种算法。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="enter image description here" src="https://images.gitbook.cn/a5965370-9ae9-11e8-bfdd-f715bfe07a77"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;第一版本采用最常用的 Trie 树结构。字典树的结构简单实用。在搜索中，可以把所有的候选词条建立一个字典树，然后根据用户输入的前缀到 Trie 树中检索候选集，展示给用户。该结构优点是简单有效，能够快速上线。但缺点就是召回率较低，这是因为字典树要求用户输入的词语必须和候选集合里的短语句子要有一致的前缀。对此我们也做了泛化优化，例如去除掉停用词或者无意义词语等，尽可能提高召回。但提升有限。&lt;/p&gt;  &lt;p&gt;第二阶段，我们直接采用 point - wise 的排序模型。因为第一阶段的数据积累和人工标注，我们已经有了一定的历史曝光点击数据。考虑到线上联想的请求性能要求较高，我们先尝试了逻辑回归模型。&lt;/p&gt;  &lt;p&gt;在特征方面，我们主要抽取了三类特征：一类是基于 word2vec 得到的句子特征，另一类是传统的 TF - IDF 特征，最后一类是重要词汇特征（这类特征是通过数据挖掘得到的对应场景的重要词）。&lt;/p&gt;  &lt;p&gt;该模型上线后整体的使用率比字典树有了明显提升，尤其召回率大幅度提高。&lt;/p&gt;  &lt;p&gt;但是，第二阶段的模型仍然存在很多缺陷，我们把线上的曝光未点击数据分析了下，发现如下问题：&lt;/p&gt;  &lt;p&gt;-线上存在很多拼音汉字混搭的case，模型没有解决能力；   &lt;br /&gt;-用户输入的话术存在很多错别字；   &lt;br /&gt;-联想请求场景以超短文本为主，大都集中在 2 - 6 个字 。&lt;/p&gt;  &lt;p&gt;微软的 DSSM 模型在解决短文本语义匹配上有很好的效果，该模型主要亮点引入了 word hashing 操作，该操作能够很好的解决了 OOV（out of vocabulary）问题。其次就是深度神经网络增强了特征的表达能力，该模型计算图如下：&lt;/p&gt;  &lt;p&gt;   &lt;img alt="enter image description here" src="https://images.gitbook.cn/9c837b00-9ae9-11e8-bfdd-f715bfe07a77"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;该模型上线后带来显著提升，整个输入联想场景的迭代效果如图。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="enter image description here" src="https://images.gitbook.cn/9507bd50-9ae9-11e8-bfdd-f715bfe07a77"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;h3&gt;总结&lt;/h3&gt;  &lt;p&gt;限于篇幅，除了上述提到的几种场景，机器学习和深度学习算法还在其他多个场景中辅助携程客服的工作，帮助提升客服工作效率和用户体验。&lt;/p&gt;  &lt;p&gt;人工智能并不是新兴的黑科技，只不过近几年深度学习的快速发展让这个词重新活跃在大众视野中。尼尔逊教授对人工智能下了这样一个定义：“人工智能是关于知识的学科――怎样表示知识以及怎样获得知识并使用知识的科学。”&lt;/p&gt;  &lt;p&gt;我们更愿意把人工智能看成是人工 + 智能算法 + 数据的一个综合体。算法工程师的定义不是简简单单懂算法就可以，而是要懂得如何用算法去优化人工提高效率，如何用算法去挖掘有效信息，紧密地让数据、算法、人工形成一个闭环。&lt;/p&gt;  &lt;h4&gt;Reference&lt;/h4&gt;  &lt;p&gt;[1] Dennis S, Landauer T, Kintsch W, et al. Introduction to latent semantic analysis[C]//Slides from the tutorial given at the 25th Annual Meeting of the Cognitive Science Society, Boston. 2003.[2] Deerwester S, Dumais S T, Furnas G W, et al. Indexing by latent semantic analysis[J]. Journal of the American society for information science, 1990, 41(6): 391-407.[3] Hofmann T. Unsupervised learning by probabilistic latent semantic analysis[J]. Machine learning, 2001, 42(1-2): 177-196.[4] Blei D M, Ng A Y, Jordan M I. Latent dirichlet allocation[J]. Journal of machine Learning research, 2003, 3(Jan): 993-1022.[5] Mikolov T, Chen K, Corrado G, et al. Efficient estimation of word representations in vector space[J]. arXiv preprint arXiv:1301.3781, 2013.[6] Huang P S, He X, Gao J, et al. Learning deep structured semantic models for web search using clickthrough data[C]//Proceedings of the 22nd ACM international conference on Conference on information &amp;amp; knowledge management. ACM, 2013: 2333-2338.[7] Lu Z, Li H. A deep architecture for matching short texts[C]//Advances in Neural Information Processing Systems. 2013: 1367-1375.[8] Ji Z, Lu Z, Li H. An information retrieval approach to short text conversation[J]. arXiv preprint arXiv:1408.6988, 2014.[9] Hu B, Lu Z, Li H, et al. Convolutional neural network architectures for matching natural language sentences[C]//Advances in neural information processing systems. 2014: 2042-2050.[10] Wan,Shengxian, Yanyan Lan, Jiafeng Guo, Jun Xu, Liang Pang, and Xueqi Cheng.&amp;quot;A Deep Architecture for Semantic Matching with Multiple PositionalSentence Representations.&amp;quot; In AAAI, pp. 2835-2841. 2016.[11] Pang,Liang, Yanyan Lan, Jiafeng Guo, Jun Xu, Shengxian Wan, and Xueqi Cheng.&amp;quot;Text Matching as Image Recognition.&amp;quot; In AAAI, pp. 2793-2799. 2016.s[12] Feng M, Xiang B, Glass M R, et al. Applying deep learning to answer selection: A study and an open task[J]. arXiv preprint arXiv:1508.01585, 2015.[13] Lai S, Xu L, Liu K, et al. Recurrent Convolutional Neural Networks for Text Classification[C]//AAAI. 2015, 333: 2267-2273.[14] Santos C, Tan M, Xiang B, et al. Attentive pooling networks[J]. arXiv preprint arXiv:1602.03609, 2016.[15] Kim Y. Convolutional neural networks for sentence classification[J]. arXiv preprint arXiv:1408.5882, 2014.[16] Young S, Gašić M, Thomson B, et al. Pomdp-based statistical spoken dialog systems: A review[J]. Proceedings of the IEEE, 2013, 101(5): 1160-1179.[17] Langley P, Meadows B, Gabaldon A, et al. Abductive understanding of dialogues about joint activities[J]. Interaction Studies, 2014, 15(3): 426-454.[18] Serban I V, Sordoni A, Bengio Y, et al. Building End-To-End Dialogue Systems Using Generative Hierarchical Neural Network Models[C]//AAAI. 2016, 16: 3776-3784.[19] Sordoni A, Galley M, Auli M, et al. A neural network approach to context-sensitive generation of conversational responses[J]. arXiv preprint arXiv:1506.06714, 2015.&lt;/p&gt;  &lt;p&gt;阅读全文:   &lt;a href="http://gitbook.cn/gitchat/activity/5b502b40f5b40e2a9fc8d402?utm_source=csdn_blog" rel="nofollow"&gt;http://gitbook.cn/gitchat/activity/5b502b40f5b40e2a9fc8d402&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;一场场看太麻烦？订阅GitChat体验卡，畅享300场chat文章！更有CSDN下载、CSDN学院等超划算会员权益！   &lt;a href="https://download.csdn.net/vip_code?utm_source=vip_blog" rel="nofollow"&gt;点击查看&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>geek</category>
      <guid isPermaLink="true">https://itindex.net/detail/58627-ai-%E6%90%BA%E7%A8%8B-%E6%99%BA%E8%83%BD</guid>
      <pubDate>Wed, 15 Aug 2018 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>干货 | Elasticsearch Reindex性能提升10倍+实战</title>
      <link>https://itindex.net/detail/58624-%E5%B9%B2%E8%B4%A7-elasticsearch-reindex</link>
      <description>&lt;div&gt;  &lt;h1&gt;1、reindex的速率极慢，是否有办法改善？&lt;/h1&gt;  &lt;p&gt;以下问题来自社区：   &lt;a href="https://elasticsearch.cn/question/3782" rel="nofollow"&gt;https://elasticsearch.cn/question/3782&lt;/a&gt;&lt;/p&gt;  &lt;h2&gt;问题1：reindex和snapshot的速率极慢，是否有办法改善？&lt;/h2&gt;  &lt;p&gt;reindex和snapshot的速率比用filebeat或者kafka到es的写入速率慢好几个数量级（集群写入性能不存在瓶颈），reindex/snapshot的时候CPU还是IO使用率都很低，是不是集群受什么参数限制了reindex和snapshot的速率？   &lt;br /&gt;reindex不管是跨集群还是同集群上都很慢，大约3~5M/s的索引速率，会是什么原因导致的？&lt;/p&gt;  &lt;h2&gt;问题2：数据量几十个G的场景下，elasticsearch reindex速度太慢，从旧索引导数据到新索引，当前最佳方案是什么？&lt;/h2&gt;  &lt;h1&gt;2、Reindex简介&lt;/h1&gt;  &lt;p&gt;5.X版本后新增Reindex。Reindex可以直接在Elasticsearch集群里面对数据进行重建，如果你的mapping因为修改而需要重建，又或者索引设置修改需要重建的时候，借助Reindex可以很方便的异步进行重建，并且支持跨集群间的数据迁移。比如按天创建的索引可以定期重建合并到以月为单位的索引里面去。当然索引里面要启用_source。&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;POST _reindex
{&amp;quot;source&amp;quot;: {&amp;quot;index&amp;quot;:&amp;quot;twitter&amp;quot;},&amp;quot;dest&amp;quot;: {&amp;quot;index&amp;quot;:&amp;quot;new_twitter&amp;quot;}
}&lt;/code&gt;&lt;/pre&gt;  &lt;h1&gt;3、原因分析&lt;/h1&gt;  &lt;p&gt;reindex的核心做跨索引、跨集群的数据迁移。   &lt;br /&gt;慢的原因及优化思路无非包括：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;1）批量大小值可能太小。    &lt;br /&gt;需要结合堆内存、线程池调整大小；&lt;/li&gt;   &lt;li&gt;2）reindex的底层是scroll实现，借助scroll并行优化方式，提升效率；&lt;/li&gt;   &lt;li&gt;3）跨索引、跨集群的核心是写入数据，考虑写入优化角度提升效率。&lt;/li&gt;&lt;/ul&gt;  &lt;h1&gt;4、Reindex提升迁移效率的方案&lt;/h1&gt;  &lt;h2&gt;4.1 提升批量写入大小值&lt;/h2&gt;  &lt;p&gt;默认情况下，_reindex使用1000进行批量操作，您可以在source中调整batch_size。&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;POST _reindex
{&amp;quot;source&amp;quot;: {&amp;quot;index&amp;quot;:&amp;quot;source&amp;quot;,&amp;quot;size&amp;quot;:5000},&amp;quot;dest&amp;quot;: {&amp;quot;index&amp;quot;:&amp;quot;dest&amp;quot;,&amp;quot;routing&amp;quot;:&amp;quot;=cat&amp;quot;}
}&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;批量大小设置的依据：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;strong&gt;（1）使用批量索引请求以获得最佳性能。&lt;/strong&gt;    &lt;br /&gt;批量大小取决于数据、分析和集群配置，但一个好的起点是每批处理5-15 MB。    &lt;br /&gt;注意，这是物理大小。文档数量不是度量批量大小的好指标。例如，如果每批索引1000个文档，:    &lt;br /&gt;1）每个1kb的1000个文档是1mb。    &lt;br /&gt;2）每个100kb的1000个文档是100 MB。    &lt;br /&gt;这些是完全不同的体积大小。&lt;/li&gt;   &lt;li&gt;    &lt;strong&gt;（2）逐步递增文档容量大小的方式调优。&lt;/strong&gt;    &lt;br /&gt;1）从大约5-15 MB的大容量开始，慢慢增加，直到你看不到性能的提升。然后开始增加批量写入的并发性(多线程等等)。    &lt;br /&gt;2）使用kibana、cerebro或iostat、top和ps等工具监视节点，以查看资源何时开始出现瓶颈。如果您开始接收EsRejectedExecutionException，您的集群就不能再跟上了:至少有一个资源达到了容量。要么减少并发性，或者提供更多有限的资源(例如从机械硬盘切换到ssd固态硬盘)，要么添加更多节点。&lt;/li&gt;&lt;/ul&gt;  &lt;h2&gt;4.2 借助scroll的sliced提升写入效率&lt;/h2&gt;  &lt;p&gt;Reindex支持Sliced Scroll以并行化重建索引过程。 这种并行化可以提高效率，并提供一种方便的方法将请求分解为更小的部分。&lt;/p&gt;  &lt;h3&gt;sliced原理（from medcl）&lt;/h3&gt;  &lt;p&gt;1）用过Scroll接口吧，很慢？如果你数据量很大，用Scroll遍历数据那确实是接受不了，现在Scroll接口可以并发来进行数据遍历了。   &lt;br /&gt;2）每个Scroll请求，可以分成多个Slice请求，可以理解为切片，各Slice独立并行，利用Scroll重建或者遍历要快很多倍。&lt;/p&gt;  &lt;h3&gt;slicing使用举例&lt;/h3&gt;  &lt;p&gt;slicing的设定分为两种方式：手动设置分片、自动设置分片。   &lt;br /&gt;手动设置分片参见官网。   &lt;br /&gt;自动设置分片如下：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;POST _reindex?slices=5&amp;amp;refresh{&amp;quot;source&amp;quot;: {&amp;quot;index&amp;quot;:&amp;quot;twitter&amp;quot;},&amp;quot;dest&amp;quot;: {&amp;quot;index&amp;quot;:&amp;quot;new_twitter&amp;quot;}
}&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;slices大小设置注意事项：   &lt;br /&gt;1）slices大小的设置可以手动指定，或者设置slices设置为auto，auto的含义是：针对单索引，slices大小=分片数；针对多索引，slices=分片的最小值。   &lt;br /&gt;2）当slices的数量等于索引中的分片数量时，查询性能最高效。slices大小大于分片数，非但不会提升效率，反而会增加开销。   &lt;br /&gt;3）如果这个slices数字很大(例如500)，建议选择一个较低的数字，因为过大的slices 会影响性能。&lt;/p&gt;  &lt;h2&gt;4.3 ES副本数设置为0&lt;/h2&gt;  &lt;p&gt;如果要进行大量批量导入，请考虑通过设置index.number_of_replicas来禁用副本：0。   &lt;br /&gt;主要原因在于：复制文档时，将整个文档发送到副本节点，并逐字重复索引过程。 这意味着每个副本都将执行分析，索引和潜在合并过程。   &lt;br /&gt;相反，如果您使用零副本进行索引，然后在提取完成时启用副本，则恢复过程本质上是逐字节的网络传输。 这比复制索引过程更有效。&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;PUT /my_logs/_settings
{&amp;quot;number_of_replicas&amp;quot;:1}&lt;/code&gt;&lt;/pre&gt;  &lt;h2&gt;4.4 增加refresh间隔&lt;/h2&gt;  &lt;p&gt;如果你的搜索结果不需要接近实时的准确性，考虑先不要急于索引刷新refresh。可以将每个索引的refresh_interval到30s。   &lt;br /&gt;如果正在进行大量数据导入，可以通过在导入期间将此值设置为-1来禁用刷新。完成后不要忘记重新启用它!   &lt;br /&gt;设置方法：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;PUT /my_logs/_settings
{&amp;quot;refresh_interval&amp;quot;: -1}&lt;/code&gt;&lt;/pre&gt;  &lt;h1&gt;5、小结&lt;/h1&gt;  &lt;p&gt;实践证明，比默认设置reindex速度能提升10倍+。   &lt;br /&gt;遇到类似问题，多从官网、原理甚至源码的角度思考，逐步拆解分析。   &lt;br /&gt;只要思维不滑坡，办法总比问题多！&lt;/p&gt;  &lt;p&gt;参考：   &lt;br /&gt;[1] Jest Reindex参考：   &lt;a href="http://t.cn/RDOyIc8" rel="nofollow"&gt;http://t.cn/RDOyIc8&lt;/a&gt;   &lt;br /&gt;[2] 官网性能优化：   &lt;a href="http://t.cn/RDOyJqr" rel="nofollow"&gt;http://t.cn/RDOyJqr&lt;/a&gt;   &lt;br /&gt;[3] 论坛讨论：   &lt;a href="http://t.cn/RDOya3a" rel="nofollow"&gt;http://t.cn/RDOya3a&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;   &lt;img alt="&amp;#36825;&amp;#37324;&amp;#20889;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://img-blog.csdn.net/20180729135256407?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dvaml1c2hpd285ODc=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70" title=""&gt;&lt;/img&gt;   &lt;br /&gt;打造Elasticsearch基础、进阶、实战第一公众号！&lt;/p&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>geek</category>
      <guid isPermaLink="true">https://itindex.net/detail/58624-%E5%B9%B2%E8%B4%A7-elasticsearch-reindex</guid>
      <pubDate>Tue, 14 Aug 2018 00:00:00 CST</pubDate>
    </item>
  </channel>
</rss>

