xhprof安装记录

选择一个工具分析PHP函数调用的资源耗用明细,以图表化的形式展现,方便优化代码。

安装xhprof

1
$ pecl install xhprof-beta
Read more

Spring data redis的一个bug

前两天上线了一个新功能,导致线上业务的缓存总是无法更新,报错也是非常奇怪,redis.clients.jedis.exceptions.JedisConnectionException: Unknown reply: 5,google到的原因是Spring data redis中的scan操作,它获取了redis的连接之后,在操作没有完全结束之前就把redis连接放回连接池,其它线程从连接池里复用该连接时,会导致数据读取的错误。

bug修复

官方维护人员在 DefaultHashOperations.java 里使用新建redis连接,并等cursor关闭的时候才关闭connection的方式解决该bug。

解决办法

  1. 提升spring data redis版本到1.8,尚未release,可以拉取最新代码,编译成jar包,并配置在local repository
  2. 换个实现方案,尝试用其它数据结构,如hset等

引用

JedisConnection.java

bug地址

微信分享常见问题

微信分享常见问题

调试

前端打开debug模式,浏览器打开时在console里会打印微信分享初始化信息,在微信里会以alert形式出现。

常见问题

  1. 签名的时候注意大小写
  2. 签名时候用的url需要和当前位置的url保持一致
  3. 微信二次分享失败
    微信在第二次分享的链接里增加了后缀,如?from=singlemessage&isappinstalled=1,这里需要转义
1
2
3
4
5
6
7
8
9
10
11
12
13
$.ajax({
url: 'https://domain/wechatshare/sign?url=' + encodeURIComponent(window.location.href.split('#')[0]),
type: 'get',
success: function (data) {
data = JSON.parse(data)
data.jsApiList = [
'onMenuShareTimeline', 'onMenuShareAppMessage','onMenuShareQQ'
];
wx.config(data);
},
error: function (xhr, textStatus) {
}
})

解决Jedis数据读取乱码问题

现象

同一套代码,同一个数据源,不同的操作系统,在OSX上数据提取编码正常,而Ubuntu上拉取数据乱码,数据拉取代码如下。

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
@Override
public List<String> mget(String... fields) {
List<byte[]> arrFields = new ArrayList<>();
for (String field : fields) {
arrFields.add(field.getBytes());
}

List<byte[]> results = redisTemplate.execute(
(RedisCallback<List<byte[]>>) connection -> connection.mGet(arrFields.toArray(new byte[arrFields.size()][]))
);
if (results == null) {
return new ArrayList<>();
}
final List<String> ret = new ArrayList<>();
results.forEach(result -> {
if (result != null) {
try {
ret.add(new String(result));
} catch (UnsupportedEncodingException e) {
ret.add(null);
}
} else {
ret.add(null);
}
});
return ret;
}

检查了Redis存储的数据都是UTF-8之后,怀疑是不同OS的编码有所不同,所以对提取的数据指定了编码格式,即new String(data, "UTF8");,解决了问题。

使用Groovy进行Spring依赖注入

为什么选择Groovy?

传统的依赖注入是XML,对我而言,可读性太差,太不美观,强烈地想换一个方式进行依赖注入,Groovy作为XML的替代方案,在Spring4之后被引入,是基于JVM的一门方言,具有较强的可读性,写更少的更易懂的脚本,完成同样的功能。

Read more

Mahout入门-1

Mahout是什么?

Mahout是用来进行机器学习和推荐算法的一个Java框架。

一个简单的基于用户的协同过滤的示例

读取用户对电影评分的数据集,为ID为2的用户推荐3部电影。

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
34
35
36
37
38
39
40
package com.mahout;

import org.apache.mahout.cf.taste.common.TasteException;
import org.apache.mahout.cf.taste.impl.model.file.FileDataModel;
import org.apache.mahout.cf.taste.impl.neighborhood.ThresholdUserNeighborhood;
import org.apache.mahout.cf.taste.impl.recommender.GenericUserBasedRecommender;
import org.apache.mahout.cf.taste.impl.similarity.PearsonCorrelationSimilarity;
import org.apache.mahout.cf.taste.model.DataModel;
import org.apache.mahout.cf.taste.neighborhood.UserNeighborhood;
import org.apache.mahout.cf.taste.recommender.RecommendedItem;
import org.apache.mahout.cf.taste.recommender.UserBasedRecommender;
import org.apache.mahout.cf.taste.similarity.UserSimilarity;

import java.io.File;
import java.io.IOException;
import java.util.List;

public class Main {

public static void main(String[] args) {
try {
DataModel model = new FileDataModel(new File("resources/dataset.csv"));

UserSimilarity similarity = new PearsonCorrelationSimilarity(model);

UserNeighborhood neighborhood = new ThresholdUserNeighborhood(0.10, similarity, model);

UserBasedRecommender recommender = new GenericUserBasedRecommender(model, neighborhood, similarity);

List<RecommendedItem> recommendations = recommender.recommend(2, 3);
for (RecommendedItem recommendation : recommendations) {
System.out.println(recommendation);
}
} catch (IOException e) {
e.printStackTrace();
} catch (TasteException e) {
e.printStackTrace();
}
}
}

实践后感

好久没有碰Java系列,今天又是看了Mahout的示例,看起来很简单,结果自己试了下,发现要调整IDE,配置Maven,包括数据集文件路径的设置都要注意。

引用

Hive环境搭建(Ubuntu)

Hive是什么?

基于Hadoop的数据查询工具,可以使用类SQL进行数据查询。

Hadoop安装

  • 参考了Hadoop安装
  • 注意到etc/hadoop/hadoop-env.sh里设置JAVA_HOME,不然运行./start_dfs.sh会报没设置JAVA_HOME
  • 运行程序时,输出目录不能存在
  • 配置文件
    • hdfs的输出目录需要保证空间充足,我在虚拟机里玩,分配的磁盘空间太小,另外加了空间并挂载,重新设置了输出目录。

Hive安装

  • 参考了安装hive
  • MetaStore用了MySQL,结果安装的驱动版本不正确,遇到MetaStoreClient lost connection. Attempting to reconnect。

实践后感

真正装环境的坑还是很多的,开始分配的虚拟机磁盘空间太小了,基本运行不了,jdbc驱动版本不正确都会导致Hive客户端开启后时常崩溃,配置文件的一些常用选项需要了解,如果出现问题可以有一个解决思路,当然Google还是常用些,实在解决不了如很难想到是jdbc驱动的原因,我就下了一份源码,定位到报错,通过代码推测了发生问题的地方。

引用

为PHP7添加新的语法特性

好文章!

nikic介绍了如何向PHP添加新的语法特性,原文写的非常精彩,具体是添加in语法功能,使最终实现:

1
2
3
4
5
6
7
8
$words = ['hello', 'world', 'foo', 'bar'];
var_dump('hello' in $words); // true
var_dump('foo' in $words); // true
var_dump('blub' in $words); // false

$string = 'PHP is fun!';
var_dump('PHP' in $string); // true
var_dump('Python' in $string); // false

我进行了一下实践,根据PHP7进行了些修改,具体记录下自己的实践过程和心得。

环境准备

Github上下载PHP7的源码,准备好PHP的开发编译环境,我是基于ubuntu,所以运行如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ apt-get -y install build-essential autoconf bison re2c
下一步,编译PHP7

$ cd php-src
# create new branch for in operator
$ git checkout -b addInOperator
# build ./configure script
$ ./buildconf
# configure PHP in debug mode and with thread safety
$ ./configure --disable-all --enable-debug --enable-maintainer-zts
# compile (4 is the number of cores you have)
$ make -j4

#PHP的二进制就是`sapi/cli/php`,可以运行以下命令测试是否编译成功:
$ ./sapi/cli/php -v
$ ./sapi/cli/php -r 'echo "Hallo World!";'

PHP脚本的生命历程

PHP脚本运行经过3个主要阶段:

  1. 词法分析
  2. 语法分析&编译
  3. 执行

词法分析

这个阶段是将源码根据规则分解成称为token更小的单元,为后面的语法分析提供材料。
修改位于Zend/目录的zend_language_scanner.l,这个文件定义了词法记号规则,为了让PHP能识别in作为关键词。

1
2
3
<ST_IN_SCRIPTING>"in" {
RETURN_TOKEN(T_IN);
}

为了让Zend引擎能识别T_IN这个记号,在zend_language_parser.y中加入:

1
%token T_IN "in (T_IN)"

重新生成tokenizer系列源文件

1
2
$ cd ext/tokenizer
$ ./tokenizer_data_gen.sh

语法分析&编译

向语法分析增加in所应用的表达式规则,在zend_language_parser.y加入:

1
2
3
4
expr_without_variable:
...
| expr T_IN expr { $$ = zend_ast_create_binary_op(ZEND_IN, $1, $3); }
...

再设置in的操作符优先级,在语法分析文件中找到以下行,并在行尾加入T_IN

1
%nonassoc '<' T_IS_SMALLER_OR_EQUAL '>' T_IS_GREATER_OR_EQUAL

执行

最后在zend_vm_def.h中添加具体的执行逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ZEND_VM_HANDLER(173, ZEND_IN, CONST|TMPVAR|CV, CONST|TMPVAR|CV)
{
USE_OPLINE
zend_free_op free_op1, free_op2;
zval *op1, *op2;

SAVE_OPLINE();
op1 = GET_OP1_ZVAL_PTR_UNDEF(BP_VAR_R);
op2 = GET_OP2_ZVAL_PTR_UNDEF(BP_VAR_R);

// TODO

FREE_OP1();
FREE_OP2();
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
}

在PHP源码根目录运行:

1
2
$ ./sapi/cli/php ./Zend/zend_vm_gen.php
$ make -j4

zend_vm_gen.php是根据zend_vm_def.h中的定义生成zend_vm_*.*系列的文件。

之后测试编译后的PHP

1
$ ./sapi/cli/php -r 'var_dump("s" in "str");'

会出现Segmentation fault,原因是我之前在zend_language_parser.y中加入的操作是zend_ast_create_binary_op,所以在编译时会调用位于zend_opcode.cget_binary_op方法,这个方法返回一个函数指针,用于处理expr in expr这个操作两个参数的语句。

1
2
binary_op_type op = get_binary_op(ast->attr);
ret = op(result, &op1, &op2);

所以之后我在3处添加代码:

zend_opcode.c中添加查询ZEND_IN操作函数的代码

1
2
3
4
5
6
7
8
ZEND_API binary_op_type get_binary_op(int opcode)
{
switch (opcode) {
...
case ZEND_IN:
return (binary_op_type) in_function;
...
}

zend_operators.c中添加处理函数

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
34
35
36
37
38
39
40
41
42
43
44
ZEND_API int ZEND_FASTCALL in_function(zval *result, zval *op1, zval *op2)
{
zval op1_copy, op2_copy;
int use_copy1 = 0, use_copy2 = 0;

switch (Z_TYPE_P(op2)) {
case IS_STRING:
use_copy1 = zend_make_printable_zval(op1, &op1_copy);
if (use_copy1) {
op1 = &op1_copy;
}

if (Z_STRLEN_P(op1) == 0) {
ZVAL_TRUE(result);
} else {
const char *found = zend_memnstr_ex(
Z_STRVAL_P(op2),
Z_STRVAL_P(op1),
Z_STRLEN_P(op1),
Z_STRVAL_P(op2) + Z_STRLEN_P(op2)
);

ZVAL_BOOL(result, found != NULL);
}
break;
case IS_ARRAY:
use_copy1 = zend_make_printable_zval(op1, &op1_copy);
if (use_copy1) {
op1 = &op1_copy;
}

ZVAL_BOOL(result, zend_hash_exists(Z_ARR_P(op2), Z_STR_P(op1)));
break;
default:
zend_error(E_EXCEPTION | E_ERROR, "Unsupported operand types");
ZVAL_FALSE(result);
return FAILURE;
}

if (use_copy1) {
zval_dtor(&op1_copy);
}
return SUCCESS;
}

zend_vm_def.h中补上TODO该做的处理逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 我在系统里的最新操作号是173,根据实际为准
ZEND_VM_HANDLER(173, ZEND_IN, CONST|TMPVAR|CV, CONST|TMPVAR|CV)
{
USE_OPLINE
zend_free_op free_op1, free_op2;
zval *op1, *op2;

SAVE_OPLINE();
op1 = GET_OP1_ZVAL_PTR_UNDEF(BP_VAR_R);
op2 = GET_OP2_ZVAL_PTR_UNDEF(BP_VAR_R);

in_function(EX_VAR(opline->result.var), op1, op2);
ZVAL_TRUE(EX_VAR(opline->result.var));

FREE_OP1();
FREE_OP2();
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
}

依旧是在PHP源码根目录下运行:

1
2
$ ./sapi/cli/php ./Zend/zend_vm_gen.php
$ make -j4

现在再测试生成的php,应该会满足:

1
2
3
4
5
6
7
8
9
10
$ ./sapi/cli/php -r 'var_dump("s" in "str");'
bool(true)
$ ./sapi/cli/php -r 'var_dump("s" in "xxx");'
bool(false)
$ ./sapi/cli/php -r 'var_dump("a" in ["a"=>1]);'
bool(true)
$ ./sapi/cli/php -r 'var_dump("a" in ["b"=>1]);'
bool(false)
$ ./sapi/cli/php -r 'var_dump("s" in 123);'
Fatal error: Unsupported operand types in Command line code on line 1

小结

实际进行了一番操作之后,对于PHP的运行机制会落实到更小的单位,从解释阶段到文件级别。
PHP7版本引入了ast这个中间结构,比起nikic那个版本来说,需要修改更多文件,下一步要剖析一下astPHP7中的作用和在执行过程中的角色。

PHP7特性概览(一)

了解PHP7的一些特性,搭建PHP7源码编译环境,并运行官网这些新特性的代码。


在64位平台支持64位integer

在64位平台支持64位integer,长度为2^64-1字符串。

更详细查看

抽象语法树

抽象语法树是语法分析之后产物,忽略了语法细节,是解释前端和后端的中间媒介。新增抽象语法树,解耦语法分析和编译,简化开发维护,并为以后新增一些特性,如添加抽象语法树编译hook,深入到语言级别实现功能。

更详细查看

闭包this绑定

新增Closure::call,优化Closure::bindTo(JavaScript中bind,apply也是一样的应用场景)。

1
2
3
4
5
<?php
class Foo { private $x = 3; }
$foo = new Foo;
$foobar = function () { var_dump($this->x); };
$foobar->call($foo); // prints int(3)

2-3行新建Foo的对象,第4行创建了一个foobar的闭包,第5行调用闭包的call方法,将闭包体中的$this动态绑定到$foo并执行。

同时官网上进行性能测试,Closure::call的性能优于Closure::bindTo

更详细查看Closure::call

简化isset的语法糖

从使用者角度来说,比较贴心的一个语法糖,减少了不必要的重复代码,使用情景如:

1
2
3
4
5
<?php
// PHP 5.5.14
$username = isset($_GET['username']) ? $_GET['username'] : 'nobody';
// PHP 7
$username = $_GET['username'] ?? 'nobody';

在服务器端想获取$_GET中的变量时,若是PHP5语法,需要使用?:操作符,每次要重写一遍$_GET['username'],而在PHP7就可以使用这个贴心的语法糖,省略这个重复的表达式。

更详细查看isset_ternary

yield from

允许Generator方法代理Traversable的对象和数组的操作。这个语法允许把yield语句分解成更小的概念单元,正如利用分解类方法简化面向对象代码。
例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
function g1() {
yield 2;
yield 3;
yield 4;
}

function g2() {
yield 1;
yield from g1();
yield 5;
}

$g = g2();
foreach ($g as $yielded) {
print($yielded);
}
// output:
// 12345

yield from后能跟随GeneratorArray,或Traversable的对象。

更详细查看generator delegation

匿名类

1
2
3
4
5
6
<?php
class Foo {}

$child = new class extends Foo {};

var_dump($child instanceof Foo); // true

更详细查看anonymous class

标量类型声明

1
2
3
4
5
6
7
8
9
10
11
12
<?php
declare(strict_types=1);
function add(int $a, int $b): int {
return $a + $b;
}

var_dump(add(1, 2)); // int(3)
// floats are truncated by default
var_dump(add(1.5, 2.5)); // int(3)

//strings convert if there's a number part
var_dump(add("1", "2")); // int(3)

更详细查看

返回值类型声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
function get_config(): array {
return [1,2,3];
}
var_dump(get_config());

function &get_arr(array &$arr): array {
return $arr;
}
$arr = [1,2,3];
$arr1 = get_arr($arr);
$arr[] = 4;
// $arr1[] = 4;
var_dump($arr1 === $arr);

更详细查看return_types

3路比较

一个语法糖,用来简化比较操作符,常应用于需要使用比较函数的排序,消除用户自己写比较函数可能出现的错误。分为3种情况,大于(1),等于(0),小于(-1)。

%}
1
2
3
4
5
6
7
<?php
function order_func($a, $b) {
return $a <=> $b;
}
echo order_func(2, 2); // 0
echo order_func(3, 2); // 1
echo order_func(1, 2); // -1

导入包的缩写

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
<?php
import shorthand
Current use syntax:

use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\NullOutput;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion as Choice;
use Symfony\Component\Console\Question\ConfirmationQuestion;

// Proposed group use syntax:

use Symfony\Component\Console\{
Helper\Table,
Input\ArrayInput,
Input\InputInterface,
Output\NullOutput,
Output\OutputInterface,
Question\Question,
Question\ChoiceQuestion as Choice,
Question\ConfirmationQuestion,
};

更详细查看

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×