把kubernetes的log转发到cloudwatch有多难?

kubernetes日志收集 docer level 最近尝试把kubernetes的log转发到aws的cloudwatch中。原因当然是因为方便查log咯。之前试过用docker的方式导出,还是比较方便的。原理就是在使用kops创建集群的时候,添加两个配置项: aws cloudwatch权限 docker配置里,设置使用awslog作为log收集器 这样就可以简单的将kubernetes中所有container的log收集到cloudwatch下的group名字为:kubernetes的下面。唯一的问题是,log name使用的是container id,不够对人类友好。当时的想法是对kubernetes的dashboard进行二次开发。因为使用了docker方式的日志收集之后,dashboard下的log功能已经废弃了,所以我想在dashboard中点击log的时候,通过获取container id的方式,直接拿到cloudwatch下的log。听上去还是很美好的,也是可行的,但是并没有动手做。 fluentd 后来折腾grpc on istio的时候,因为看log实在是太麻烦了,本来问题就很tricky,看log又麻烦。索性就用fluentd重新搞了一遍log收集。 之前用fluentd cloudwatch搞过一次,因为失败了就没再搞。这次算是抱着必死的决心,一定要搞定! Helm 这里使用helm安装。首先吐槽helm,helm的文档基本是摆设,实际功能全靠自己摸索。虽然说kubernetes很复杂,但是人家谷歌文档确实写得好啊。helm的文档从排版到内容,啧啧… 我这里使用kops安装kubernetes的,是一台内存1g的linux,使用snap安装helm。helm在init的时候需要为tiller创建service account和role: apiVersion: v1 kind: ServiceAccount metadata: name: tiller namespace: kube-system --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRoleBinding metadata: name: tiller roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: cluster-admin subjects: - kind: ServiceAccount name: tiller namespace: kube-system 注意这里指定了namespace: kube-system。这是kubernetes所谓role based access controll(RBAC)的重要一步。少了这一步,后面会有各种各样的麻烦,helm文档居然没有强制要求,最佳实践也是一笔带过。 kube2iam 接下来需要装kube2iam。这里就需要之前安装的tiller了: helm install stable/kube2iam --namespace kube-system --name my-release --set=extraArgs.base-role-arn=arn:aws:iam::xxxxxxxxxx:role/,extraArgs.default-role=kube2iam-default,host.iptables=true,host.interface=cbr0,rbac.create=true 关于role的解释在这里。对应kops下创建的集群,kops会在aws下创建多个role,对应node的role的名字应该是nodes.xxx.xxx.local。这里的问题是,如果我现在需要对这个role新增policy,就需要每次修改node role。kube2iam就是来解决这个问题的。kube2iam使用了aws iam中assume的功能,这里的assume可以理解为承担。你需要创建一个新role,这里就是kube2iam-default,在这个role中修改trust relation:...

September 19, 2018 · 2 min · magicalne

Kubernetes on AWS

在AWS上安装kubernetes遇到了挺多问题的,主要是网络问题,受限于aws的各种安全机制。 推荐使用kops安装。新版本安装可以使用local模式,不需要对dns做额外配置。如果用非local模式,需要配合route 53,并创建一个域名用来绑定kubernetes集群。这里的域名可以是internal也可以是external。 如果是使用自定义的域名,要注意dns更新的机制。虽然aws route 53的dns更新是很快的,但是ttl默认设置的时间还是很长的,貌似要两天。所以如果改了dns,可能要两天才能生效,或者提前修改ttl,然后再更改dns。 还是local的模式好用,不需要配置dns,kops使用aws的elb做网关。 接下来要解决的问题是vpc。aws上不同的应用可能属于不同的vpc,不同的vpc之间是不能互相访问的。比如生产的数据库的vpc=sg-01,而kubernetes自己的vpc=sg-02。这时候kubernetes内的pod是无法访问DB的。 这里需要再vpc中加入peer connection,将两个vpc合并起来,并在各自vpc的route table上加上对方的路由解析。最后需要enable dns解析!我就是这一步漏掉了,导致pod无法访问db。理论上这时候pod就可以访问不同vpc的db了。

August 30, 2018 · 1 min · magicalne

关于HBase

用HBase也快一年了,记录一下心得和理解。 Why HBase? NoSQL之前还用过mongodb,当初选型就是HBase没跑了。关于各种NoSQL的特性讲解,可以参考那本很薄的《NoSQL精萃》。Mongodb是存document的,站在用户角度可以理解为JSON。很难想象Mongodb居然可以上市…这个东西很容易被用错。在上家公司供职的时候,有个同事说出了我对这个DB最大的concern: Mongodb写的时候不需要做额外操作,拿到Object就可以直接写。我们这个场景天然适合mongodb。 这里最大的问题就是,对于任何数据库,写不是最终目的,写是为了将来能够查询。如果不能方便查询,写是没有意义的。即使是写log,也要考虑将来是要以怎样的方式进行查询。 AWS上还有DynamoDB,看起来像是Mongodb,但貌似对标的是cassandra。 从业务考虑,主要存储目标是高表,可以考虑存储通话记录这种功能场景。一个人会有很多通话记录,我希望每个通话记录能作为一行进行存储。显然,HBase这种range scan的查询特性跟业务天然合拍。 SQL or No SQL? 从当年google的big table,提出摒弃SQL,使用编程的方式替代SQL。再到big table退出历史舞台,google又搞了个spanner出来,重新请SQL走出来解决一切。可以看出,SQL,真的,很重要。而在HBase上写SQL,只有phoenix一家支持。 Phoenix Phoenix的代码质量很让人操心。构造方法的参数列表写了好几行的,且没有任何长度限制(120?不存在的)。核心功能在小版本出bug,而且还是AWS上的选用版本。我在phoenix上踩的坑都好憋屈。跑到jira上去看描述都能把我逗笑了。。。 但是没办法,还得用。 CAP HBase是CP,舍弃了A。C体现在CAS、行锁、多版本等一系列特性,P体现在region和region server。那么如果一台server挂了,怎么办?会丢数据吗?一台region server挂了,只会影响对这一台server的读/写。之前保存在挂了的这台region server的数据,只要硬盘不坏,数据就不丢。HBase的写是先写WAL再写memstore。 这里确实体现了HBase不支持HA的软肋了。当zookeeper与这台死掉的znode之间的session断掉一段时间后,zk认为这台server真的死掉了,会把这个znode删除。然后,会触发恢复操作。因为底层使用了HDFS,所以数据会有3份,可以用来恢复。下线的region发散到集群中的其他节点。但是由于待恢复的region的数据来源于集群中,数据的本地性很差,所有会有性能问题。这还是停留在理论上。我当初发现的问题是,一个server挂了,HBase根本就没有恢复过来。。。 HA 那么就没有别的办法保证高可用了吗?勉强有一个,双集群,replica,也叫复制。详见jira:10070。估计是整个HBase中最复杂的部分了,能把读可用性提高的99.999%,当然写还是会挂。 其他 HBase的查询还是得靠缓存。所以业务上,一定要对cache友好。特别是HBase on AWS,HDFS后面还有一层EMRFS。没有命中缓存性能差了好多。 查询不要太复杂,3层left join顶天了。很多查询,我都是查询raw data,剩下的自己做处理。不要过于依赖phoenix提供的高级功能,能用基本功能替代就用基本功能吧,业务稍微妥协一下也是好的…

June 4, 2018 · 1 min · magicalne

半年aws emr有感

2017年6月末加入新公司,开始了一段startup之旅。核心技术自然是aws:) 我负责一个线上服务,后面拓展到了3个线上服务+一个hbase集群。多亏aws,我一个人就能hold住,几乎。 EMR 因为主要工作内容跟data pipeline有关,所以EMR用的更多些。一开始并不是很理解EMR的设计,踩过几次坑之后,也就明白了。 EMR的核心思想是把存储和计算分离。 传统的技术架构,计算和存储耦合在一起其实也没什么问题。比如从主库抽数据到data warehouse,再在比如spark/hive上做计算。但是这里的问题就是,这个data warehuose必须一直online。当然对于大公司这没什么问题。但是对于一个小公司,只有一个人用,平常可能一星期才跑一次spark,如果一直online是很贵的。 所以EMR的玩法就是,所有的数据都放在S3或者Dynamodb上,当然AWS自己的其他基础设施也行。S3最适合我们的情况。计算就用spark或者hive。hive就用作导表,spark用于具体计算。hbase集群也是依托于S3的。 EMR-HBase on S3 顺便一说,如果在生产环境下用hbase,一定要用on S3的模式,也就是存储是S3。而且要启动consistent view。这个consistent view用于集群和s3之间的一致性问题。怎么解决的呢?一致性用dynamodb再存一次咯。当然这里的dynamodb我们是访问不到的。 如果不用on s3模式,那就是一个玩具,随时会崩。虽然我无从得知具体的实现细节,但是可以猜测。hadoop那层还是要用hdfs的。而这层hdfs是在emrfs上面的。emrfs你是碰不到的,如果出现不一致的情况,只能让它崩溃了。这也就是consistent view存在的原因。 on S3的模式就不需要再像hdfs那样存3份了。hbase cluster会从s3 dir上读。实践下来还是有坑的。一致性的问题还是会有。不过稳定很多。 另外,hbase on s3就不再需要snapshot了,s3就是天然的snapshot。而且还可以加入replication cluster。但是这个replication是有损的。如果你用了phoenix on hbase,那么phoenix没法在replication上工作。我发现replication的实现方式是新增hbase:meta-cluster_id表。我记得hbase:meta这个表在master启动时会用到的,所以应该不是客户端配置的,通过修改指定hbase:meta表的方式,emr可以让你在只有一个s3 dir的情况下,为replication共享。用这种隔离系统表的方式,隔离replica。 Data pipeline 我所有的data pipeline都是围绕hbase的。入口处还有几个postgresql上主站的表。 不得不吐槽,hbase和phoenix对spark的支持真是差到家了。。。导表还是hive好,可以直接把hbase的表导到s3。postgresql的表是用spark导的。最后用spark sql跑计算,并把结果写回S3。 spot instance是个好东西,可以省不少钱。

February 17, 2018 · 1 min · magicalne

JDBC, field binding, 驼峰 VS 下划线

项目引入phoenix之后,也一并引入了很多问题,那天估计脑子真的热了,居然花时间做了这样的一个功能;( 事情是这样的,phoenix是hbase上层的sql“解释器”,用来将sql翻译成对应的hbase scan,带来了很多副作用,但是,有一个好处,这是SQL!会让你少写很多模版代码,而且处理数据的方式更加标准。 JDBC这块,没有引入ORM框架,整个项目也没有使用到spring,项目角度更佳倾向轻量级。但是为了开发速度,引入了apache下的commons-dbutils。 这里有个小小的问题,像ORM框架一样,你在select的时候,如何将JDBC返回的数据映射到java中的entity呢?当然是按字段名称一一映射了啊。可是,数据库的字段都是下划线风格的,而java都是驼峰风格的。。。那天真是强迫症犯了啊。。。居然一定要做一个风格转换。。。 通用的解决方案就是用annotation,比如json parser,可以通过定义类似@JsonProperty(“loan_id”),重新进行mapping。但是写annotation还是有点麻烦的,这里用了偷懒的方案。 dbutils支持通过自定义BeanProcessor来实现类似功能。而我这里的功能只需要将,大写的下划线风格转化成驼峰风格即可。 这里只需要继承BeanProcessor, 并实现方法: public int[] mapColumnsToProperties(ResultSetMetaData rsmd, PropertyDescriptor[] props) throws SQLException @Override public int[] mapColumnsToProperties(ResultSetMetaData rsmd, PropertyDescriptor[] props) throws SQLException { int cols = rsmd.getColumnCount(); int[] columnToProperty = new int[cols + 1]; Arrays.fill(columnToProperty, PROPERTY_NOT_FOUND); for (int col = 1; col <= cols; col ++) { String columnName = rsmd.getColumnLabel(col); if (null == columnName || 0 == columnName.length()) { columnName = rsmd.getColumnName(col); } String propertyName = columnToPropertyOverrides....

January 5, 2018 · 1 min · magicalne

Oh spark,贫穷限制了我的想象力

两天前写好了一个一千行的spark sql,测试好了准备把结果写到s3上,结果写了两天没写进去,今天终于搞定了,特此记录。 事件回顾 这条sql不算复杂,左表10000+行,来回join两张千万级的表和两张不到一亿的表。中间所产生的内存开销为28.5G(从spark UI的input字段得出)。查询结果可以show出来,可以limit 100,但是limit 1000就不行了,更不能写到s3上。 报错信息是: spark.java.lang.OutOfMemoryError: Unable to acquire 44 bytes of memory, got 0 at org.apache.spark.memory.MemoryConsumer.allocatePage(MemoryConsumer.java:127) 我跟这个error纠结了两天。。。查了文档,jira,各种查,各种参数各种试,总是在最后一个stage弹出这个error。 spark版本是2.2.0,使用的是aws的emr,因为是28.5g的数据,我就开了core nodes是5台8 cores, 15g mem的cluster,这里的可用内存大概是55g,我心想怎么也该够用了吧。 今天试了2.0.0和2.1.1版本,都得出了相同的错误。还咨询了之前搞spark的同事,依然没搞定。 当我第三次阅读doc时,我隐约发现了问题所在。 什么是storage 这里就不介绍spark的内存管理了,贴个简介。 简单理解storage,spark是把内存当硬盘用的,来持久化rdd,所以大多数情况,storage就是内存,但是当内存不够用的时候,就会出现spill to disk的情况,也就是写硬盘。所以如果storage足够大,内容就会被保存在内存中,反之就会spill to disk,当太小时,可能会出现频繁spill to disk,从而影响效率。 那么,executor的内存怎么办呢 executor会占用另外一部分内存,但是我现在不确定executor会不会spill to disk。至少凭借现在的现象观测下来,executor在shuffle write的时候是不会spill to disk的。这里也好理解,毕竟shuffle是要在executors之间转移的。 两个参数spark.memory.fraction和spark.memory.storageFraction spark.memory.fraction默认是0.6 Fraction of (heap space - 300MB) used for execution and storage. The lower this is, the more frequently spills and cached data eviction occur. The purpose of this config is to set aside memory for internal metadata, user data structures, and imprecise size estimation in the case of sparse, unusually large records....

December 15, 2017 · 1 min · magicalne

妈呀,内存又爆了

最近写了一个从postgresql到hbase导表job。pg中存的是text,有的条目还是很大的。 一开始没怎么注意,就用了200个线程导,发现很快内存就到上限了。回头看代码,也没啥问题,都是GC友好的。于是线程数降到100,内存还是慢慢的爆了。现象就是内存不断上涨,基本不下降,phoenix client在commit的时候会报奇怪的exception,这个时候job基本就不能工作了。挂上GC log继续跑了一次,发现GC基本无效,但还是有效的,只不过回收的很少。 回头再看业务,我是根据customer id出发导表的,每个id会有n条记录,每条记录是一个长度为m的数组,需要序列化整个数组,然后打平。我猜问题就出现在这里了,很多customer的记录会有上万条,每条记录的数组长度如果太长的话,这个序列化之后的list会很大。但是这些对象只是大而已,导的速度还是很快的,导完就可以扔掉了。 于是尝试使用G1GC,虽然没有都没有调大heap空间,但是效果明显改善,GC十分有效。 默认Parallel collector的log,连续几次的full gc: 6.783: [Full GC (Metadata GC Threshold) [PSYoungGen: 2880K->0K(424960K)] [ParOldGen: 8036K->8024K(52736K)] 10916K->8024K(477696K), [Metaspace: 20761K->20761K(1069056K)], 0.1111975 secs] [Times: user=0.11 sys=0.00, real=0.11 secs] 11.710: [GC (Metadata GC Threshold) Desired survivor size 11010048 bytes, new threshold 1 (max 15) [PSYoungGen: 413842K->7153K(424960K)] 421867K->22659K(477696K), 0.0436894 secs] [Times: user=0.04 sys=0.01, real=0.04 secs] 11.754: [Full GC (Metadata GC Threshold) [PSYoungGen: 7153K->0K(424960K)] [ParOldGen: 15506K->19928K(86016K)] 22659K->19928K(510976K), [Metaspace: 34780K->34780K(1081344K)], 0.2035734 secs] [Times: user=0....

November 17, 2017 · 2 min · magicalne

快餐

我还是挺喜欢吃快餐的,基本每周五中午都会固定吃一顿麦当劳巨无霸中号套餐,当然可乐一般不会喝。跟很多人一样,吃饭这事一直纠结着我。每天花心思和时间思考今天吃什么,然后排队等,或者面对琳琅满目的外卖无从下手。。。麦当劳挺好的,比肯德基好吃一点,但是没有汉堡王好吃,可是汉堡王贵啊,麦当劳还是比较亲民的。而且这些快餐绝对干净,吃完绝对不会拉肚子。你也不用思考到底要吃什么,反正就那些东西,从小吃到大。很多人说吃快餐不健康,呵呵,类比“不含糖”的味全果汁系列,其实没有比可乐好多少,而且这些快餐绝对没有地沟油。 午餐还好一点,晚餐是最难办的。不加班还好,可以回家自己做点吃,一旦加班可选择的余地就更少了。我一直希望能有一个互联网思维的晚餐解决方案。我也不用能有多好吃,也不用很多花样,固定几种就行,最重要的就是要方便,要快,健康,别太贵!有肉有菜即可。别让我做选择,不行我多花点钱买个清静。 这就是非常现实的问题,我相信不是我一个人有这个需求,这就是我们现在的生活。我以前从来没有想过如今的生活节奏是如此之快。快到没时间停下来思考,真正有时间停下来思考的时候,又没有时间做出改变了。。。 现在的人,面临太多选择,但是选择的成本太大了,更好的服务才是我们所追求的。 上大学的时候玩wow,那时候wow已经有点走下坡路了,国服更是如此。一个号从无到满级,到装备毕业,都有淘宝店铺出售,玩家只需要花钱就行了,甚至自己都不需要上号。慢慢的游戏也变味了,大家都是快餐,魔兽世界里没有人再跟你一起探险,他们只是买家和卖家。 连知识都能“听”来的时代,时间真的是越发宝贵了。就拿我来说,我自认自己的开发能力就是入门级别的。而且可悲的是,公司的工作不能压榨完我所有的精力。就是说,我从每天8小时工作收获而来的经验和知识不足已支撑我不断提升自己的能力。所以下班后,理所当然的要搞一些高端大气的东西。诚然,国内没多少公司需要招了解底层的程序员,甚至面试官自己都不知道所谓的底层到底是哪一层,这些东西当然也只能利用零散时间自学。所以就在工作的时间赶快把工作做完,尽量别出事故,没事不加班,下班赶紧走人。路上走快点,到家随便吃点,休息一下赶紧开搞。这里也有需要权衡的地方,我是很喜欢健身的,饭后的黄金时段到底干点啥呢?锻炼身体了就没时间搞技术了,但是身体也很重要啊。于是就养成了一个能跑就不走的习惯。半年前开始的,早上赶公交都是跑着去的,尽管那时候是夏天。。。这几个月发现,下午5点钟不吃点东西,很难坚持到吃晚饭,毕竟哥年轻,新陈代谢快嘛。于是最近几个月每天下午去旁边的85度C买面包吃,然后就瘦了4斤:)神奇不,神奇的地方就在于,我是跑着去跑着回来的。。。年后寻思着,写个日程表,把每个星期的事宜都按时间定下来,继续权衡健身和搞技术。 10000小时理论 VS 快餐 面对10000小时理论,是否有健康的快餐服务?这个快餐服务说的就是微信公众号和罗辑思维类型的知识服务。我现在对微信的公众号越来越失望了,干货变软的风向不变,搞技术的也确实不能指望看一篇文章就提升技术,除非你看完了又跟着写了段代码验证作者的想法。基本上,我已经放弃了从公众号学习技术了。公众号就是读读新闻的地方。与提供干货不同的罗辑思维是一个冲击认知的地方,得到APP也同样是一个听新闻的地方,保证你在理论道路上不会走的太弯。但是回过头来还是要靠自己的实践去验证理论学习成果。我认为所谓的10000小时理论所需要的时间会越来越短,因为知识服务的出现,各个领域的知识体系会慢慢变得得体、明确、清晰,你去学就好了。

January 29, 2017 · 1 min · magicalne

2016年终总结——What A Fucking Year...

马上就要过年了,回顾一下2016的成长。 简述 react全家桶太复杂,不能听任大厂任意吹逼,缺点也是技术选型的关键; spring boot隐藏的细节有点多,不看文档不知道的东西太多; checkstyle, findbugs和sonar都是好东西,帮助你写出bug free的代码; Intellij很好很强大,经常更新版本吧,惊喜不断; 使用依赖注入时,不要用field injection,应该配合lombok使用constructor injection; rabbimtq是用来处理CPU密集型的场景,不适合IO密集型的场景; GC的核心是如何做到异步的GC,以及自动调优,期待G1; java不适合做客户端应用,如android。GC严重影响使用,只能期待程序员好好写代码; 同理go不适合写android。引申,go不能做android开发的话,就不会持续的发展; 正确学习java的路径:语法>基础>集合>io>多线程>内存模型>GC>JIT>JVM。 先说前端 2016年的前端应该是前端开发的元年了。这一年发生了好多。。。我从来没想过做专职的前端开发,我不喜欢js这个语言,不喜欢css。但是我不排斥开发前端。毕竟作为最直接的交互界面,前端是很重要的。这一年有一段时间一直在搞前端,用了react全家桶。。。总感觉facebook很不负责任的说,大厂吹的很好听,但是一旦入坑就不是那么回事了。react的难点在于如何一开始构建一个框架,全家桶里的东东实在太多了。。。影响开发体验。不过框架搭起来就好了,模块化代码还是挺好的体验。但是我写前端的时间越长就越排斥前端。心中bgm是:哎,只要把xxx做成xxx就行了,可是好烦啊,不想搞啊,asfjoqwekfhnjr…..还是赶紧搞定去搞别的吧。。。还是喜欢写java。 java 曾几何时,我还是挺不喜欢java的。那时的我还是php神,嗯,PHP is the best language!现在不那么想了,java真的是能接触到的最优秀的语言,而且是最能提升自己对整个计算机系统理解能力的语言。java足够高层,可以对实现做抽象,同时java又足够底层,可以让你放手去实现底层的构建,这一切都需要你去深入到os、cpu、内存中去,去窥探那个奇妙的世界。几个月前开始订阅java社区开发的邮件,期间逐渐证实了我之前的一个猜想——java做了太多努力在跨平台上。跨平台绝对是黑科技!因为跨平台不仅仅跨的是操作系统,还在跨cpu。java要针对不同的os和cpu去订制实现,真的是订制!现代cpu想跑的快,硬件上只能多加core,软件上尽可能的在单个core上完成任务,每个core都有自己的寄存器,实现新的reorder、barrier,但是多线程编程一定要跨线程上下文,阻止reorder,合理的打开barrier。不同的os和cpu有各自不同的实现标准,然而java却要综合所有平台的特性,一再强调跨平台。如果一上来,没有把跨平台作为java 的首要特性,我相信java一定会发展的更好,不过话说回来,如果没有点噱头,估计java都活不下来。 我觉得2016年,我真的是跌跌撞撞地把java打通关了,我可没说我精通,但是精通是早晚的事,2017年差不多可以,2018年以后我就可以放开手脚研究os和硬件了。我发现学java最好的途径还是读specification和官方文档。thinking in java、effective java、JCIP都是很优秀的书,但是有一个问题,这个问题特别像是读技术博客。在博客中,博主对技术的阐述,只是自己的理解,等到读者再读的时候,那仅仅是读的博主的理解而已。这些优秀的技术书籍只能帮助你理解java,但是本质上那都是作者们自己的理解以及他们的宝贵经验。想要了解java真实的一面,还是要下狠功夫读晦涩的说明和相关论文。 一个理论 关于技术,今年我还得出了一个理论:对于客户提出的需求,如果程序员认为这个需求要超过3天才能完成,有三种可能: 需求不合理,不是说不能做,只是不合理,比如设计的功能不应该包含在该系统A当中,应该放在系统B上; 系统架构不正确; 程序员没能力。 这个理论是我从自身出发,并参考其他同事的日常工作得出的结论。需求肯定永远都是对的,只不过提需求的人多种多样,很有可能用户没有受过专业训练,用户其实并不理解自己真正想要的是什么。这种问题慢慢沟通,提供自己的建议就好。第二种可能是灾难性的,架构上缺乏思考,扩展性不够,程序员的锅啊。今后可能因为这个问题出现各种各样的bug,慢慢你就是救火队长了。第三种情况很微妙,很少人会承认自己的能力上限。特别是在工作中,有kpi考核呢。平常开玩笑的时候可以说说,你不行啊,你个弱鸡,你没能力啊。但是大家都是码农,有些事不必戳穿。 要做正确的事 我经常说:要做正确的事。不论是做人,还是做技术。做技术的没有不踩坑的,踩坑了不怕,就看你怎么走出来,怎么解决问题。经常说,哎呀,系统出bug了,要是想彻底修好应该从底层xxxx,但是这样太费时间了,还是先hotfix一下吧。。。我觉得这时候就是体现一个程序员能力的时候,这时候就应该分析清楚应该怎么解决这个问题,然后贯彻执行力,解决这个难题,只有不断的自己跟自己较劲,技术才能不断提升。有时候在修很恶心的bug 的时候,我也很想放弃,换个捷径走,但是我自己是知道的,这个时候恰恰是提升自己能力的时候。因为你在做你不熟悉的事,而做不熟悉的事是最能锻炼人的,你在突破你的瓶颈,冲出你的舒适区。 代码质量 Dijkstra曾经说过一句很有意思的话,大意是: 测试只能测试出测试出来的bug,但是不能测试出没有被测试出来的bug。 那怎么才能找到那些藏在代码中的没能被发现的bug呢? 不断的看自己写的代码,不断看别人写的代码,这就是一个重构的过程,你总会发现之前想的不周到的地方,开发不是一蹴而就的。而是不断迭代不断重构的过程。程序员不应该把功能开发完之后就万事大吉了,后面随便测测就再也不看了。出了bug甩给别人那简直就是可耻了。。。 最后 自己还有很多不懂的地方,2016再见,2017加油!做一个快乐的傻缺技术男。。。

January 24, 2017 · 1 min · magicalne

在java应用中集成jython

今天是新年第一天,但是上周欠的帐还没还完。 上周在规则引擎的系统中加入了python部分的实现,即jython,碰到了好多坑。。。有种要死的感觉。。。其实系统之前已经集成了groovy,使用的实现是GroovyScriptEngine,就是把一段字符串形式的groovy脚本通过groovy classloader加载进来,相当于直接执行编译后的class文件。为了能平滑支持jython,实现改成jsr223的通用实现,即script engine,同时支持groovy和jython2.7(项目需要这个老版本。。。)。改完之后,jython各种不支持,然后就一点点填坑。最后就剩下最后一个问题了!本地可以正常运行,但是上到demo环境就不行了。。。然后会神奇的抛出一个空指针异常,说对应的engine没有找到。debug跟进去发现jython初始化engine的时候需要到一个叫Lib的地方import一些library,其中有一个“site”没有找到。然后就没法初始化这个jython engine。 我发现之所以,本地可以跑jython是因为跑spring boot的时候我是在intellij中用main函数的方式跑,这时候所引用的第三方jar是从.gradle/cache下读的。而通过gradle build,然后java -jar xxx方式启动的时候,jython会向这里找Lib:/Users/magiclane/git/dr/morpheus/api/build/libs/api-1.0.jar!/BOOT-INF/lib/jython-standalone-2.7.0.jar。 问题就出在路径上了。 spring boot build称可执行的jar之后的结构是这样的: example.jar | +-META-INF | +-MANIFEST.MF +-org | +-springframework | +-boot | +-loader | +- +-BOOT-INF +-classes | +-mycompany | +-project | +-YourClasses.class +-lib +-dependency1.jar +-dependency2.jar 而且java -jar xxx的时候,这些第三方jar也不会被打开。 然而jython找Lib的默认位置是通过自己的找一个class的classpath定位的。 String urlString = URLDecoder.decode(rawUrl, "UTF-8"); urlString = urlString.replaceAll(escapedPlus, plus); int jarSeparatorIndex = urlString.lastIndexOf(JAR_SEPARATOR); if (urlString.startsWith(JAR_URL_PREFIX) && jarSeparatorIndex > 0) { // jar:file:/install_dir/jython.jar!/org/python/core/PySystemState.class jarFileName = urlString.substring(JAR_URL_PREFIX.length(), jarSeparatorIndex); } 然而真实路径却是在那之前还有一段:libs/api-1....

January 1, 2017 · 1 min · magicalne

GC是个什么鬼

今天看了一个视频。挺有意思。标题是:Understanding Java Garbage Collection and what you can do about it。Speaker是Gil Tene,他是Azul的Vice President of Technology and CTO, Co-Founder。 这个视频开头就埋了一个大大的伏笔。Gil问大家,各位所在公司开发的系统的服务器内存有多大?1/2 GB? 1GB? 2GB? 4GB? 10GB? 20GB? 50GB???细想起来这个问题真的好有意思啊。 接下来就是各种扫盲。虽然关于GC也看了不少,但是学的不系统,知识总归没成体系。这个视频还是有一定点拨作用的。最重要的是,这个视频不是一板一眼的跟你讲GC,而是很诚实的说出来了GC没能做到的,或者说是很难做到的。然后你就可以以此来判断这个jvm实现是否够好了:)当然,那都是套路。。。 视频说到,GC可能需要分代收集,不同代有不同的收集策略,而GC最终需要解决的就是STOP-THE-WORLD。如何尽量减少stop-the-world的频率和时间是JVM需要考虑的首要问题。然而这里面有各种各样的tradeoff。考虑到当年是2010年,当时的Hotspot的G1和CMS,并不是完全的concurrently,而是mostly。所以CMS还是会stop-the-world。 这里又引出了另一个问题,理论上,如果可用内存足够大,永远不需要GC。而如果可用内存过小,就越需要GC。一个系统理论上,平均每秒会产生1/4G~1/2G对象,这些对象都是GC需要关心的。如果GC回收内存的速度比应用产生垃圾的速度快,那么应用自然会正常运行。一旦GC回收内存速度没能跟上应用产生垃圾的速度,应用就会慢慢死掉。如果应用所使用的server内存很大,stop-the-world所产生的影响会更加明显。所以就会碰到一个问题——Java application memory wall。就是说java 应用不能完全利用server的硬件资源,具体说就是越来越大的内存。 那这个问题看起来好严重啊,内存越来越便宜,但是GC不够屌,只能用那么几G内存,太大了就性能不好。怎么办呢? JVM tuning 完全不去stop-the-world,垃圾回收都是完全并行处理。 这个视频最后很有意思,峰回路转有提到了视频开头提到的问题,就是问各位开发的系统的服务器内存有多大?Gil所在的Azul开发的Zing使用的核心GC算法叫C4(Continuously Concurrent Compacting Collector),比当时的hotspot要屌的多。因为zing没有stop-the-world,是完全concurrently。然后对比jvm tuning,又把hotspot狠狠黑了一把。 一般来说hotspot的jvm调优看起来是这样的: •Examples of actual command line GC tuning parameters: •Java -Xmx12g -XX:MaxPermSize=64M -XX:PermSize=32M -XX:MaxNewSize=2g • -XX:NewSize=1g -XX:SurvivorRatio=128 -XX:+UseParNewGC • -XX:+UseConcMarkSweepGC -XX:MaxTenuringThreshold=0 • -XX:CMSInitiatingOccupancyFraction=60 -XX:+CMSParallelRemarkEnabled • -XX:+UseCMSInitiatingOccupancyOnly -XX:ParallelGCThreads=12 • -XX:LargePageSizeInBytes=256m … •Java –Xms8g –Xmx8g –Xmn2g -XX:PermSize=64M -XX:MaxPermSize=256M •-XX:-OmitStackTraceInFastThrow -XX:SurvivorRatio=2 -XX:-UseAdaptiveSizePolicy •-XX:+UseConcMarkSweepGC -XX:+CMSConcurrentMTEnabled •-XX:+CMSParallelRemarkEnabled -XX:+CMSParallelSurvivorRemarkEnabled •-XX:CMSMaxAbortablePrecleanTime=10000 -XX:+UseCMSInitiatingOccupancyOnly •-XX:CMSInitiatingOccupancyFraction=63 -XX:+UseParNewGC –Xnoclassgc...

December 22, 2016 · 1 min · magicalne

Paper -- "The java.util.concurrent Synchronizer Framework"

最近在看Doug Lea的那篇基于AQS实现的理论论文。陆陆续续看了几遍。论文的问题就是作者会把一个或者一类具体问题进行抽象和总结,针对这个或这类问题进行研究,并试图站在一个更高的位置得出结论。这就导致,读这篇论文还是有点难的。主要的难点是细节理论的理解。毕竟这些论文看的少,很多上下文不是很懂,但是若是为了彻底理解这些,把引用的论文都看一遍,有点累。。。 我还是从作者的出发点开始理解吧,试图理解整个AQS为什么被设计成这样的。后续会跟着写几篇关于java memory model,reorder,barrier的文章,试图把整个多线程技术,从最顶层java实现到最底层现代cpu、寄存器、内存全部串起来。另类撸串:) Introduction 首先看看什么是synchronizer——同步器。所谓的synchronizer就是用来维护一组状态的变量,在java1.5发布的AQS中,就是一个32位的int,后面在java1.6发布的时候,又开发了基于long 64位的sycnronizer——AbstractQueuedLongSynchronizer。 Among these components are a set of synchronizers – abstract data type (ADT) classes that maintain an internal synchronization state (for example, representing whether a lock is locked or unlocked), operations to update and inspect that state, and at least one method that will cause a calling thread to block if the state requires it, resuming when some other thread changes the synchronization state to permit it....

December 10, 2016 · 2 min · magicalne

到底什么是高科技?

现在是北京时间凌晨00:47,实在是睡不着,起来写点东西吧。 最近迷上了听“罗辑思维”,经常写代码的时候也听。其实一开始我是没法在写代码的时候听这种东西的,因为会严重分心嘛。但是周围确实又太吵了,听歌又很容易听腻,所以有一天就放着罗辑思维写代码,效果还不错。当然了,我是全神贯注写代码的,很少听罗胖在说啥,只是偶尔停下来听一听,放松一下。其实,这时候我只是需要一个人在我旁边瞎逼逼就可以了:)正好罗胖的声音又是那种我特别喜欢的肉肉的、憨憨的声音,哈哈。 话说回来,罗辑思维这个栏目我很喜欢,喜欢的原因不仅仅是可以在我写代码的时候充当主动降噪的功能,而是可以听一个跟自己没有半点交集的人在跟你瞎逼逼。不能说罗胖说的都是对的,但是他确实在一定程度上改变我,而且是正在改变我!在我听罗辑思维之前,从来没想到这种知识分享可以做到这样的效果。经常看所谓的技术干货,使得我已经对所谓微信时代给我们带来的这些种种,早已失去了兴趣。每天利用零散时间看几个技术公众号就能提升技术?半年前这话我还信,现在的话,我觉得公众号还是用来扫盲比较好。 得到 说到罗辑思维,就得说说得到这个app,今晚睡不着觉,很大原因就是因为它!和他!——王煜全。 先说说得到这个app,这个app在我看来,就是一帮人在里面胡说八道,而且你还得花钱听他们胡说八道,越贵的栏目越“胡说八道”!此处没有半点黑的意思,而是绝对的粉啊! 你平常所能接触到的人跟你的家庭有关、学校有关、工作有关。我作为一个普通人,我每天所能接触到的人不超过5个,我的圈子就是这么小。。。我的认知范围也就这么小。尽管每天我都会接触各种各样零零散散的信息,但这些信息实在是太碎片了,使得我看到的世界太不真实了。而得到给我了一个机会,可以接触各种各样的人,听听他们的想法,从而改变自己的认知。对和错真的没那么重要,他们是不是在那一本正经的胡说八道也不重要。重要的是,他们都是相对成功的人,他们会对各种问题有自己的态度,听听挺好。 我在得到上买的第一个就是王煜全的“前哨”。乱序听了大约一星期吧,直到今天才真正听到了震颤我灵魂的东西。今天所听的标题忘记了,内容可以理解为:为什么王煜全在美国投资却不去硅谷,真正的高科技创新到底是什么,硅谷的核心是什么。王煜全在前哨中多次提出,美国现在的主流创新是指一个牛逼的教授拿着显著的科研成果加上一个牛逼的CEO。这个牛逼的教授的科研成果领先世界,或者说是世界第一,可以投入生产应用。而这个牛逼的CEO是一个有信用背景的,就是说他/她多次创业成功。那些个vc就是照这样的项目投资。那所谓的科研成果指的是什么呢?可以是太阳能电池载体、可见光、光传输等等,那些在我们周围不怎么出现的,但是一定是有出路的,能标志未来的科研成果。这些科研成功一旦量产,垄断这个词已经不足以形容了。而一个有信用的CEO,多次创业之后,下次创业也更有可能成果。所以vc会主动找到他们,求他们分一杯羹。 那么问题来了, 为什么这些科研成果大多数没有流向硅谷? 既然这些CEO这么屌,为什么我们没有听说过他们? 到底什么才是高科技? 为什么这些科研成果大多数没有流向硅谷? 这个问题可以转化为硅谷有什么。从王煜全那里听来,大多数硅谷的公司依然是模式创新。确实,像facebook这种的,本身功能并不复杂,只是在模式上创新了。然后就需要迅速的扩张,时时刻刻保持警惕,保证自己在社交领域走在前面,因为毕竟除了积累的用户,其他也没啥优势。twitter其实也是如此,就如彼得蒂尔所说: “We wanted flying cars, instead we got 140 characters.” 他们是创新,但仅仅是模式创新,就算是docker这种技术创新,想尝试解决devops的问题,也会在后来引来新的对手kubernetes。 既然这些CEO这么屌,为什么我们没有听说过他们? 因为他们不需要出名。相比较本科没毕业,辍学创业,几年后成为最年轻的亿万富翁的CEO,和多次创业,这次又跟某某著名教授合作创业的CEO,显然前者更能捕捉媒体的关注。 到底什么才是高科技? 不言自明,那些教授的最新科研成果自然就是高科技。最近在用同事的bose qc25,主动降噪真是叼得一逼,只是因为自己不争气,从小就耳鸣,带着听一段时间就头好痛。然而我想说的是,bose的发明人,是一个博士,最初主动降噪是军用的,bose最终将其产品化。可能现在来看这已经不是啥高科技了,但是这就应该是世界前进的方向。好的产品应该帮助我们成为更好的人,过上更加舒适的生活。而不是打擦边球,去约炮、看热闹,或者促使你把时间消耗在毫无意义的事情上面。 由此我在想,我所从事的开发工作能算是高科技嘛?从王煜全的观点看,显然不是。像我这种的程序员,虽然有机会开发比较底层的系统,但仍然是面向业务的。淘宝再大也是一个CRM,你的模式再新颖,你所构建的产品也无非就是围绕CRUD组成的一个或多个系统。这么一想自己好失败啊。以为自己是个匠人,其实也不过如此。你可以说软件是高科技,但不能说每个软件都是一个高科技产品。

November 18, 2016 · 1 min · magicalne

Memory model

内存模型是一个相对底层的概念,对于很多编程语言来说,内存模型是对程序员屏蔽的概念。但是如果是涉及并发编程的话,内存模型是一个逃不开的知识点。本文是对自己理解的Java Memory Model的知识梳理。 What is reordering? 你所写的java代码会被compiler、JIT、cache、instructions、CPU执行时重排序。 Source order – how you write the code – Naïve expectation that everything happens exactly as written • Program order – generated machine code presented to the CPU – “Any resemblance to source order is purely coincidental” – It’s all about performance! As long as you can’t tell the difference • Execution order – how the hardware actually executes the code – Speculative execution, instruction reordering, caching, pipeline stalls – It’s all about performance!...

November 17, 2016 · 3 min · magicalne

关于java异常

写代码的时候一直使用checkstyle和findbug,checkstyle和findbug可以帮助你写出标准规范的代码。特别是跟intellij插件结合之后,你的缩进、格式、甚至是异常控制都会被规范化。比如之前的项目中,对于异常的管理很不严格,大家都是所以的throw Exception和catch Exception。所以有一天我就抽时间将这些Exception都改成了具体的某个异常。 但是我发现,有些地方catch Exception甚至是catch Throwable是很必要的。 比如rabbitmq处理rpc返回的地方,如果发送的rpc请求在worker端发生异常,这个异常可能是任何异常,甚至可能是一个Error,所以rabbitmq这里其实会throw Throwable。当然,这里其实是spring amqp抛出的。但是在处理这类异常的时候,还是不应该让Throwable流到上层,因为客户端只需要知道这里出现异常应该怎么办,而不是关心整个异常链。所以应该这样: try { xxx } catch (Throwable t) { Throw new SomeRPCException(t, "blablabla"); } 另外在试图从异常中恢复,特别是在多线程中,尝试因为抛出的异常终止或恢复线程,也需要catch Exception/Throwable。

November 16, 2016 · 1 min · magicalne

关于java中的assert

自从最近才发现,我一直在用错误的方式使用assert!这一切都源于自己的想当然没有去阅读java doc! 原来我使用assert的方式就是方法的输入参数校验,这是错误的。特别对于public方法。因为assert校验失败之后抛出AssertError,而不知道到底是什么原因导致了校验失败,所以参数校验还是应该使用IllegalArgumentException。 Do not use assertions for argument checking in public methods. Argument checking is typically part of the published specifications (or contract) of a method, and these specifications must be obeyed whether assertions are enabled or disabled. Another problem with using assertions for argument checking is that erroneous arguments should result in an appropriate runtime exception (such as IllegalArgumentException, IndexOutOfBoundsException, or NullPointerException). An assertion failure will not throw an appropriate exception....

November 4, 2016 · 1 min · magicalne

javascript的一些问题

版本问题 这里说的javascript版本问题不是指浏览器兼容性。其实对于开发来说,有了babel的话,根本不需要担心版本问题,我一样可以用最新的语法特性。 但是问题是,每一次javascript的版本升级,不像是真正意义上的版本升级。诚然,每个版本都在解决一部分问题,但是各个升级后的版本看起来更像是一个新的方言。光导入就有AMD、commonjs和es6 import。平常用的时候可能感觉没啥问题,我能用es6的地方都是用import的,但是有些地方使用import是不能解决问题的,你需要使用require。比如在webpack使用react引入图片的问题,你只能使用require,而不能使用import!!! <Image src={require('../img/xxx.jpg')} /> 当时各种场景都试过了,根本没有思考要使用require去做这件事,结果试了一天都没把一个破图片搞出来。。。最后换了require试了一下居然出来了。。。后来认为应该跟webpack的加载方式有关系,尽管babel是会把import翻译成require的,但是webpack是把图片hash过的,这里需要一层mapping吧。总之,看前端项目的时候,就是import中混合了零星的require。 javascript给开发人员带来了无限的想象 此前,没有任何一款语言能像javascript一样,像瘟疫一样在世界各个角度,横向纵向的传播。从最早活跃在浏览器内部的js,到后台依托v8的nodejs,在到后来的桌面应用,甚至翻译成lua之后可以跑在嵌入式系统。一部分人极力推崇javascript是多么多么强大的语言,一部分人承认javascript是有问题的,并认为迭代开发升级是解决之道,另一部分人认为js。。。我就是最后一部分人,这语言坑太多了。真正能够掌握这门语言得记住多少pattern啊。 github统计贡献代码量js常年排第一,为啥啊?还不是因为这语言问题太多。前端发展到现在还是围绕着html、css和js,到现在前端也没有一个说的出来的最佳实践。每一个新框架、库仅仅是尝试解决某一个问题,要想真正构建一个生产级别的环境,就是把各个解决单一任务的第三方依赖组织到一起。看起来这是一个一劳永逸的事情,但是这件事从来没有停止发展过。。。几乎每两个月就会有新的解决方案替代旧的解决方案。 这不,最近facebook看不下去npm了,就自己搞了yarn。但是你确定yarn就足够好嘛。。。明天google会不会出个啥幺蛾子。。。不过版本管理这种东西让facebook这种公司维护,确实要比某某社区要好。 最近在开发一个chrome extension,开发google的东西,还是用angular更合适。由于当时像用material ui,就用了angular material。这真是一个失败的决定。。。这个库跟react下的material-ui简直没法比。从文档组织到具体实现。当我开发一个功能时,需要光标自动聚焦到输入框中,结果内置的directive居然不支持。这个issue被好多人讨论。解决官方宣称现在要开发angular material 2,所以决定“close this issue”。怪angular 2咯?

October 21, 2016 · 1 min · magicalne

cookie和csrf的故事

cookie是一个很古老的东西,但是直到今天我才发现,我还是很不了解cookie啊。 cookie path是干什么的? 首先说明一点,cookie只认识地址,不认识端口号。而path指的是所在服务页面地址。例如,path=/foo,意味着,/foo和/foo下面所有的页面都可以读取cookie,但是path=/bar或者path=/都不能读取path=/foo下的cookie。 注意这里说的是页面地址。现在大都是使用的restful接口,且都是前后端分离的,那么我向后台发送的/api/xxx所返回的set-cookie的内容,默认的path其实是后台的domain,假如后台的domain是api,则返回的path=/api。注意如果在/home页面下的时候发送/api/xxx,其实cookie不会自动带上去的。 spring security中如何实现csrf保护 首先,无论是否使用restful api,你的service都应该是有状态的,也就是基于token(cookie,jsessionId)的。默认的path值,就是service的domain。另外当登录执行成功的时候,response中会增加一个xsrf-token的cookie。spring security会对PATCH, POST, PUT, and/or DELETE这些修改状态的方法要求xsrf验证。验证方法就是在header中,(对,是自己在header中手动加入的)增加:x-xcrf-token:cookie.load(“xcrf-token”)。这个貌似是angularjs默认支持的header名字。

October 20, 2016 · 1 min · magicalne

HTTP 413

记住了。。。tomcat默认的request body大小是2M,nginx默认的request body大小是1M…

October 20, 2016 · 1 min · magicalne

Login Security

最近有机会深入了解一下神秘的登录了:)之前开发的规则引擎ui需要权限验证功能了,我就不得不写了一套权限验证的东西,从前端到后端。其实公司里是有集成的统一登录项目,但是这个项目做的不好,没有考虑好前后端分离场景下的权限验证,导致我走了很多弯路。 先看看一般的登录场景 用户访问某一资源 后台系统发现用户没有登录 重定向到登录页面 用户输入用户名和密码,并提交请求道后台 后台验证用户名密码成功,跳转到登录成功后的页面 很多旧系统,特别是前后端没有分离的系统,基于用户名密码的验证方式,当用户验证成功之后,会将当前session标记为authenticated,这样当用户再访问后续资源时,只要带着cookie中的sessionId就可以了,而不需要后续验证。即,在session生效的时间段内,用户可以访问任何资源而不需要进行权限验证。这里称之为基于状态的验证(cookie)。 另外一种,这里统称为基于token的验证,例如oauth2、basic authentication。每次访问的时候需要在header中加入token进行验证。 以basic authentication为例。很长时间以来,我都认为basic auth是一种十分不可靠的权限验证方式,因为不像oauth2,token是从服务器临时获取而来的,basic auth的token是由用户名和密码经过base64加密而来的,众所周知base64是不安全的。但在我看过这个视频之后,我感觉我了解了真相。。。 简单来说,这个视频是spring security的老大讲解rest api的安全问题。从这个分享,我认为Rob Winch是一个安全专家(至少在我的认知范围内是这样的)。深受启发呀。先抛出一个很有可能被忽视的事实: the browser always sends cookies, and the server always has a session (unless you switch it off). 回头再来看rest api的安全问题。从协议角度来说,rest api是无状态的,也就是说使用rest api访问资源不应该依赖session状态,而是依赖token。http是无状态的,但是在生产环境下使用http访问资源是十分危险的,最简单的例子是,当提交用户名密码时,在http下使用post请求,也是明文传输密码的。所以安全的传输必定是建立在https上的。而TLS/SSL是有状态的 The web server and the client (browser) cache the session including the cryptographic keys to improve performance and do not perform key exchange for every request. 所以https下的rest api其实是stateful!所以正确的保护rest api的方式,是使用token+session的方式。使用token保护rest api所访问的资源看起来十分自然,而使用session的原因在于csrf的保护,只要有浏览器访问rest api,就应该有防范csrf,因为浏览器每次发请求都会带上cookie,而且server总会维护session(除非主动关闭),所以使用基于session的方式放置csrf是很本能的做法。这里不需要再写其他的代码,只需要关注你的业务逻辑即可。...

September 28, 2016 · 1 min · magicalne