• LongAdder简单介绍
    LongAdder类似于AtomicLong是原子性递增或者递减类,AtomicLong已经通过CAS提供了非阻塞的原子性操作,相比使用阻塞算法的同步器来说性能已经很好了,但是JDK开发组并不满足,因为在非常高的并发请求下AtomicLong的性能不能让他们接受,虽然AtomicLong使用CAS但是CAS失败后还是通过无限循环的自旋锁不断尝试的 public final long incrementAndGet() { for (;;) { long current = get(); long next = current + 1; if (compareAndSet(current, next)) return next; } }在高并发下N多线程同时去操作一个变量会造成大量线程CAS失败然后处于自旋状态,这大大浪费了cpu资源,降低了并发性。那么既然AtomicLong性能由于过多线程同时去竞争一个变量的更新而降低的,那么如果把一个变量分解为多个变量,让同样多的线程去竞争多个资源那么性能问题不就解决了?是的,JDK8提供的LongAdder就是这个思路。LongAdder维护了一个延迟初始化的原子性更新数组和一个基值变量base.数组的大小保持是2的N次方大小,数组表的下标使用每个线程的hashcode值的掩码表示,数组里面的变量实体是Cell类型,Cell类型是AtomicLong的一个改进,用来减少缓存的争用,对于大多数原子操作字节填充是浪费的,因为原子性操作都是无规律的分散在内存中进行的,多个原子性操作彼此之间是没有接触的,但是原子性数组元素彼此相邻存放将能经常共享缓存行,所以这在性能上是一个提升。另外由于Cells占用内存是相对比较大的,所以一开始并不创建,而是在需要时候在创建,也就是惰性加载,当一开始没有空间时候,所有的更新都是操作base变量,自旋锁cellsBusy用来初始化和扩容数组表使用,这里没有必要用阻塞锁,当一次线程发现当前下标的元素获取锁失败后,会尝试获取其他下表的元素的锁。
  • [技术干货] glic下的ptmalloc 内存管理
    openstack 用的Python开发,Python底层是c,c的底层默认的内存管理由glic下的ptmalloc负责。有必要弄清ptmalloc的分配与回收过程。本文主要分析以下几个问题:1. Glibc 在什么情况下不会将内存归还给操作系统?free(pointer)2. Glibc 的内存管理方式有哪些约束?适合什么样的内存分配场景?3. Python中的内存管理方式是否与 Glibc 的内存管理的约束相悖的?(未完)4. Glibc 是如何管理内存的?(暂略)基于Glibc 2.17版本。以下内容不进行分析Ptmalloc的内存布局(由于复杂性,不进行具体分析)。编译选项不进行分析。 Ptmalloc的一些不足之处以及为什么会这样ptmalloc 在设计时折中了高效率,高空间利用率,高可用性等设计目标。在其实现代码中,隐藏着内存管理中的一些设计假设,由于某些设计假设,导致了在某些情况下ptmalloc的行为很诡异。这些设计假设包括:1.具有长生命周期的大内存分配使用 mmap。2.特别大的内存分配总是使用 mmap。3.具有短生命周期的内存分配使用 brk,因为用mmap 映射匿名页, 当发生缺页异常时,linux内核为缺页分配一个新物理页,并将该物理页清0,一个 mmap 的内存块需要映射多个物理页,导致多次清 0 操作,很浪费系统资源,所以引入了mmap分配阈值动态调整机制,保证在必要的情况下才使用 mmap 分配内存。4.尽量只缓存临时使用的空闲小内存块,对大内存块或是长生命周期的大内存块在释放时都直接归还给操作系统。5.对空闲的小内存块只会在 malloc 和 free 的时候进行合并,free 时空闲内存块能放入 pool 中,不一定归还给操作系统。6.收缩堆的条件是当前 free 的块大小加上前后能合并chunk 的大小大于 64KB、并且堆顶的大小达到阈值,才有可能收缩堆,把堆最顶端的空闲内存返回给操作系统。7.需要保持长期存储的程序不适合用 ptmalloc 来管理内存。8.为了支持多线程,多个线程可以从同一个分配区(arena) 中分配内存,ptmalloc假设线程 A **掉一块内存后,线程 B 会申请类似大小的内存,但是 A **的内存跟B 需要的内存不一定完全相等,可能有一个小的误差, 就需要不停地对内存块作切割和合并,这个过程中可能产生内存碎片。 Ptmalloc 的响应用户内存分配要求的具体步骤为:1) 获取分配区的锁,为了防止多个线程同时访问同一个分配区,在进行分配之前需要取得分配区域的锁。线程先查看线程私有实例中是否已经存在一个分配区,如果存在尝试对该分配区加锁,如果加锁成功,使用该分配区分配内存,否则,该线程搜索分配区循环链表试图获得一个空闲(没有加锁) 的分配区。如果所有的分配区都已经加锁,那么 ptmalloc 会开辟一个新的分配区,把该分配区加入到全局分配区循环链表和线程的私有实例中并加锁,然后使用该分配区进行分配操作。开辟出来的新分配区一定为非主分配区,因为主分配区是从父进程那里继承来的。开辟非主分配区时会调用 mmap()创建一个sub-heap,并设置好 top chunk。2)将用户的请求大小转换为实际需要分配的 chunk 空间大小。3)判断所需分配 chunk的大小是否满足chunk_size
  • [技术干货] 华为云Linux服务器时经常使用常用命令汇总
    大家使用华为云Linux服务器时经常使用常用命令汇总,高手请飘过,拍砖请绕道。ls   显示文件或目录 -l 列出文件详细信息l(list) -a 列出当前目录下所有文件及目录,包括隐藏的a(all)mkdir 创建目录 -p 创建目录,若无父目录,则创建p(parent)cd 切换目录touch 创建空文件echo 创建带有内容的文件。cat 查看文件内容cp 拷贝mv 移动或重命名rm 删除文件 -r 递归删除,可删除子目录及文件 -f 强制删除find 在文件系统中搜索某文件wc 统计文本中行数、字数、字符数grep 在文本文件中查找某个字符串rmdir 删除空目录tree 树形结构显示目录,需要安装tree包pwd 显示当前目录ln 创建链接文件more、less 分页显示文本文件内容head、tail 显示文件头、尾内容ctrl+alt+F1 命令行全屏模式 系统管理命令stat 显示指定文件的详细信息,比ls更详细who 显示在线登陆用户whoami 显示当前操作用户hostname 显示主机名uname 显示系统信息top 动态显示当前耗费资源最多进程信息ps 显示瞬间进程状态 ps -auxdu 查看目录大小 du -h /home带有单位显示目录信息df 查看磁盘大小 df -h 带有单位显示磁盘信息ifconfig 查看网络情况ping 测试网络连通netstat 显示网络状态信息man 命令不会用了,找男人 如:man lsclear 清屏alias 对命令重命名 如:alias showmeit="ps -aux" ,另外解除使用unaliax showmeitkill 杀死进程,可以先用ps 或 top命令查看进程的id,然后再用kill命令杀死进程。 打包压缩相关命令gzip:bzip2:tar: 打包压缩 -c 归档文件 -x 压缩文件 -z gzip压缩文件 -j bzip2压缩文件 -v 显示压缩或解压缩过程 v(view) -f 使用档名例:tar -cvf /home/**.tar /home/** 只打包,不压缩tar -zcvf /home/**.tar.gz /home/** 打包,并用gzip压缩tar -jcvf /home/**.tar.bz2 /home/** 打包,并用bzip2压缩当然,如果想解压缩,就直接替换上面的命令 tar -cvf / tar -zcvf / tar -jcvf 中的“c” 换成“x” 就可以了。 关机/重启机器shutdown -r 关机重启 -h 关机不重启 now 立刻关机halt 关机reboot 重启 Linux管道将一个命令的标准输出作为另一个命令的标准输入。也就是把几个命令组合起来使用,后一个命令除以前一个命令的结果。例:grep -r "close" /home/* | more 在home目录下所有文件中查找,包括close的文件,并分页输出。 Linux软件包管理dpkg (Debian Package)管理工具,软件包名以.deb后缀。这种方法适合系统不能联网的情况下。比如安装tree命令的安装包,先将tree.deb传到Linux系统中。再使用如下命令安装。sudo dpkg -i tree_1.5.3-1_i386.deb 安装软件sudo dpkg -r tree 卸载软件 注:将tree.deb传到Linux系统中,有多种方式。VMwareTool,使用挂载方式;使用winSCP工具等;APT(Advanced Packaging Tool)高级软件工具。这种方法适合系统能够连接互联网的情况。依然以tree为例sudo apt-get install tree 安装treesudo apt-get remove tree 卸载treesudo apt-get update 更新软件sudo apt-get upgrade 将.rpm文件转为.deb文件.rpm为RedHat使用的软件格式。在Ubuntu下不能直接使用,所以需要转换一下。sudo alien **.rpm vim使用vim三种模式:命令模式、**模式、编辑模式。使用ESC或i或:来切换模式。命令模式下::q 退出:q! 强制退出:wq 保存并退出:set number 显示行号:set nonumber 隐藏行号/apache 在文档中查找apache 按n跳到下一个,shift+n上一个yyp 复制光标所在行,并粘贴h(左移一个字符←)、j(下一行↓)、k(上一行↑)、l(右移一个字符→) 用户及用户组管理/etc/passwd 存储用户账号/etc/group 存储组账号/etc/shadow 存储用户账号的密码/etc/gshadow 存储用户组账号的密码useradd 用户名userdel 用户名adduser 用户名groupadd 组名groupdel 组名passwd root 给root设置密码su rootsu - root /etc/profile 系统环境变量bash_profile 用户环境变量.bashrc 用户环境变量su user 切换用户,加载配置文件.bashrcsu - user 切换用户,加载配置文件/etc/profile ,加载bash_profile更改文件的用户及用户组sudo chown [-R] owner[:group] {File|Directory}例如:还以jdk-7u21-linux-i586.tar.gz为例。属于用户hadoop,组hadoop要想切换此文件所属的用户及组。可以使用命令。sudo chown root:root jdk-7u21-linux-i586.tar.gz 文件权限管理三种基本权限R 读 数值表示为4W 写 数值表示为2X 可执行 数值表示为1例如,当前目录下一个文件jdk-7u21-linux-i586.tar.gz的权限为-rw-rw-r---rw-rw-r--一共十个字符,分成四段。第一个字符“-”表示普通文件;这个位置还可能会出现“l”链接;“d”表示目录第二三四个字符“rw-”表示当前所属用户的权限。 所以用数值表示为4+2=6第五六七个字符“rw-”表示当前所属组的权限。 所以用数值表示为4+2=6第八九十个字符“r--”表示其他用户权限。 所以用数值表示为2所以操作此文件的权限用数值表示为662 更改权限sudo chmod [+增加权限 -减少权限] [r w x] 目录名 例如:有一个文件filename,权限为“-rw-r----x” ,将权限值改为"-rwxrw-r-x",用数值表示为765sudo chmod u+x g+w o+r filename上面的例子可以用数值表示sudo chmod 765 filename
  • [技术干货] 协程在openstack中的使用
    协程在openstack中的使用OpenStack 由python 语言编写,主要依赖协程处理并发事务。协程coroutine又称为用户态线程,完全由应用程序负责调度,其上下文切换的开销远远小于线程,由于多线程的效率因GlobalInterpreter Lock而大打折扣,所以 python 多用协程而非线程处理并发。 协程有如下优点:(1)每个coroutine有自己私有的stack及局部变量。(2)同一时间只有一个coroutine在执行,无需对全局变量加锁。(3)顺序可控,完全由程序控制执行的顺序。通常的多线程是由操作系统进行调度,而协程的切换完全由程序员自己控制。(4)同步编写代码,异步执行,更加符合人类的思维逻辑。 Openstack中应用的协程库eventlet主要依赖于两个关键的库:Greenlet:协程库,提供并发能力。Select.epoll(Epoll/kqueue): 基于事件驱动的网络库,处理网络请求。 eventlet是一个用来处理和网络相关的python库函数,而且可以通过协程来实现并发,在eventlet里,把“协程”叫做greenthread(绿色线程)。所谓并发,就是开启了多个greenthread,并且对这些greenthread进行管理,以实现非阻塞式的I/O。比如说用eventlet可以很方便的写一个性能很好的web服务器,或者是一个效率很高的网页爬虫,这都归功于eventlet的“绿色线程”,以及对“绿色线程”的管理机制。更让人不可思议的是,eventlet为了实现“绿色线程”,竟然对python的和网络相关的几个标准库函数进行了改写,并且可以以补丁(patch)的方式导入到程序中,因为python的库函数只支持普通的线程,而不支持协程,eventlet称之为“绿化”。 以最常用的eventlet.spawn函数为例,它的实现如下: hub有两个作用:1.调度器hub在eventlet中是单例实现,全局只有一个实例,其中包含一个greenlet实例,作为主协程,主协程是所有其他创建的greenthread的父greenlet,运行主循环,调度其管理的所有子协程。主协程内部维护一个小顶堆来管理所有子协程,每次从堆顶取出一个进行控制权的切换,当某一个子协程遇到了IO或者主动sleep,子协程需要主动把控制权交回给主协程,由主greenlet进行下一次的调度。2.异步IO协程常用于IO密集型的系统中,并且在任何一个协程里面不能有阻塞操作,否则会是整个进程**作系统挂起。所以在提供协程的框架里面除了调度器一般还会有一个基于事件机制的异步IO网络库。网络库中还是一般网络编程中基于回调的模式,在linux下底层一般使用epoll或者select系统调用。当greenletA需要访问网络的时候,它会将网络请求封装好,让后带着请求数据swich到主greenlet中,主greenlet代理greenletA把请求发送出去后,继续调度其他子greenlet B进行执行。当greenlet B遇到IO等操作时再次切换回主协程。此时主协程如果再poll中收到了greenlet A发出去网络请求的回包数据,在合适的时间会带着回包数据再次swich到greenlet A。
  • [技术干货] 线程池、线程缓存和连接池
    1. 在MySQL5.6出现以前,MySQL处理连接的方式是One-Connection-Per-Thread,即对于每一个数据库连接MySQL-Server都会创建一个独立的线程服务,请求结束后,销毁线程。再来一个连接请求,则再创建一个连接,结束后再进行销毁。这种方式在高并发情况下,会导致线程的频繁创建和释放。当然,通过thread-cache,我们可以将线程缓存起来,以供下次使用,避免频繁创建和释放的问题,但是无法解决高连接数的问题。所以线程缓存的核心还是一个线程来服务一个连接。即使使用了线程缓存,也是如此。当然使用线程缓存可以减少创建线程、销毁线程的开销。2. One-Connection-Per-Thread方式随着连接数暴增,导致需要创建同样多的服务线程,高并发线程意味着高的内存消耗,更多的上下文切换(cpu cache命中率降低)以及更多的资源竞争,导致服务出现抖动。相对于One-Thread-Per-Connection方式,一个线程对应一个连接,Thread-Pool实现方式中,线程处理的最小单位是statement(语句),一个线程可以处理多个连接的请求。这样,在保证充分利用硬件资源情况下(合理设置线程池大小),可以避免瞬间连接数暴增导致的服务器抖动。线程池的核心就是:一个线程可以处理多个连接请求。并且线程处理的最小单位是语句。不再一个线程对应一个连接了。3. 连接池通常实现在Client端,是指应用(客户端)创建预先创建一定的连接,利用这些连接服务于客户端所有的DB请求。如果某一个时刻,空闲的连接数小于DB的请求数,则需要将请求排队,等待空闲连接处理。通过连接池可以复用连接,避免连接的频繁创建和释放,从而减少请求的平均响应时间,并且在请求繁忙时,通过请求排队,可以缓冲应用对DB的冲击。线程池实现在server端,通过创建一定数量的线程服务DB请求,相对于one-conection-per-thread的一个线程服务一个连接的方式,线程池服务的最小单位是语句,即一个线程可以对应多个活跃的连接。通过线程池,可以将server端的服务线程数控制在一定的范围,减少了系统资源的竞争和线程上下文切换带来的消耗,同时也避免出现高连接数导致的高并发问题。连接池和线程池相辅相成,通过连接池可以减少连接的创建和释放,提高请求的平均响应时间,并能很好地控制一个应用的DB连接数,但无法控制整个应用集群的连接数规模,从而导致高连接数,通过线程池则可以很好地应对高连接数,保证server端能提供稳定的服务。如图2所示,每个web-server端维护了3个连接的连接池,对于连接池的每个连接实际不是独占db-server的一个worker,而是可能与其他连接共享。这里假设db-server只有3个group,每个group只有一个worker,每个worker处理了2个连接的请求。
  • [技术干货] MASTER_HEARTBEAT_PERIOD和slave-net-timeout
    Change master的时候有几个参数容易被忽略,其含义如下: --master-retry-count : 86400次 重试连接次数--master-connect-retry: 60秒 间隔多长时间重试连接(连接一次要多久才算timeout,暂时不知道)--slave-net-timeout: 3600秒 从库认为连接中断(一直没有收到变更)了,间隔多长时间会尝试重连; 这个参数MySQL5.6的默认值为3600, MySQL5.7默认值为60 MASTER_HEARTBEAT_PERIOD 默认值为slave-net-timeout除于2 这几个值结合起来,来防止同步假死的状态。在没有心跳包的情况下,如果网络有问题或者主库一直没有任何变更,那从库就一直收不到任何有关主库的信息。这样从库就会等待slave-net-timeout秒去尝试连接主库。如果连接失败,就会以间隔master-connect-retry秒来重试,重试了master-retry-count次还是不行,就表示失败。 有心跳包的情况下,主库就会没隔一段时间发了个心跳包给从库,让从库知道主库有“变更”。这样从库就不会尝试去连接主库了。 要想做好这个实验:必须要想办法断开网络,可以使用iptables的方法来实现。比如:iptables -A INPUT -s 192.168.0.26 -d 0/0 -j DROP Binlog dump线程是否kill无关紧要。 如果仅仅是kill binlog dump线程而不断开网络则没有用,因为kill会对tcp连接发出disconnect信号,系统会重新生成新的复制线程。 尝试重连过程中,IO线程一直处于Connecting,当重试的次数达到设置值的时候,如果还没有成功,则会成为NO的状态。
  • 纠删码简介
    1、朴素的解释 有下列6个方程组成的方程组(1)x1 = 1 (2)x2 = 2 (3)x3 = 3 (4)x1 + x2 + x3= 6 (5)x1 + 2*x2 +4*x3 = 17 (6)x1 + 3*x2 +9*x3 = 34 要知道x1,x2,x3三个数的值,只需要上面任意三个方程即可解出来。假设有上面4个方程,有趣的地方出现了,如果丢了一个方程,那么仍然可以用其他三个方程求出x1,x2, x3的值。相当于只多了一个方程就能解决x1,x2,x3任何一个数的值丢失的问题。 把上面的方程(1)(2)(3)看做是分布式系统的数据,(4)(5)(6)看做是code,那么只要一个code,即使丢了(1)(2)(3)中的任何一个数据都是可以恢复的, 达到这样的效果只需要存储4个方程。 如果采取副本策略,要达到(1)(2)(3)丢失任何一个数据都能恢复的话,只要把(1)(2)(3)三个方程都存储两份,也就是存储了6个方程。于是纠删码比副本策略在存储效率上的优势就体现出来,4/6的比值,节省1/3的空间。实际根据code的多少,存储效率会不一样。 2、存储系统中的符号约定 k:数据块的个数 m:校验块的个数(就是code) n:k+m,也就是数据块和校验块的个数总和。 编码效率:r = k/m 上面的解释是参照Jerasure库的代码解释的,IntelEC库符号表示不同,但是意义一样,不再赘述。 3、现有的EC库 (1)Jerasure库 http://jerasure.org/ (2)Intel EC库 http://www.intel.com/content/www/us/en/storage/erasure-code-isa-l-solution-video.html 实际实验发现,(1)线程不安全,(2)线程安全(本人简单看过一部分代码+1000线程并发测试)尚未面世的Hadoop 3.0据说要使用EC编码。查资料发现用的应该也是英特尔库。本人近日工作是基于英特尔的EC库封装LRC库, 也就是线程安全的LRC(见后文)。
  • 做开发十年,我总结出了这些开发经验
    在一线做了十年的开发,经历了网易、百度、腾讯研究院、MIG等几个地方,陆续做过3D游戏、2D页游、浏览器、移动端翻译app等。积累了一些感悟。必然有依然幼稚的地方,就当抛砖引玉,聊为笑谈。一、对于团队而言,流程太重要了行军打仗,你需要一个向导;如果没有向导,你需要一个地图;如果没有地图,至少要学习李广,找一匹识途的老马;如果你连老马也没有,那最好可以三个臭皮匠好好讨论,力图胜过一个诸葛亮;如果三个臭皮匠连好好讨论也做不到,那就是典型的乌合之众了,最好写代码前,点上三炷香,斟上一杯浊酒,先拜拜菩萨,再拜拜谷歌。我个人属于性格温和的(程序员大多性格不错),但确实见过少数强势的人,说很多强势的话。在技术上一言而决,一听到任何反对就上升到私人恩怨。这样的风格,到底是刚愎自用,还是胸有成竹,就需要仔细判断了。为什么说流程重要呢?实际上,如果团队上有孙悟空存在,去西天取经,大概也不需要什么流程,只要方向就可以了。 但作为普通的战士,应该先虑败。找人算命时,应该先听听不好的地方,好的地方就不用听了,总归是好的,不好的地方一定要听,这样才能规避。 这就是我的态度:先悲观一点,划清底线,考虑在这个底线上你该怎么做?这是我做开发的一个习惯,但这个习惯肯定不适用于买房。 怎么划清底线呢?就是假想团队中没有孙悟空了,光靠你唐玄奘、猪八戒和沙和尚,应该怎么去取经。这个月走什么地方,遇到山怎么走,遇到河怎么过,遇到路上有妖怪劫道,谁去抵挡。遇到路上有少女要搭救,怎么办?这就是流程,是原则。我经历过一个流程很混乱的阶段。都是很多年前的事情了,可以拿出来说说,不涉及单个人。2011年在百度浏览器团队时遇到几件让人影响深刻的事情。 有一次开会,产品拿出Google某个产品的DEMO,里面有一段很酷炫3D 效果,要求开发加上,只给2天时间,大家目瞪口呆。后续的开发为了赶节奏,导致非常多的bug,又为了修改bug,leader将所有的bug按照人员平均分配,导致不同模块间的同学相互修改。。。。。实在难以想象。好比让做花卷的厨子,去修改西湖醋鱼的味道。最初的现象是:bug下降的慢,延伸bug反而增加,每个人都累的半死,代码风格极其杂乱,为了赶工导致的临时方案层出不穷;到了中期:人员离职越来也多,代码难以维护,新加的需求与之前的临时方案冲突。到了后期:想做一些修复,想调整架构,又要保证正常运行,其难度好比在一架飞行的飞机上拆换零件。然后我也急忙离职了。。。。实在看不到成功的可能性。后来到了腾讯的团队,感觉流程就规范多了。需求和bug有Tapd跟踪,产品发布按照节奏,需求提出前会和开发反复讨论可行性,有专门的质量跟踪,有专门的用户反馈,每天知道要做什么,也知道明天要做什么。有产品需求,也有开发需求!这个非常重要。很多团队,都是只有产品需求,开发好像牛一样,耕完地就不管了?流程其实没那么复杂,就是各司其责+节奏。我们都是“哆瑞咪发梭拉西多”中的一员,各自有各自的责任,然后组合在一起,按照一个节奏跑起来。把该做的事情与该跑的节奏定好。二、不要炫技,老老实实写代码网上有一个段子,说有人要用JS实现一个简单的功能,然后朋友给他推荐了几十个库。真的有必要吗?具体情况具体分析。居家过日子,你只需要一套普通的工具就可以了;如果你是修车的,你需要一套修车的工具;如果你是光头强,你需要一台伐木机。 吃饭用筷子,用刀叉,都可以,但不要用杀猪刀,不要用丈八长矛!,当然也不能用牙签。用什么工具,用什么库,问问过来人,多在KM上搜索一下。举个例子:android上加密,用SQLChpher[/backcolor]就可以了,微信也在用,你当然可以学习;数据库ORM思想,用KM上推荐的GreenDAO就可以了;PC上3D引擎,用OGRE就可以了;小型游戏DEMO,用Irrlicht足够;写WebGL,用ThreeJS足够。首先想想:一些大库hold的住吗,后续发展如何?这些库对安装包的体积影响有多大?有没有调研过同样的产品在用什么?想清楚了再决定用什么,最好是跟随成功项目的脚步。三、架构上实用+适用很喜欢曾国藩的一句话:结硬寨、打呆仗。一字长蛇阵、八门金锁阵,哪个好?iOS都是单个进程,微信Android版本3.5以前是单进程,3.5以后有独立的网络进程; PC浏览器的进程架构更加复杂,UI进程、内核进程、Render进程,而且还有根据页面多少的进程调节模型。这些设计都很好,各有各的道理,都适用于当前的产品。所以我的观点是:首先分析当前产品的规模、性质,然后再设计架构。 在当前阶段达到:开发效率+架构的平衡;并向后展望3个月,或者半年左右,看看架构能不能适应。我做腾讯翻译君时,曾反复犹豫要不要模仿微信加入独立的网络进程。后来逆向了有排在第一二位的竞品,最终采用了现在的主功能单进程模型。产品规模、人员规模、功能阶段,具体问题具体分析。四、既要有攻城之力,也要有熬战之气——BUG产品开发完成后,必然有bug。其实开发人员在工作过程中,是有一定的直觉或者心理预判的,即:某个功能模块的质量如何。 这里面的质量包括:可维护性、扩展性、算法\渲染效率,还有就是bug与崩溃率。功能开发完成后,就要开始守城了。bug,一部分产生是由于架构带来的,例如比较复杂的架构,会导致复杂的实现细节;但还有很大部分bug,其实是基于如下三个原因产生的:[*]对于某个api的不了解,或者对于某个平台,或者SDK版本的不了解。 举例而言:andrid里面非主线程,是不能直接处理UI相关的事情的;JAVA的内存释放也不是绝对的,相互指向是无法释放的;函数个数是有DEX问题制约的———————这些bug的产生,也是开发人员摸索学习的过程,经历过一次就不会再犯了。这是学习广度与熟练度的问题;[*]还有一些bug,是由于粗心大意导致的。例如空指针的问题,野指针的问题。在C的开发中,野指针的问题,GDI句柄的释放问题,这些都是严谨的代码需要避免的; 而又一些工具,或者方法是可以规避这些问题的,例如android中的利用@Nullable和@NonNull加强空指针检测等方法;[*]还有一些bug,是由于“使用情况各异导致的”。例如:偶现在某个模块crash。这里的本质还是因为逻辑的异常边界没有处理好。例如android上的OOM问题,还有PC上UI焦点导致的对象释放问题。这些异常情况,一部分靠测试发现,一部分靠用户反馈,还有一部分就靠自己的异常处理。例如Android中的try catch机制,其实就是遇到异常了,你能纠正错误的机会。五、自审每过一段时间,都要站在高空俯视自己,问问:到底是在承担过去,还是在改变未来。如果之前程序代码质量不好,后面修改问题的时间就会比较多。到了开发的中期,得多问问自己,你在不停的改正以前的错误,还是在做新的东西。 如果修改错误的时间多一点,那就要注意自己的代码质量了!六、注释我很喜欢写注释。有大牛说:代码就是最好的注释。 可惜我还没有达到那个程度。所以,我会把注释写的非常清楚。[*]其一:为了自己以后维护的方便;[*]其二:为了其他人接手的方便。[*][*][*]这是我在翻译君项目中写注释的方式。[*]1:对于很复杂的逻辑,务必用12345的顺序依次写清楚;[*]2:对于函数中的某个参数,需要解释为什么要设置这个参数,尤其是公用工具类里面的函数—说清楚参数的背景含义,可以让其他调用者理解的更加清晰。我一般不用英文写。虽然这样看起来格调很低,但胜在大家都能轻松的看懂。写代码不能太傲娇,写注释也不要太傲娇,目的是让你的搭档或者接手者,更轻松的理解,让她/他少加班。七、代码结构代码结构要清晰。有按照功能划分的,有按照UI结构划分的。还有公用工具类,有数据管理,有主逻辑控制。不管用哪种思想,有序的代码结构,可以让每个人感觉很干净。好比日本的收纳整理技巧让很多小资推崇,无非就是干净、整洁、便于管理。而且,还有一个重要的好处:代码结构表现出来的其实是——程序的一个模块\逻辑思想——让大家工作在不同的区域。八、代码风格代码风格统一!好比一家人,有叫Tom的,有叫安东尼的,还有叫流川枫、石破天、圣杰夫拉斯基,无所适从。理论上,看一个函数,就能从名称上区分哪些是成员变量,哪些是局部变量,哪些是全局静态值。除了命名统一外,还有一行代码最大的宽度,函数的连续调用长度等,头文件的包含风格,也最好有一个约定。类的出现时间,创建人名,最好也加上,看起来没用,但到了追踪问题时,就能看出时间线的好处。九、安全与逆向这是针对Android说的,还有PC插件也需要考虑。Android上首先要防止被别人逆向,我成功逆向并重新打包过有第一位和第二位的竞品。这似乎有点不可思议,但确实做到了。加固+混淆+代码判断,最好都有。安全上,可以看金刚扫描的漏洞,逐一修改就行。公司很多工具很好用的!十、开发效率开发效率可以用这些方式提升:[*]构建公用工具类,方便大家使用[*]使用开源的一些包,例如ORM思想的数据库等[*]可以很快的找到问题。开发中,找bug的时间,往往是很多的。我用的方法有3个: 使用try catch; 拦截所有crash到我指定的地方;超多的Log,Log有统一的控制开关。[*]借力:数据上报用灯塔,崩溃上报用bugly,公司KM上很多经验,拿过来用。十一、安装包体积[*]TINY压缩图片[*]删除无效的资源文件十二、UI渲染效率[*]UI是用户的第一感觉;UI快并稳定,第一感觉就不会差太多;管理好内存,基本管理好了一半crash;管理好UI,等于管理了人机交互感受。[*]UI上的开发是:渲染效率与渲染效果的平衡。
  • [技术干货] 运行工作流时,提示:工作流进程启动失败,任务队列源不足,请尝试提交到其它任务。
    问题如题所示。如下图所示:
  • [技术干货] MySQL的内存使用分析
    这个公式只能计算出应用并发跑满的最极端情况下,mysql使用的内存的最大值。这个值的意义不大。我其实是想弄明白一个连接进来不执行任何操作(空连接),mysql或者操作系统会为这个连接分配多少内存,分配的内存包含哪些部分。 Memories for variablesread_buffer_size, sort_buffer_size, read_rnd_buffer_size, tmp_table_size areallocated as & when required.根据上边这段话的意思,建立一个空连接只需要分配 thread_stack+net_buffer_length这么多空间。我怎么去验证这个问题?从内存的使用方式MySQL 数据库的内存使用主要分为以下两类:线程独享内存 和 全局共享内存 Mysql Server Memory Usage= Sum of Global Buffers + (number of Connection * Per thread memory variables). 线程独享内存上面所列举的 MySQL 线程独享内存仅仅只是所有线程独享内存中的部分,并不是全部,选择的原则是可能对MySQL 的性能产生较大的影响,且可以通过系统参数进行调节。由于以上内存都是线程独享,极端情况下的内存总体使用量将是所有连接线程的总倍数。所以各位朋友在设置过程中一定要谨慎,切不可为了提升性能就盲目的增大各参数值,避免因为内存不够而产生Out Of Memory 异常或者是严重的 Swap 交换反而降低整体性能。 全局共享内存这里所列举的各种共享内存,是我个人认为对 MySQL 性能有较大影响的集中主要的共享内存。实际上,除了这些共享内存之外,MySQL还存在很多其他的共享内存信息,如当同时请求连接过多的时候用来存放连接请求信息的back_log队列等。
  • [技术干货] root密码忘记如何处理
    一、MySQL5.6方面:当最高权限的用户密码root备忘记后,可以按如下方式来处理1. 关闭mysql进程2. 在/etc/my.cnf配置文件中加上 skip-grant-tables 3. 启动mysql进程4. 修改密码 3. 将skip-grant-tables从配置文件中删除4. 重启mysql进程二、MySQL5.7方面:如果还是按同样的方法,则会报错...需要使用下面的方法:
  • [云日志] [云日志]如果你也好奇日志服务后端如何做到平滑停机
    在日志服务中,我们使用了 kafka 作为消息中间件,日志数据会首先暂存在消息队列中,然后消费者再根据后端业务的处理能力,逐步消费这些日志数据。采用消息队列,能够做到前后端业务解耦,对外的接口可以提供高吞吐量,内部业务系统可以根据实际处理能力,对消息进行消费处理。 在服务端,我们的消费者从 kafka 中取出消息,然后累积至一定的数量,再批量提交至服务端的业务系统。不同的业务系统,批量处理能力不同,每个业务系统会建立各自的线程池与kafka交互。 kafka 与我们的业务系统对接采用典型的分布式部署方式,kafka 采用三节点部署组成集群,业务系统每个集群至少为两个节点,避免单点故障。由于在业务系统中,我们缓存了部分数据,因此面临一个重要的问题就是:如何在系统升级时,重启应用做到内存中的数据不丢失?解决的方式很简单,在程序启动的时候,注册shutdown hook,通过该shutdown hook,程序就可以接收进程停止的信号。在接收到该信号之后,通知业务线程,不再处理新的请求或者接收新的事件,并把内存中缓存的数据消费处理完就可以了。在这个处理环节中,我们在程序中创建了一个“生命周期管理器”,所有的业务线程在启动的时候,首先将自己注册到管理器中,然后才进行业务处理(事件监听、接收请求等)。当要进行程序停止(升级操作)时,通过外部的启停脚本,给该进程发送TERM信号(kill / kill –TERM )。Hook线程接收到该信号之后,通知“生命周期管理器”,然后等待其处理结束。注意:这里必须等待生命周期管理器处理完成,否则程序会立即结束。在管理器里,我们最开始是顺序遍历所有的业务线程,调用业务线程的stop方法,通过但是由于业务线程量比较大,如果等待所有的线程结束,整体时间是不可控的。改进后的方案是,在顺序遍历所有业务线程时,new新的线程用于执行原有业务线程的stop方法,这种做法相当于并发的将stop命令通知到所有的业务线程。在下发stop命令的同时,我们传入一个计数器给该业务线程,在业务线程的stop方法结束时,该计数器减1,计数器的大小是所有业务线程的个数,这样当该计数器为0的时候,可以认为所有的业务线程已经正常停止,就可以真正的停止程序。以上是我们服务端的平滑停机的一些实践,由于经验有限,还希望大家多多提意见。
  • [技术干货] rocksdb特性介绍
    Gets,迭代器和快照键和值被视为纯字节流.没有大小的限制。Get接口允许应用程序从数据库中获取一个键值。MultiGet接口允许应用程序从数据库中检索多个键值。MultiGet接口返回的key-value对都是相互匹配的。所有数据库中的数据都按顺序存放。应用程序可以指定一个key比较方法来定义key的排序顺序。迭代器允许应用程序对数据库进行RangeScan。迭代器可以先定位一个指定的键,然后应用程序就可以从这个定位点开始一个一个扫描key。迭代器还可以用来对key做反向迭代。创建迭代器是会创建当前数据库的一个快照视图,因此,通过迭代器返回的所有键都来自同一个数据库视图。Snaps**允许应用程序创建一个快照视图。Get和迭代器可以从一个指定的快照读取数据。在某种意义上,Snaps**和迭代器都提供了某个时间点上数据库的快照视图,但两者的实现是不同的。短暂的扫描最好通过迭代器而耗时较长的扫描最好通过快照。迭代器记录了数据库当前视图对应文件——直到迭代器被释放才删除这些删除。而快照并不能阻止文件删除;相反,compaction流程知道当前的快照并且不会删除任何现有快照中的key。数据库重启后,快照将丢失。重载RocksDB(通过服务器重启)会释放所有先前的快照。前缀迭代器大多数LSM引擎无法支持一个高效RangeScan,因为它需要查找每一个数据文件。但大多数应用程序不会对key进行随机扫描,而更多的是扫描给定前缀的key。RocksDB利用了这种优势。应用程序可以通过prefix_extractor指定一个key的前缀。RocksDB用此来保存每个key前缀的bloom,指定了前缀(通过ReadOptions)的迭代器将使用bloom二进制位来避免查找不包含指定key前缀的文件。更新Put操作向数据库**单个key-value。如果键已经存在,旧值将被覆盖。Writer操作允许将多个keys-values原子地**到数据库中。数据库保证同一个Writer操作中的所有keys-values要么全部出入,要么都不**。如果其中任何一个键已经存在于数据库中,旧值将被覆盖。持久性Put操作数据会存储在内存中的缓冲区称为memtable,也会选择性地**到事务日志。每一个Put操作都有一组标志(通过WriteOptions设置),这些标志指定Put操作数据是否应该**到事务日志。WriteOptions也可以指定在put操作提交前一个同步调用是否写入事务日志。在内部,RocksDB使用batch-commit机制批量写入事务日志,这样它可以使用一个同步调用提交多个事务。容错RocksDB使用校验和检测数据是否正确。每个块(通常是4k到128k大小)都有自己的校验和。一块数据一旦写入将不会修改。RocksDB通过硬件支持动态获取校验和的计算结果,以避免需要是自己计算校验和。多线程CompactionCompactions可以删除同一key的多个副本,副本是应用程序覆盖现有key是产生的。可以删除key。通过配置可以让Compaction也多线程方式运行。LSM的写数据的整体吞吐量直接取决于Compaction的速度,特别是当数据存储在SSD或RAM这种存储器中。RocksDB可以处理多个线程并发的Comopaction请求。多线程Compaction场景下写数据的速率比单线程场景下的速率快10倍。整个数据库存储在一组sstfiles。memtable写满时,它的内容会被写入Level-0层的一个文件中,在此过程中,重复和被覆盖的key会被删除。一些文件被定期压缩合并形成更大的文件——这就是所谓的compaction。RocksDB支持两种不同形式的compaction。普遍的做法是将所有文件按时间顺序保存在L0。compaction选择几个彼此相邻的文件并将它们合并成一个新文件L0。所有文件可以有重叠的key。分层形式的compaction将数据存储在数据库中的多个层中。最新的数据存储在L0和最旧的数据存储在Lmax。L0层中的文件可以有重叠的key,但其他层中文件的key不能重叠。一次compaction过程就是选择Ln层的一个文件及它在Ln+1层的所有重叠文件进行压缩合并形成Ln+1层的新文件。相比层形式的compaction方法,普遍的compaction方法写数据性能较低,但空间利用率较高。MANIFEST文件记录了数据库的状态。compaction在添加新文件和从数据库删除现有的文件后,会将这些操作记录到MANIFEST文件。事务日志是被批量提交到MANIFEST文件中的,目的是为了减少对MANIFEST文件的重复同步访问。Compaction过滤器一些应用程序可能需要在Compaction过程中处理某些key。例如,一个支持TTL的数据库可能删除过期的key,这可以通过定义一个Compaction过滤器完成。如果应用程序想要不断删除旧数据,可以使用Compaction过滤掉丢弃过期的记录。RocksDB Compaction过滤器可以允许应用程序去修改对应key的value或作为Compaction过程的一部分直接丢弃key。只读的模式数据库可以在只读的模式下打开。只读模式下应用程序不能修改任何数据。这可以保证更高的读取性能,因为避免了代码执行路径的切换和锁的使用。数据库调试日志RocksDB的详细日志被写入到名为LOG*的文件中。这些日志用于调试和分析运行中的系统。可以按配置的指定周期记录日志。数据压缩RocksDB支持snappy,zlib,bzip2 lz4和lz4_hc压缩算法。对不同层的数据可以配置不同的压缩算法。一般来说,90%的数据保存在Lmax层。一个典型的安装可能是L0-L2层不配置压缩算法,中间层用snappy压缩算法,而Lmax层采用zlib压缩。事务日志RocksDB将事务日志保存在logfile文件中以防止系统崩溃。系统启动时会重新处理日志文件。logfile和_sstfile_s可以存放在不同目录下,比如下面的场景,当你希望将所有数据文件存储在非持久但快速的存储设备中,同时把事务日志保存在存取速度慢但持久的存储设备中。
  • HWSQL线程池实现原理
    HWSQL线程池实现原理常见场景 数据库在平时的应用开发中是不可或缺的一部分。但是,随着应用越来越复杂,一个数据库承载的应用数量逐渐增加,数据库性能随着负载的增加而降低。为了解决这个问题,大家可能使用了中间件来管理数据库连接,或者干脆将数据库拆分。但是,使用中间件使用了新的组件,引入了不稳定的风险;数据库拆分又没有充分使用硬件的性能。所以,一款能够使用大量连接,并在大量连接下仍能够保证良好的执行效率的数据库就显得格外重要。那么,为什么数据库性能会随着连接数的增加而降低呢?目前MySQL社区版针对每一个连接都创建一个线程来处理该连接的请求。当并发请求过大时,操作系统会在资源上和线程调度上耗费大量的资源。线程的频繁切换还会带来大量的内存页面失效和CPUcache的不命中,内存换页和重新cache会导致MySQL运行效率大幅度降低。这样,使用线程池的方式处理任务针对大量请求连接变得十分必要。线程池特性分析 线程池的设计原理为:1) 创建线程池,线程池中有线程组,每个线程组中有若干个线程。2) 网络连接多路复用,循环监听所有套接字,一次获取可读的套接字。3) 针对每一个可读的套接字,从线程池中选择空闲的线程组,处理该请求。4) 处理完成后线程组变为空闲状态,等待新的任务。5) 线程组中有高优先级队列和低优先级队列,已经开始事务或者获得锁的任务更容易被执行,防止大量已经处理但未处理完的半成品。6) 低优先级的任务不会发生过长时间的饥饿,会根据情况切换到高优先级队列中。 以下是连接的处理示意图,能够更加形象的说明该特性的原理。MySQL社区版连接处理示意图 上图为社区版本的连接示意图,MySQL会对每个连接创建一个服务线程,该线程持续等待用户的请求,并负责处理该请求并返回结果。HWSQL线程池对连接的处理示意图 上图为HWSQL线程池处理连接的方式,用户连接由线程池负责调度处理。线程个数由参数配置,不会因为连接数的增加而增加,这样能够提高cache和内存页面的命中率,减少线程切换以及线程本身对系统资源的消耗。 通过Sysbench压力测试可以发现,MySQL社区版的TPS随着连接数的增大先增加后减小,最后降低趋近为0,而使用了线程池的HWSQL的TPS随着连接数的增大而增加,最后保持最大值。HWSQL与社区版读写性能性能对比图HWSQL与社区版只读性能性能对比图插件形式提供线程池功能 MySQL有一套完善的插件机制,定制化的功能可以使用插件的形式来实现。使用插件可以避免修改MySQL代码和主要流程,降低代码耦合。可以动态加载和卸载,使用灵活。可以对插件进行独立升级,减少代码修改带来的影响。 有关连接管理和调度部分,MySQL已经考虑到了自定义开发,在官方提供的两种调度管理外,还提供了一个抽象类让用户自定义连接的管理方法,以及为插件提供了初始化,去初始化方法。所以,线程池借助了这些便利,使用插件的形式实现是再好不过的了。 HWSQL使用了插件实现线程池,避免大量侵入MySQL原有逻辑。并方便用户进行配置,参数组的开启和关闭使用一个配置即可实现。性能测试(基于MySQL5.6,性能仍在不断优化提升) Sysbench workload 测试结果
  • [技术干货] MySQL半同步复制演进
    1 介绍MySQL 提供了异步复制,主库并不关心备库是否收到日志,从而可能导致较多的数据丢失。从MySQL5.5开始引入了一种半同步复制功能,该功能可以确保主服务器和至少一台从服务器之间的数据一致性和冗余,从而可以减少数据的丢失。1.1 异步复制 master将binlog event发送给slave后,不去确认slave是否已经收到就返回成功给客户端。1.2 半同步复制 master将binlog event发送给slave后,确认slave已经收到时才返回成功给客户端。 2 半同步复制演进2.1 减少LOCK_log锁冲突在MySQL5.7.2版本中重构DUMP线程减小了LOCK_log锁冲突。 在之前版本中,写binlog的设计为先持有LOCK_log锁(锁住了整个binlog文件),写完后释放锁,再发送binlog更新信号。DUMP线程在读取binlog内容时,也要先获取到LOCK_log锁,然后读取binlog内容,然后释放锁。这样当写binlog的线程和DUMP线程在处理同一个binlog时,就会有较严重的冲突。 在MySQL5.7中调整了这块的设计。因为写binlog时是追加写入,DUMP线程只是读取,这样可以用一个变量(binlog_end)记录binlog文件末尾位置,每次追加写入后更新下这个变量就可以(用lock_binlog_end锁保护变量binlog_end),DUMP线程在读取时只要不超过标记的binlog末尾位置就可以。只需要在读取到末尾位置时,获取下lock_binlog_end锁,获取下最新的末尾位置。2.2 半同步复制等待点调整 在MySQL5.6版本中,master将每个事物写入binlog传递到slave刷新到磁盘(relay log),同时主库提交事务。master等待slave 反馈收到relay log,只有收到ACK后master才将commit OK结果反馈给客户端。 这样主库返回给客户端commit ok前,可能事物已经真正提交了,但是从库还没有收到日志。这时其他客户端已经可以看到了提交的事物,这时如果主库故障,主备切换后,其他客户端发现刚才提交的事物又消失了。 为了解决这个问题MySQL在5.7.2中有了一个解决方案” transaction loss-less”.具体逻辑为master将每个事务写入binlog , 传递到slave刷新到磁盘(relay log)。master等待slave反馈接收到relay log的ack之后,再提交事务并且返回commit OK结果给客户端。即使主库crash,所有在主库上已经提交的事务都能保证已经同步到slave的relay log中。同时引入了参数rpl_semi_sync_master_wait_point来控制使用的策略,默认值为AFTER_SYNC,即5.7的改进做法,还有一个可选值为AFTER_COMMIT,即5.6中的策略。MySQL5.6处理逻辑图:MySQL5.7.2处理逻辑图:2.3 独立线程处理ACKMySQL5.6的semi sync 受限于dump thread,原因是dump thread 承担了两份不同且又十分频繁的任务:传送binlog给slave ,还需要等待slave反馈信息,而且这两个任务是串行的,dump thread 必须等待 slave 返回之后才会传送下一个 events 事务。dump thread 已然成为整个半同步提高性能的瓶颈。在高并发业务场景下,这样的机制会影响数据库整体的TPS 。为了解决上述问题,在5.7.4版本的semi sync框架中,独立出一个 ack collector thread ,专门用于接收slave的反馈信息。这样master 上有两个线程独立工作,可以同时发送binlog 到slave ,和接收slave的反馈。2.4 master支持等待多个slave在MySQL5.6中当半同步开启时,master只是保证一个slave接收到日志,这样当在一主多从的场景下,master和接收到最新日志的slave都挂掉了,那就会丢失数据。这样增加多个备库也不会提高HA的可用性。在MySQL5.7.3中新增了参数rpl_semi_sync_master_wait_slave_count,可以用来控制master需要保证多少个slave接收到日志。
总条数:273 到第
上滑加载中