速度与压缩比如何兼得?压缩算法在构建部署中的优化

速度与压缩比如何兼得?压缩算法在构建部署中的优化

背景 通常而言,服务发布平台的构建部署的流程(镜像部署除外)会经过构建(同步代码 -> 编译 -> 打包 -> 上传)、部署(下载包 -> 解压到目标机器 -> 重启服务)等步骤。以美团内部的发布平台 Plus 为例,最近我们发现一些发布项在构建产物打包压缩的过程中耗时比较久。如下图所示的 pack 步骤,一共消耗了1分23秒。 而在平常为用户解答运维问题的时候我们也发现,很多用户会习惯将一些较大的机器学习或者 NLP 相关的数据放入到仓库中,这部分数据往往占据几百兆,甚至占据几个GB的磁盘空间,十分影响打包的速度。 Java 项目也是如此,由于 Java 服务框架繁多,依赖也多,通常这些服务打包后也要占据百兆级别的空间,耗时也会达到十多秒。下图是我们的 pack 步骤的中位数,基本上大部分的 Java 服务和 Node.js 服务都至少要消耗 13s 左右的时间来做压缩打包 。 pack 作为几乎所有需要部署的服务必需步骤,它目前的耗时基本上仅低于编译和构建镜像,因此,为了提高整体构建的效率,我们准备对 pack 打包压缩的步骤进行一轮优化工作。 方案对比 准备场景数据 发布项的包大小分析 为了尽可能地模拟构建部署中的应用场景,我们将 2020 年的部分构建包数据进行了整理分析,其中压缩后的包大小如下图所示,钟形曲线说明了整体的包体积呈正态分布,并且有着较明显的长尾效应。压缩后体积主要在 200M 以内,压缩前的大小大致在 516.0MB 以内。 而 99%的服务压缩包大小会在 1GB 以内,而对于压缩步骤而言,其实越大的项目耗时越明显,优化的空间越大。因此,我们在针对性的方案对比测试中选择了 1GB 左右的构建包进行压缩测试,既能覆盖 99% 的场景,也可以看出压缩算法之间比较明显的提升。 这样选择的主要原因如下: 数据大的情况下计算结果会比小数据误差小很多。 能够覆盖绝大多数应用场景。 效果对比明显,可以看到是否有明显的提升。 备注:由于在相同压缩库相同压缩比等配置的情况下,Compression Speed 并没有明显变化,因此没有做其它包体积的批量测试和数据汇总。 本文中我们使用的测试项目为美团内部的较大型的 C++ 项目,其中文件类型除去 C++、Python、Shell 代码文件,还有 NLP、工具等二进制数据(不包括 .git 中存储的提交数据),数据类型比较全面。 目录大小为 1.2G,也可以比较清晰地对比出不同方案之间的差距。 gzip gzip 是基于 DEFLATE 的算法,它是 LZ77 和 Huffman 编码 的结合。DEFLATE 的目的是为了取代 LZW 和其他受专利保护的数

踩到一个关于分布式锁的非比寻常的BUG!

踩到一个关于分布式锁的非比寻常的BUG!

提到分布式锁,大家一般都会想到 Redis。 想到 Redis,一部分同学会说到 Redisson。 那么说到 Redisson,就不得不掰扯掰扯一下它的“看门狗”机制了。 所以你以为这篇文章我要给你讲“看门狗”吗? 不是,我主要是想给你汇报一下我最近研究的由于引入“看门狗”之后,给 Redisson 带来的两个看起来就菊花一紧的 bug : 看门狗不生效的 BUG。 看门狗导致死锁的 BUG。 为了能让你丝滑入戏,我还是先简单的给你铺垫一下,Redisson 的看门狗到底是个啥东西。 看门狗描述 你去看 Redisson 的 wiki 文档,在锁的这一部分,开篇就提到了一个单词:watchdog https://github.com/redisson/redisson/wiki/8.-distributed-locks-and-synchronizers watchdog,就是看门狗的意思。 它是干啥用的呢? 好的,如果你回答不上来这个问题。那当你遇到下面这个面试题的时候肯定懵逼。 面试官:请问你用 Redis 做分布式锁的时候,如果指定过期时间到了,把锁给释放了。但是任务还未执行完成,导致任务再次被执行,这种情况你会怎么处理呢? 这个时候,99% 的面试官想得到的回答都是看门狗,或者一种类似于看门狗的机制。 如果你说:这个问题我遇到过,但是我就是把过期时间设置的长一点。 时间到底设置多长,是你一个非常主观的判断,设置的长一点,能一定程度上解决这个问题,但是不能完全解决。 所以,请回去等通知吧。 或者你回答:这个问题我遇到过,我不设置过期时间,由程序调用 unlock 来保证。 好的,程序保证调用 unlock 方法没毛病,这是在程序层面可控、可保证的。但是如果你程序运行的服务器刚好还没来得及执行 unlock 就宕机了呢,这个你不能打包票吧? 这个锁是不是就死锁了? 所以...... 为了解决前面提到的过期时间不好设置,以及一不小心死锁的问题,Redisson 内部基于时间轮,针对每一个锁都搞了一个定时任务,这个定时任务,就是看门狗。 在 Redisson 实例被关闭前,这个狗子可以通过定时任务不断的延长锁的有效期。 因为你根本就不需要设置过期时间,这样就从根本上解决了“过期时间不好设置”的问题。默认情况下,看门狗的检查锁的超时时间是 30 秒钟,也可以通过修改参数来另行指定。 如果很不幸,节点宕机了导致没有执行 unlock,那么在默认的配置下最长 30s 的时间后,这个锁就自动释放了。 那么问题来了,面试官紧接着来一个追问:怎么自动释放呢? 这个时候,你只需要来一个战术后仰:程序都没了,你觉得定时任务还在吗?定时任务都不在了,所

Netty 实现长连接服务的难点和优化点

Netty 实现长连接服务的难点和优化点

推送服务 还记得一年半前,做的一个项目需要用到 Android 推送服务。和 iOS 不同,Android 生态中没有统一的推送服务。Google 虽然有 Google Cloud Messaging ,但是连国外都没统一,更别说国内了,直接被墙。 所以之前在 Android 上做推送大部分只能靠轮询。而我们之前在技术调研的时候,搜到了 jPush 的博客,上面介绍了一些他们的技术特点,他们主要做的其实就是移动网络下的长连接服务。单机 50W-100W 的连接的确是吓我一跳!后来我们也采用了他们的免费方案,因为是一个受众面很小的产品,所以他们的免费版够我们用了。一年多下来,运作稳定,非常不错! 时隔两年,换了部门后,竟然接到了一项任务,优化公司自己的长连接服务端。 再次搜索网上技术资料后才发现,相关的很多难点都被攻破,网上也有了很多的总结文章,单机 50W-100W 的连接完全不是梦,其实人人都可以做到。但是光有连接还不够,QPS 也要一起上去。 所以,这篇文章就是汇总一下利用 Netty 实现长连接服务过程中的各种难点和可优化点。 Netty 是什么 Netty: http://netty.io/ Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. 官方的解释最精准了,其中最吸引人的就是高性能了。但是很多人会有这样的疑问:直接用 NIO 实现的话,一定会更快吧?就像我直接手写 JDBC 虽然代码量大了点,但是一定比 iBatis 快! 但是,如果了解 Netty 后你才会发现,这个还真不一定! 利用 Netty 而不用 NIO 直接写的优势有这些: 高性能高扩展的架构设计,大部分情况下你只需要关注业务而不需要关注架构 Zero-Copy 技术尽量减少内存拷贝 为 Linux 实现 Native 版 Socket 写同一份代码,兼容 java 1.7 的 NIO2 和 1.7 之前版本的 NIO Pooled Buffers 大大减轻 Buffer 和释放 Buffer 的压力 …… 特性太多,大家可以去看一下《Netty in Action》这本书了解更多。 另外,Netty 源码是一本很好的教科书!大家在使用的过程中可以多看看它的源码,非常棒! 瓶颈是什么 想要做一个长链服务的话,最终的目标是什么?而它的瓶颈又是什么? 其实目标主要就两个: 更多的连接 更高的 QPS 所以,下面就针对这两个目标来说说他们的难点和注意点吧。 更多的连接 非阻塞 IO 其实无论是用 Java NIO 还是用 Netty,达到百万连接都没有任何难度。因为它们都是非阻塞的 IO,不需要为每个连接创建一个线程了

十种JVM内存溢出的情况,你碰到过几种?

十种JVM内存溢出的情况,你碰到过几种?

导言: 对于java程序员来说,在虚拟机自动内存管理机制的帮助下,不需要自己实现释放内存,不容易出现内存泄漏和内存溢出的问题,由虚拟机管理内存这一切看起来非常美好,但是一旦出现内存溢出或者内存泄漏的问题,对于不熟悉jvm虚拟机是怎么使用内存的话,那么排查错误将会是一项非常艰巨的任务。所以在了解内存溢出之前先要搞明白JVM的内存模型。 JVM(Java虚拟机)是一个抽象的计算模型。就如同一台真实的机器,它有自己的指令集和执行引擎,可以在运行时操控内存区域。目的是为构建在其上运行的应用程序提供一个运行环境。JVM可以解读指令代码并与底层进行交互:包括操作系统平台和执行指令并管理资源的硬件体系结构。 JVM内存模型 根据 JVM8 规范,JVM 运行时内存共分为虚拟机栈、堆、元空间、程序计数器、本地方法栈五个部分。还有一部分内存叫直接内存,属于操作系统的本地内存,也是可以直接操作的。 元空间(Metaspace) 元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。 2.虚拟机栈(JVM Stacks) 每个线程有一个私有的栈,随着线程的创建而创建。栈里面存着的是一种叫“栈帧”的东西,每个方法会创建一个栈帧,栈帧中存放了局部变量表(基本数据类型和对象引用)、操作数栈、方法出口等信息。栈的大小可以固定也可以动态扩展。 本地方法栈(Native Method Stack) 与虚拟机栈类似,区别是虚拟机栈执行java方法,本地方法站执行native方法。在虚拟机规范中对本地方法栈中方法使用的语言、使用方法与数据结构没有强制规定,因此虚拟机可以自由实现它。 程序计数器(Program Counter Register) 程序计数器可以看成是当前线程所执行的字节码的行号指示器。在任何一个确定的时刻,一个处理器(对于多内核来说是一个内核)都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要一个独立的程序计数器,我们称这类内存区域为“线程私有”内存。 5.堆内存(Heap) 堆内存是 JVM 所有线程共享的部分,在虚拟机启动的时候就已经创建。所有的对象和数组都在堆上进行分配。这部分空间可通过 GC 进行回收。当申请不到空间时会抛出 OutOfMemoryError。堆是JVM内存占用最大,管理最复杂的一个区域。其唯一的用途就是存放对象实例:所有的对象实例及数组都在对上进行分配。jdk1.8后,字符串常量池从永久代中剥离出来,存放在队中。 6.

Python HTTP 库:requests 快速入门

Python HTTP 库:requests 快速入门

2013 年我接触 Python 的时候,就听闻 Python 的网络编程能力十分强大。因此,在熟悉 Python 的基本语法之后,我就和几个小伙伴一起合作,试着用 Python 的 urllib 和 urllib2 库构建了一个百度贴吧 Python 客户端。 然而,使用的过程中,我发现两个标准库的语法并不自然,甚至可以说十分反人类——用着很难受。又有,我平时使用 Python 甚少涉及到网络编程的内容。因此,Python 的网络编程就被我放下了,直到我认识了 requests 库。 初识 requests requests 库的宣言是 HTTP for Humans (给人用的 HTTP 库) 我们首先来验证一下。 在网络编程中,最最基本的任务包含: 发送请求 登录 获取数据 解析数据 反序列化打印内容 我们以 GitHub 为例,先看一下使用 urllib2 要怎么做。为了把事情弄简单点,我们假设实现已经知道,GET 请求 https://api.github.com/ 返回的内容是个 JSON 格式的数据(实际上通过 content-type 也能判断)。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import urllib2 import json gh_url = 'https://api.github.com' cs_user = 'user' cs_psw = 'password' req = urllib2.Request(gh_url) password_manager = urllib2.HTTPPasswordMgrWithDefaultRealm() password_manager.add_password(None, gh_url, cs_user, cs_psw) auth_manager = urllib2.HTTPBasicAuthHandler(password_manager) opener = urllib2.build_opener(auth_manager) urllib2.install_opener(opener) handler = urllib2.urlopen(req) if handler.getcode() == requests.codes.ok: text = handler.read() d_text = json.loads(text) for k, v in d_text.items(): print k, v 如果运行正确,那么代码应该返回: 1 2 3 4 5 6 7 issues_url https://api.github.com/issues current_user_repositories_url https://api.github.com/user/repos{?type,page,per_page,sort} rate_limit_url https://api.github.com/rate_limit repository_url https://api.github.com/repos/{owner}/{repo} ... user_repositories_url https://api.github.com/users/{user}/repos{?type,page,per_page,sort} team_url https://api.github.com/teams 同样的效果,用 requests 库则有如下代码: 1 2 3 4 5 6 7 8 9 10 11 import requests cs_url = 'https://api.github.com' cs_user = 'user' cs_psw = 'password' r = requests.get(cs_u

联系我们

联系电话

4000-640-466

联系邮箱

service@f-li.cn

办公地址

上海黄浦区外滩源1号

谢谢,您的信息已成功发送。
请填写信息。