查看Android应用占用内存情况

要查看指定app在手机上占用多少运行内存,首先将手机连接到电脑,然后在命令行执行下面的命令(其中com.my.package.name是app的包名):

adb shell dumpsys meminfo com.my.package.name

执行结果通常如下,其中Pss那一列的值(单位:kB)是我们主要需要关注的:

adb-dumpsys

参考链接:

adb shell dumpsys meminfo - What is the meaning of each cell of its output?

AndroidStudio无法在MIUI安装APK问题

现象

AndroidStudio 2.3,在小米4c搭载miui8真机上运行程序,提示下面的错误信息:

Installation error: INSTALL_CANCELED_BY_USER

 

解决方案

在MIUI开发者选项里,关闭“启用MIUI优化”选项。

关闭此选项时被要求重启,重启后暂时没有发现日常使用有什么变化。

Update: 关闭此选项后发现手机发热和耗电明显,应该是对后台应用的拦截失效导致的。

turn_off_miui_opt

参考链接:

Android Studio: Application Installation Failed

JVM内存模型

JVM的内存分为堆(heap)和栈(thread stack)两类区域,分别存放不同数据,规则如下。

以下数据存放在heap中:

  • 所有对象(object)。但对象的method里的local variable存放在stack中。
  • 所有对象的member variable,不论是primitive或是指向一个对象。
  • 所有静态类的variable

以下数据存放在stack中(每个线程有自己的thread stack,互相不可见):

  • 当前线程执行过的所有方法(method)
  • 当前线程内所有local variable(method里的变量)。如果此变量指向一个对象,则变量存放在stack中而对象仍然存放在heap中。
  • 当前线程内所有primitive类型(如int, long, boolean)的local variable

java-memory-model-3

参考资料:

Java Memory Model

Java 内存区域和GC机制

不守规矩的爬虫

在apache access log里看到很多一搜(现在好像叫“神马搜索”)的爬虫:

yisou

貌似完全不理会robots.txt啊:

User-agent: *
Disallow: 
Crawl-delay: 60

果断在tomcat的server.xml里禁掉IP段:

<Context ...>
<!-- 220.181.108.*, 123.125.71.*: baidu spider, 106.11.15*.*: yisou -->
<Valve className="org.apache.catalina.valves.RemoteAddrValve" allow="" deny="106.11.15\d.\d+, 220.181.108.\d+, 123.125.71.\d+"/>
</Context>

看看效果:

yisou-forbid

参考资料:

Tomcat doc - Remote Address Filter

大数据系统的若干瓶颈

一、Zookeeper里watch的上限

虽然zk本身没有对watch数量设置上限,但在实际场景里,由于watch数量过多导致系统资源被耗尽的情况偶有发生。

以一个实际场景为例,这个场景里有30000个设备,在zk里每个设备对应一个znode,然后有storm的topology对每个znode加上3个watch(通过curator,一个spout两个bolt),这个topology的并发是200。

计算一下总的watch数量就是30000x200x3=1800万个,按照ZOOKEEPER-1177的描述,平均每个watch占用100字节左右内存,1800万个watch大约占用1.8GB内存。这时必须相应调高zk能够使用的内存数量。

在命令行使用 “echo wchs | nc zkaddress port” 命令可以查看当前zk里watch数量,相当于先telnet到zk再发送zk提供的四字命令(完整命令列表):

zookeeper_watches

二、Impala分区数量的上限

根据Cloudera的建议(见:Impala maximum number of partitions),一个impala表最多使用10万个分区(partition),最好不超过1万个分区。

在实际场景里,假设我们想按“设备ID”和“天”对设备数据进行分类,那么当有20000台设备时,每年所需要的分区数量是20000x365=7300000个,已经大大超出了impala的限制,这时就要考虑调整分区粒度,比如从时间维度调整为每个月分一个区,从设备维度调整为将若干个设备分为一组再以组为单位分区。但无论如何,这些调整通常对业务应用是有代价,需要衡量是否能够接受。

要统计一个表有多少个分区,可以使用explain语句:

impala_partitions

要查看详细分区信息,使用show partitions mytable语句:

impala_show_partitions

三、Impala文件数量限制

(注:经过核实,此问题在通过JDBC且SYNC_DDL时出现,impala-shell里REFRESH通常不会超时)

直接修改HDFS上的文件后,需要使用Impala的REFRESH命令更新Impala元数据。当文件数量过多(例如200万个),REFRESH命令会超时。

解决方法:按Partition依次执行REFRESH命令,只要每个Partition的文件数量不多,就可以实现更新整个表的元数据。

REFRESH [db_name.]table_name [PARTITION (key_col1=val1 [, key_col2=val2...])]

四、HDFS文件数量限制

HDFS最著名的限制是namenode单点失效问题。

根据cloudera博客文章The Small Files Problem的解释,每个文件、每个目录以及每个块(block)会在namenode节点占用150字节内存,假设有一千万个文件,每个文件一个块,则总共占用20000000*150=3GB物理内存。

仍然以设备数据为例,假设我们想把每个设备的数据按天保存到hdfs文件,那么20000个设备每年产生730万个文件,三年2200万个文件,是硬件资源可以承受的数量级。但如果业务要求对设备的不同类别数据分文件存放,例如设备的高频数据与低频数据,则文件总数量还要乘以数据类型的个数,这时就必须考虑namenode物理内存是否够用。

Hortonworks根据文件数量推荐的namenode内存配置表如下(来源),可以看出1000万文件建议配置5.4GB,比前面估计的值(3GB)稍高,这应该是考虑到namenode服务器额外开销和一定冗余度的数值:

hortonworks_namenode_heapsize

使用“hadoop fs -count /”命令可以统计当前hdfs上已有文件数量,不过这个命令无法看到块的数量。

hadoop_file_count2

五、Parquet文件列数限制

Parquet是基于列的存储格式,最大优势是从文件中抽取小部分列时效率很高,同时Impala、Hive和Spark等大数据查询/分析引擎都支持它,所以不少大数据系统底层都是用parquet做数据存储。

由于parquet不支持对已有文件的修改,因此在设计系统时就要考虑文件里包含哪些列,就像为数据库设计表结构一样。值得关注的问题是,一个parquet文件里能包含的列数目是有限的,至于上限值是多少与多个因素有关,很难给出一个确切的数字。我查到的一些建议是尽量不要超过1000个列。

例如在PARQUET-222的讨论里,一个例子是假设一个parquet文件有2.6万个整数类型的列,因为每个writer至少需要64KB x 4的内存,写入这样一个parquet文件至少需要6.34GB内存,很容易超出jvm限制。

(三个月前遇到这个问题,当时没有及时记录,现在找资料又花了好几个小时,这次赶紧记下)

参考 PARQUET-222 和 PARQUET-394

在ClouderaManager上安装/更新Spark2

在Cloudera 5.9上安装/更新Spark 2.1.0。

1、下载CSD文件 http://archive.cloudera.com/spark2/csd/,将csd文件(.jar)放在/opt/cloudera/csd目录,并执行命令将文件拥有者修改成cloudera-scm

chown cloudera-scm:cloudera-scm /opt/cloudera/csd/SPARK2_ON_YARN- 2.1.0.cloudera1.jar

重启 cloudera-scm-server服务:

service cloudera-scm-server restart

然后在CM页面选择Cluster -> Cloudera Managerment Service -> 操作 -> 重启。

2、下载Parcel文件和对应的.sha1文件 http://archive.cloudera.com/spark2/parcels,将下载得到的parcel文件和.sha1文件复制到/opt/cloudera/parcel-repo目录下,并把.sha1文件改名为.sha(否则可能无法识别

mv SPARK2-2.1.0.cloudera1-1.cdh5.7.0.p0.120904-trusty.parcel.sha1 SPARK2-2.1.0.cloudera1-1.cdh5.7.0.p0.120904-trusty.parcel.sha

3、点击CM界面右上角的parcel按钮,再点击右上角的检查新Parcel按钮

cm-parcels

4、选择对应的parcel文件分配和激活(如果是新安装Spark2,在CM页面里选择添加服务 -> Spark 2.0)

5、按提示重启相关服务

cm-activate-parcel

Linux中目录的x权限

直接放结论:

x权限对于目录来说是非常重要的,当用户对某个目录没有x权限时,则不管用户对该目录下的子目录拥有什么样的权限,都将无法对子目录进行任何操作。

举例:

通常/root缺省是没有x权限的,因此mysql导出文件(SELECT ... INTO OUTFILE)到/root下的任何目录都会提示无法写入,不论这个目录的owner是谁,也不论这个目录是否777权限:

ERROR 1 (HY000) at line 1: Can't create/write to file '/root/my/out.csv' (Errcode: 13)

而/home缺省是有x权限的,因此在这个目录下mysql用户的文件夹,是可以用于mysql导出到文件的。

linux-directory-x

Spark安装和使用

安装Spark

略,见参考资料。

用docker安装spark

docker hub上有不少spark镜像,例如p7hb/docker-spark,可以快速安装好。

docker pull p7hb/docker-spark
docker run -it -p 4040:4040 -p 8080:8080 -p 8081:8081 -h spark --name=spark p7hb/docker-spark:2.2.0

进入Spark-shell

$ spark2-shell
Setting default log level to "WARN".
To adjust logging level use sc.setLogLevel(newLevel).
17/04/18 13:08:38 WARN spark.SparkContext: Use an existing SparkContext, some configuration may not take effect.
Spark context Web UI available at http://10.1.235.9:4040
Spark context available as 'sc' (master = yarn, app id = application_1491024547163_1752).
Spark session available as 'spark'.
Welcome to
 ____ __
 / __/__ ___ _____/ /__
 _\ \/ _ \/ _ `/ __/ '_/
 /___/ .__/\_,_/_/ /_/\_\ version 2.0.0.cloudera1
 /_/
 
Using Scala version 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_80)
Type in expressions to have them evaluated.
Type :help for more information.

scala>

在spark-shell里输入代码时,可以按tab键得到补全提示,很方便。

使用第三方jar包

主要通过下面这两个参数指定,注意两个参数中多个jar之间的分隔符是不一样的。

  • --jars driver和executor都需要的包,多个包之间用逗号(,)分割
  • --driver-class-path driver所依赖的包,多个包之间用冒号(:)分割

注:有一说是--jars里包含的包不需要在--driver-class-path里再次指定,但在spark2.0.0里发现仍然需要在--driver-class-path里指定。

使用java类/方法

scala> import java.lang.Double.isNaN
import java.lang.Double.isNaN

scala> isNaN(1) 
res57: Boolean = false

或直接使用全限定名:

scala> java.lang.Double.isNaN(1)
res58: Boolean = false

加载外部scala文件

事先写好一个test1.scala文件,然后在spark-shell里:

scala> :load test1.scala

注意load前面带一个冒号(:)

参考资料:

Spark On YARN 集群安装部署 (不错的安装教程,spark 1.3+hadoop 2.6)

Apache Spark技术实战之6 -- spark-submit常见问题及其解决