fastjson处理Map的 key 为 int 的bug
JSON 中的 MAP 是不支持 int 为 key 的;
Map<Integer, String> map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
System.out.println(JSON.toJSONString(map));
// {1:"one",2:"two"}
标准的json规范中,要求对象的key必须为string
JSON 中的 MAP 是不支持 int 为 key 的;
Map<Integer, String> map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
System.out.println(JSON.toJSONString(map));
// {1:"one",2:"two"}
标准的json规范中,要求对象的key必须为string
其实在实现的 php 函数里面是很容易获取到的,参考 php 的 builtin 函数 func_get_args()
就可以知道了。
void **p;
int arg_count;
int i;
zend_execute_data *ex = EG(current_execute_data);
if (!ex || !ex->function_state.arguments) {
RETURN_FALSE;
}
p = ex->function_state.arguments;
arg_count = (int)(zend_uintptr_t) *p;
for (i = 0; i < arg_count; i++) {
zval *element, *arg;
arg = *((zval **) (p - (arg_count - i)));
php_var_dump(&arg, 1 TSRMLS_CC);
}
但是在 zend_execute_ex 中,是不能使用 function_state.arguments
来获取参数的,需要从 argument_stack
中获取调用函数的实参。
static void (*old_zend_execute_ex) (zend_execute_data *execute_data TSRMLS_DC);
ZEND_API void learn_execute_ex (zend_execute_data *execute_data TSRMLS_DC)
{
php_printf("====== extension debug start ======\n");
php_printf("function name: %s\n", get_active_function_name(TSRMLS_C));
old_zend_execute_ex(execute_data TSRMLS_CC);
int stacked = 0;
void **top;
void **bottom;
zval *arguments;
smart_str buf = {0};
array_init(arguments);
top = zend_vm_stack_top(TSRMLS_C) - 1;
if (top) {
stacked = (int)(zend_uintptr_t) *top; // argc
if (stacked) {
bottom = zend_vm_stack_top(TSRMLS_C);
EG(argument_stack)->top = top + 1;
if (zend_copy_parameters_array(stacked, arguments TSRMLS_CC) == SUCCESS) {
php_json_encode(&buf, arguments, 0 TSRMLS_CC);
}
EG(argument_stack)->top = bottom;
}
}
smart_str_0(&buf);
php_printf("%s\n", buf.c);
smart_str_free(&buf);
zval_dtor(arguments);
php_printf("====== extension debug end ======\n");
}
PHP_MINIT_FUNCTION(learn)
{
old_zend_execute_ex = zend_execute_ex;
zend_execute_ex = learn_execute_ex;
return SUCCESS;
}
PHP_MSHUTDOWN_FUNCTION(learn)
{
zend_execute_ex = old_zend_execute_ex;
return SUCCESS;
}
后来看到,其实不用上面这中方法就可以实现, php 5.5之后要从 prev 里面去取
/**
* php_var_dump defined in this head file.
*/
#include "ext/standard/php_var.h"
zend_execute_data *real_execute_data = execute_data->prev_execute_data;
void **p = real_execute_data->function_state.arguments;
int arg_count = (int) (zend_uintptr_t) * p;
zval *argument_element;
int i;
// zval *obj = real_execute_data->object;
unsigned long start = mach_absolute_time();
for (i = 0; i < arg_count; i++) {
argument_element = *(p - (arg_count - i));
php_var_dump(&argument_element, 1);
}
这是一次线上 bug,后台 sku 修改的时候,使用了大量的数组,造成了 php 端 $stock
变量无法完全解析。
stock[1][price]=1234
stock[1][stock]=99
stock[1][id]=1
// ...
stock[500][price]=1234
stock[500][stock]=99
stock[500][id]=500
由于线上的 php 版本是由低版本升级上去的,所以以前是不会有这样的问题的,PHP 5.3.9为了防止hash冲突就加了那么个参数 max_input_vars
。
曾今天真的以为上面的例子的 input vars 应该是 500 个,看了 PHP 的源码之后才发现应该是 500 * 3;其实 PHP 是按照 &
为 token 来计数的。
// vim main/php_variables.c +438
// 这里的 separator,在 GET POST 中是 &; 在 COOKIE 中是 ;
var = php_strtok_r(res, separator, &strtok_buf);
// :458
while (var) {
if (++count > PG(max_input_vars)) {
// ...
}
}
file_get_contents('php://input')
来获取之后在 json_decode;我觉得这中方法比较可取,因为 restful 设计里面就采用的这种方法。初始化数组
zval *arr;
MAKE_STD_ZVAL(arr);
array_init(arr);
插入和更新
// 索引
add_index_bool(arr, 10, 1);
add_next_index_long(arr, 1000);
// 关联数组
add_assoc_stringl(arr, "name", ZEND_STRL("xiaozi"), 1);
查找和删除
char *name = "name";
int nameLen = strlen(name);
// 查找
zend_hash_exists(Z_ARRVAL_P(arr), name, nameLen + 1);
// 删除
zend_hash_del(Z_ARRVAL_P(arr), name, nameLen + 1);
读取
char *name = "name";
int nameLen = strlen(name);
zval **value;
if (zend_hash_find(Z_ARRVAL_P(attributes), name, nameLen + 1, (void **)&value) == SUCCESS) {
php_var_dump(value, 1 TSRMLS_CC);
}
这次用 php 扩展的方式来实现一下 Laravel5.1 Support 中的 Fluent,实现一下 php 内置接口 ArrayAccess 的方法
method参数的定义
#include "Zend/zend_interfaces.h"
zend_class_entry *fluent_ce;
ZEND_BEGIN_ARG_INFO_EX(arginfo_fluent___construct, 0, 0, 1)
ZEND_ARG_ARRAY_INFO(0, attributes, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_fluent_offsetexists, 0, 0, 1)
ZEND_ARG_INFO(0, offset)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_fluent_offsetget, 0, 0, 1)
ZEND_ARG_INFO(0, offset)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_fluent_offsetset, 0, 0, 2)
ZEND_ARG_INFO(0, offset)
ZEND_ARG_INFO(0, value)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_fluent_offsetunset, 0, 0, 1)
ZEND_ARG_INFO(0, offset)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_fluent___get, 0, 0, 1)
ZEND_ARG_INFO(0, name)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_fluent___set, 0, 0, 2)
ZEND_ARG_INFO(0, name)
ZEND_ARG_INFO(0, value)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_fluent___isset, 0, 0, 1)
ZEND_ARG_INFO(0, name)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_fluent___unset, 0, 0, 1)
ZEND_ARG_INFO(0, name)
ZEND_END_ARG_INFO()
定义类方法
ZEND_METHOD(fluent, __construct) {
zval *attributes, *instance;
MAKE_STD_ZVAL(attributes);
array_init(attributes);
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|a", &attributes) == FAILURE) {
RETURN_NULL();
}
instance = getThis();
zend_update_property(fluent_ce, instance, ZEND_STRL("attributes"), attributes TSRMLS_DC);
}
ZEND_METHOD(fluent, offsetExists) {
zval *value, *instance, *member;
char *offset;
int offsetLen;
int exists;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &offset, &offsetLen) == FAILURE) {
RETURN_NULL();
}
instance = getThis();
MAKE_STD_ZVAL(member);
ZVAL_STRINGL(member, offset, offsetLen, 1);
exists = std_object_handlers.has_property(instance, member, 0, 0 TSRMLS_DC);
zval_ptr_dtor(&member);
RETURN_BOOL(exists);
}
ZEND_METHOD(fluent, offsetGet) {
zval *value, *instance;
char *offset;
int offsetLen;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &offset, &offsetLen) == FAILURE) {
RETURN_NULL();
}
instance = getThis();
php_printf("%s\n", offset);
value = zend_read_property(fluent_ce, instance, offset, offsetLen, 1 TSRMLS_CC);
RETURN_ZVAL(value, 1, 0);
}
ZEND_METHOD(fluent, offsetSet) {
zval *value, *instance;
char *offset;
int offsetLen;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz", &offset, &offsetLen, &value) == FAILURE) {
RETURN_NULL();
}
instance = getThis();
zend_update_property(fluent_ce, instance, offset, offsetLen, value TSRMLS_DC);
}
ZEND_METHOD(fluent, offsetUnset) {
zval *instance, *member;
char *offset;
int offsetLen;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &offset, &offsetLen) == FAILURE) {
RETURN_NULL();
}
instance = getThis();
MAKE_STD_ZVAL(member);
ZVAL_STRINGL(member, offset, offsetLen, 1);
std_object_handlers.unset_property(instance, member, 0 TSRMLS_DC);
zval_ptr_dtor(&member);
}
ZEND_METHOD(fluent, __get) {
char *name;
int nameLen;
zval *attributes, *instance;
zval **value;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &name, &nameLen) == FAILURE) {
RETURN_NULL();
}
instance = getThis();
attributes = zend_read_property(fluent_ce, instance, ZEND_STRL("attributes"), 0 TSRMLS_CC);
if (zend_hash_find(Z_ARRVAL_P(attributes), name, nameLen + 1, (void **)&value) == SUCCESS) {
RETURN_ZVAL(*value, 1, 0);
} else {
RETURN_NULL();
}
}
ZEND_METHOD(fluent, __set) {
char *name;
int nameLen;
zval *value, *attributes, *instance;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz", &name, &nameLen, &value) == FAILURE) {
RETURN_NULL();
}
instance = getThis();
attributes = zend_read_property(fluent_ce, instance, ZEND_STRL("attributes"), 0 TSRMLS_CC);
add_assoc_zval(attributes, name, value);
zval_add_ref(&value);
}
ZEND_METHOD(fluent, __isset) {
char *name;
int nameLen;
zval *attributes, *instance;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &name, &nameLen) == FAILURE) {
RETURN_NULL();
}
instance = getThis();
attributes = zend_read_property(fluent_ce, instance, ZEND_STRL("attributes"), 0 TSRMLS_CC);
if (zend_hash_exists(Z_ARRVAL_P(attributes), name, nameLen + 1) == SUCCESS) {
RETURN_BOOL(1);
} else {
RETURN_BOOL(0);
}
}
ZEND_METHOD(fluent, __unset) {
char *name;
int nameLen;
zval *attributes, *instance;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &name, &nameLen) == FAILURE) {
RETURN_NULL();
}
instance = getThis();
attributes = zend_read_property(fluent_ce, instance, ZEND_STRL("attributes"), 0 TSRMLS_CC);
zend_hash_del(Z_ARRVAL_P(attributes), name, nameLen + 1);
}
static zend_function_entry fluent_methods[] = {
ZEND_ME(fluent, __construct, arginfo_fluent___construct, ZEND_ACC_CTOR | ZEND_ACC_PUBLIC)
ZEND_ME(fluent, offsetExists, arginfo_fluent_offsetexists, ZEND_ACC_PUBLIC)
ZEND_ME(fluent, offsetGet, arginfo_fluent_offsetget, ZEND_ACC_PUBLIC)
ZEND_ME(fluent, offsetSet, arginfo_fluent_offsetset, ZEND_ACC_PUBLIC)
ZEND_ME(fluent, offsetUnset, arginfo_fluent_offsetunset, ZEND_ACC_PUBLIC)
ZEND_ME(fluent, __get, arginfo_fluent___get, ZEND_ACC_PUBLIC)
ZEND_ME(fluent, __set, arginfo_fluent___set, ZEND_ACC_PUBLIC)
ZEND_ME(fluent, __isset, arginfo_fluent___isset, ZEND_ACC_PUBLIC)
ZEND_ME(fluent, __unset, arginfo_fluent___unset, ZEND_ACC_PUBLIC)
{NULL, NULL, NULL}
};
模块初始化的时候注册类
PHP_MINIT_FUNCTION(learn)
{
zval *attributes;
MAKE_STD_ZVAL(attributes);
array_init(attributes);
zend_class_entry ce;
INIT_CLASS_ENTRY(ce, "Fluent", fluent_methods);
fluent_ce = zend_register_internal_class(&ce TSRMLS_CC);
// implements ArrayAccess
zend_class_implements(fluent_ce TSRMLS_DC, 1, zend_ce_arrayaccess);
zend_declare_property_null(fluent_ce, ZEND_STRL("attributes"), ZEND_ACC_PROTECTED TSRMLS_CC);
return SUCCESS;
}
PHP_FUNCTION(learn_call) {
// 实例化类
zval *learn;
MAKE_STD_ZVAL(learn);
object_init_ex(learn, learn_ce);
char *name = "xiaozi";
char *methodName = "__construct";
zval *param, *method, *callResult;
zval **params[1];
MAKE_STD_ZVAL(method);
ZVAL_STRINGL(method, methodName, strlen(methodName), 1);
MAKE_STD_ZVAL(param);
ZVAL_STRINGL(param, name, strlen(name), 1);
params[0] = ¶m;
// 调用类的 __construct 方法
call_user_function_ex(&(learn_ce)->function_table, &learn, method, &callResult, 1, params, 0, NULL TSRMLS_CC);
zval_ptr_dtor(&method);
zval_ptr_dtor(¶m);
zval_ptr_dtor(&learn);
if (callResult) {
zval_ptr_dtor(&callResult);
}
}
zend_class_entry *learn_ce;
ZEND_BEGIN_ARG_INFO_EX(arginfo_learn___construct, 0, 0, 1)
ZEND_ARG_INFO(0, name)
ZEND_END_ARG_INFO()
// Learn::__construct 的实现
ZEND_METHOD(learn, __construct) {
char *name = "tianzi", *methodName;
zval *method, *callResult, *instance;
int nameLen = strlen(name);
// 带有默认值的传入参数
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s", &name, &nameLen) == FAILURE) {
RETURN_NULL();
}
instance = getThis();
// 更新类的属性
zend_update_property_stringl(learn_ce, instance, ZEND_STRL("name"), ZEND_STRL(name) TSRMLS_DC);
}
// Learn::hi 的实现
ZEND_METHOD(learn, hi) {
zval *instance = getThis();
// 读取类的属性
zval *name = zend_read_property(learn_ce, instance, ZEND_STRL("name"), 0 TSRMLS_CC);
char *theName = estrndup(Z_STRVAL_P(name), Z_STRLEN_P(name));
php_printf("hi %s\n", theName);
efree(theName);
}
// 类方法的属性
static zend_function_entry learn_methods[] = {
ZEND_ME(learn, __construct, arginfo_learn___construct, ZEND_ACC_PUBLIC | ZEND_ACC_CTOR)
ZEND_ME(learn, hi, NULL, ZEND_ACC_PUBLIC)
{NULL, NULL, NULL}
};
PHP_MINIT_FUNCTION(learn)
{
zend_class_entry ce;
INIT_CLASS_ENTRY(ce, "Learn", learn_methods);
// 注册类
learn_ce = zend_register_internal_class(&ce TSRMLS_CC);
// 定义类属性
zend_declare_property_null(learn_ce, ZEND_STRL("name"), ZEND_ACC_PUBLIC TSRMLS_CC);
return SUCCESS;
}
PHP_MINIT_FUNCTION(learn)
{
// CONST_CS 区分大小写
// CONST_PERSISTENT 在模块加载的时候都是有效的,否则每次 request 之后就会删除
REGISTER_BOOL_CONSTANT("LEARN", 1, CONST_CS | CONST_PERSISTENT);
return SUCCESS;
}
实现 vuejs 和 codemirror 的相互绑定
Vue.directive("codemirror", {
twoWay: true,
bind: function () {
// 请自行初始化CodeMirror, 可使用 $(this.el).data('codemirror')
this.editor = editor;
// model主动赋值标记
this.silent = false;
this.handler = function () {
if (!this.silent) {
this.set(this.editor.getValue(), true); // 加锁,防止相互赋值
}
}.bind(this);
this.editor.on("change", this.handler);
},
update: function (value, oldValue) {
this.silent = true;
this.editor.setValue(this.vm.$data[this.raw]);
this.silent = false;
}
});
使用:
<!-- code 为 Vue data 中使用的变量名 -->
<textarea id="codepad" v-codemirror="code"></textarea>
在实现 golang 中发布订阅模式的时候,需要按照优先级排序回调函数;golang 中的 map 是无序的,需要手动取出 key,并对key进行排序,下面是排序一块的代码段:
[
1 => [func1, func2]
0 => [func5, func6]
2 => [func3, func4]
]
...
[func5, func6, func1, func2, func3, func4]
func (ed *eventdispatcher) SortListeners(event string) {
ed.sorted[event] = nil
// 发布订阅模式中函数执行的优先级
priorities := make([]int, 0)
for priority, _ := range ed.listeners[event] {
priorities = append(priorities, priority)
}
// 对优先级的数字进行排序
sort.Ints(priorities)
sorted := make([]func(interface {}) interface {}, 0)
// 按照优先级顺序合并 map
for _, priority := range priorities {
sorted = append(sorted, ed.listeners[event][priority]...)
}
ed.sorted[event] = sorted
}
Actions 里面一个 dict 是一个图标,由于 PopClip 不支持直接执行可执行文件,所以要使用 shell 来执行一下。
自己给定的两个图标的颜色是没有关系的,PopClip 会自动修改图标的颜色。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Actions</key>
<array>
<dict>
<key>After</key>
<string>copy-result</string>
<key>Image File</key>
<string>id.png</string>
<key>Regular Expression</key>
<string>(?s)1\w+$</string>
<key>Title</key>
<string>Url2Id</string>
<key>Shell Script File</key>
<string>url2id.sh</string>
</dict>
<dict>
<key>After</key>
<string>copy-result</string>
<key>Image File</key>
<string>url.png</string>
<key>Regular Expression</key>
<string>(?s)\d+$</string>
<key>Title</key>
<string>Id2Url</string>
<key>Shell Script File</key>
<string>id2url.sh</string>
</dict>
</array>
<key>Apps</key>
<array>
<dict>
<key>Link</key>
<string>http://tool.lu/</string>
<key>Name</key>
<string>在线工具</string>
</dict>
</array>
<key>Credits</key>
<array>
<dict>
<key>Link</key>
<string>mailto:245565986@qq.com</string>
<key>Name</key>
<string>xiaozi</string>
</dict>
</array>
<key>Extension Description</key>
<string>Convert ids for mogujie.</string>
<key>Extension Identifier</key>
<string>lu.tool.popclip.extension.id-converter</string>
<key>Extension Image File</key>
<string>id.png</string>
<key>Extension Name</key>
<string>Id Converter</string>
<key>Version</key>
<integer>1</integer>
</dict>
</plist>
PopClip 操作的文本是直接放在环境变量 POPCLIP_TEXT
里面的,所以下面的代码可以当做是 go 的一个插件模板
package main
import (
"fmt"
"os"
)
func main() {
text := os.Getenv("POPCLIP_TEXT")
fmt.Print(text)
}
go build -o IdConverter .
mv IdConverter/ IdConverter.popclipext
zip -r IdConverter.popclipext.zip IdConverter.popclipext
mv IdConverter.popclipext.zip IdConverter.popclipextz
import com.yahoo.platform.yui.compressor.CssCompressor;
import org.apache.commons.io.IOUtils;
import java.io.*;
public class YUIProcessor {
public String compressCss(String code) {
Reader in = new InputStreamReader(IOUtils.toInputStream(code));
try (Writer out = new StringWriter()) {
CssCompressor compressor = new CssCompressor(in);
compressor.compress(out, -1);
out.flush();
return out.toString();
} catch (IOException e) {
e.printStackTrace();
}
return "";
}
}
追加:
yui compresser 很久没有更新,有很多的不足,已经废弃使用。
+-----------+
--> | counter 1 |
+-----------+
--> | counter 2 |
+-----------+ -> sum(subCounter)
--> | counter 3 |
+-----------+
--> | counter 4 |
+-----------+
在内部都有多个计数器,这样在并发更新对象的时候,随机选择一个,就可以将锁冲突的概率降低到 1/n
拿文章的喜欢计数来做例子,当有n个人同时点击喜欢的时候,INNODB会锁住这条记录,这篇文章的喜欢计数器将是串行增长的。
pre_posts
+-------+-----+----------+
| id | ... | favs |
+-------+-----+----------+
| 10000 | ... | 8001 |
+-------+-----+----------+
再看下面的,可以将喜欢数从 posts 表拆分出来,一个文章 id,对应多个子计数器,在更新计数器的时候,可以随机选择一条记录增加,在取文章喜欢数的时候,可以sum一下累加起来;这样就降低了并发时候的冲突。
pre_post_favs
+-----+----------+----------+
| id | post_id | favs |
+-----+----------+----------+
| 1 | 10000 | 2001 |
| 2 | 10000 | 1994 |
| 3 | 10000 | 2010 |
| 4 | 10000 | 1898 |
+-----+----------+----------+
显然上面的数据解决了数据并发写入的问题,但是查询的代价却是提高了,想到的办法就是对计数器进行缓存。
读取的时候被动缓存的话,就会产生计数器的延迟;所以这里可以采用 canal 监听 mysql binlog 主动触发 cache 的更新。
那么问题又来了,既然是存到 redis 中,该用什么类型的;如果使用大量的 string 的话,key 就会占用大量的内存,可以部分改用 hash,但是 redis 的设定中,只有 数量 < hash-max-ziplist-entries
的时候才会采用优化的存储方式;根据多位大牛的研究 1000 是比较合适的。
favs:10
000 -> 8001
001 -> 0
...
999 -> 0
当 数据量 和 qps/tps 达到一定的时候,将某块业务的一系列的表迁移到另外一个库是必然需要的。
+----------------------------------------------+
A | |
+----------------------------------------------+
++
B ||
++
+----------------------------------------------+------------+
A | | |
+----------------------------------------------+------------+
++ +------------+
B || | |
++ +------------+
可以一次dump 10条数据 (拍脑袋),对于更新比较频繁的表可以适当减少,这里会有少量的数据是不一致的 (表的并发更新之类,dump 不能保证原子性),不用担心,我们可以在后面校验数据的时候直接修复掉
对 id取模,多进程dump 加快速度
+----------------------------------------------+------------+
A | | |
+----------------------------------------------+------------+
+-------------+ +------------+
B | | | |
+-------------+ +------------+
这块要一条一条数据的进行对比 (扫表)
在业务代码里面,将 db 的连接改到新库 B 上
到这一步,代码里面的双写就可以直接去掉了;完美迁库。(旧库中的表不急忙删除,你懂的)