算法学习之SnowFlow雪花算法-PHP实现 作者: lovingyu_er 时间: 2020-06-27 20:32:42 分类: 数据结构和算法之美, 技术品鉴 评论 ##SnowFlake算法 ####SnowFlake算法原理: snowflake是Twitter开源的分布式ID生成算法,结果是一个64位长的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。 #####符号位 最高位是符号位,为保证生成的ID是正数,因而不使用,恒为0 #####时间戳 用来记录时间戳的毫秒数。一般会选择系统的上线时间作为时间戳的相对起点,而不是用JDK默认的时间戳起点(1970-01-01 00:00:00)。41位长度的时间戳可以保证使用69年。对于系统而言,这个是时间长度绝对是够用了 #####数据中心ID,机器ID 数据中心,也就是我们所说的机房ID,机器ID一共10位,用于标示工作的计算机,在这里数据中心ID,机器ID各占5位。实际上,数据中心ID的位数,机器ID位数可根据实际情况进行调整,没有必要一定按照1:1的比例来分配这10位 #####序列号 这12位,在整个64位中,是处于最低位的12位,这个也和系统应用的层次比较接近,用于标示,区分同一个计算机在相同毫秒时间内产生的ID。 SnowFlake雪花算法生成的ID不是随机的,也是和时间的生序排列的;且可以保证在分布式高并发环境下生成的ID不会发生重复。 ####PHP的实现: ``` /** * 使用 snowflake 算法生成递增的分布式唯一ID. * 该算法支持 15 条业务线,4 个数据中心,每个数据中心最多 128 台机器,每台机器每毫秒可生成 4096 个不重复ID. */ class Snowflake { const SEQUENCE_BITS = 12; const MILLISECOND_BITS = 39; const BUSINESS_ID_BITS = 4; const DATA_CENTER_ID_BITS = 2; const MACHINE_ID_BITS = 7; const TWEPOC = 1399943202863; /** * @var int 7bit 毫秒内序列号 */ protected $sequence; /** * @var int 39bit 毫秒数,最后一次生成的时间戳 */ protected $last_timestamp; /** * @var int 4bit 业务线标识 */ protected $business_id; /** * @var int 2bit 机房标识,仅支持4个数据中心 */ protected $data_center_id; /** * @var int 7bit 机器标识, */ protected $machine_id; /** * Snowflake constructor. * @param int $business_id 业务线标识,必须大于0小于等于15 * @param int $data_center_id 数据中心标识,必须大于0小于等于4 * @param int $machine_id 机器标识,必须大于0小于等于128 * @param int $sequence 顺序号,必须大于0小于等于4096 * @throws \Exception */ public function __construct($business_id,$data_center_id,$machine_id,$sequence = 0) { if($business_id <= 0 || $business_id > $this->maxBusinessId()){ throw new \Exception('Business Id can\'t be greater than 15 or less than 0'); } if($data_center_id <=0 || $data_center_id > $this->maxDataCenterId()){ throw new \Exception('DataCenter Id can\'t be greater than 4 or less than 0'); } if($machine_id <= 0 || $machine_id > $this->maxMachineId()){ throw new \Exception('Machine Id can\'t be greater than 128 or less than 0'); } if($sequence <= 0 || $sequence > $this->maxSequence()){ throw new \Exception('Sequence can\'t be greater than 4096 or less than 0'); } $this->business_id = $business_id; $this->data_center_id = $data_center_id; $this->business_id = $business_id; $this->sequence = $sequence; } /** * @return float 获取当前毫秒数 */ public function getTimestamp() { return floor(microtime(true) * 1000); } protected function nextMillisecond($lastTimestamp) { $timestamp = $this->getTimestamp(); while ($timestamp <= $lastTimestamp) { $timestamp = $this->getTimestamp(); } return $timestamp; } private function maxMachineId() { return -1 ^ (-1 << self::MACHINE_ID_BITS); } private function maxDataCenterId() { return -1 ^ (-1 << self::DATA_CENTER_ID_BITS); } private function maxBusinessId() { return -1 ^ (-1 << self::BUSINESS_ID_BITS); } private function maxSequence() { return -1 ^ (-1 << self::SEQUENCE_BITS); } private function mintId64($timestamp,$business_id, $datacenterId, $machine_id, $sequence) { return (string)$timestamp | $business_id | $datacenterId | $machine_id | $sequence; } private function timestampLeftShift() { return self::SEQUENCE_BITS + self::MACHINE_ID_BITS + self::DATA_CENTER_ID_BITS + self::BUSINESS_ID_BITS; } private function businessIdLeftShift() { return self::SEQUENCE_BITS + self::MACHINE_ID_BITS + self::DATA_CENTER_ID_BITS ; } private function dataCenterIdShift() { return self::SEQUENCE_BITS + self::MACHINE_ID_BITS; } private function machineIdShift() { return self::SEQUENCE_BITS; } protected function nextSequence() { return $this->sequence++; } public function getLastTimestamp() { return $this->last_timestamp; } public function getDataCenterId() { return $this->data_center_id; } public function getMachineId() { return $this->machine_id; } public function getBusinessId() { return $this->business_id; } public function getNextId() { $timestamp = $this->getTimestamp(); if ($timestamp < $this->last_timestamp) { throw new InvalidSystemClockException(sprintf("Clock moved backwards. Refusing to generate id for %d milliseconds", ($this->lastTimestamp - $timestamp))); } if ($timestamp == $this->last_timestamp) { $sequence = $this->nextSequence() & $this->maxSequence(); // sequence rollover, wait til next millisecond if ($sequence == 0) { $timestamp = $this->nextMillisecond($this->last_timestamp); } } else { $this->sequence = 0; $sequence = $this->nextSequence(); } $this->last_timestamp = $timestamp; $t = floor($timestamp - self::TWEPOC) << $this->timestampLeftShift(); $b = $this->getBusinessId() << $this->machineIdShift(); $dc = $this->getDataCenterId() << $this->dataCenterIdShift(); $worker = $this->getMachineId() << $this->machineIdShift(); return $this->mintId64($t, $b, $dc, $worker, $sequence); } } ``` 以上代碼摘自```github.com```,只是參考
PHP扩展开发学习笔记之三ext_skel工具的使用 作者: lovingyu_er 时间: 2020-06-26 21:31:33 分类: php7底层设计和源码实现 评论 注意:我使用的是php-7.1.0的版本,其他的版本可能使用方式不一样,请注意参考官方的文档。 PHP的扩展开发,为了方便开发者快速构建一个PHP的扩展,PHP官方提供了构建扩展骨架的工具```ext_skel``` 这个工具是在学习PHP扩展开发的第一个需要了解的工具: 该工具在源码的ext目录下:会有一个ext_skel可执行文件。 命令: ``` ./ext_skel --extname=ext_name ``` 比如: ``` ./ext_skel --extname=myext ``` 会在ext的目录下生成一个```myext```目录,目录的内容和如下:  该命令的别的参数: ``` ./ext_skel --extname=module [--proto=file] [--stubs=file] [--xml[=file]] [--skel=dir] [--full-xml] [--no-help] //常用的一个参数:--extname 扩展的名称 --extname=module module is the name of your extension --proto=file file contains prototypes of functions to create --stubs=file generate only function stubs in file --xml generate xml documentation to be added to phpdoc-svn --skel=dir path to the skeleton directory --full-xml generate xml documentation for a self-contained extension (not yet implemented) --no-help don't try to be nice and create comments in the code and helper functions to test if the module compiled ```
PHP扩展开发学习笔记之php-config 作者: lovingyu_er 时间: 2020-06-26 20:29:00 分类: php7底层设计和源码实现 评论 这个就是PHP源码中的```script/php-config.bin``` 源文件內容如下: ``` #! /bin/sh SED="@SED@" prefix="@prefix@" datarootdir="@datarootdir@" exec_prefix="@exec_prefix@" version="@PHP_VERSION@" vernum="@PHP_VERSION_ID@" include_dir="@includedir@/php" includes="-I$include_dir -I$include_dir/main -I$include_dir/TSRM -I$include_dir/Zend -I$include_dir/ext -I$include_dir/ext/date/lib" ldflags="@PHP_LDFLAGS@" libs="@EXTRA_LIBS@" extension_dir='@EXTENSION_DIR@' man_dir=`eval echo @mandir@` program_prefix="@program_prefix@" program_suffix="@program_suffix@" exe_extension="@EXEEXT@" php_cli_binary=NONE php_cgi_binary=NONE configure_options="@CONFIGURE_OPTIONS@" php_sapis="@PHP_INSTALLED_SAPIS@" # Set php_cli_binary and php_cgi_binary if available for sapi in $php_sapis; do case $sapi in cli) php_cli_binary="@bindir@/${program_prefix}php${program_suffix}${exe_extension}" ;; cgi) php_cgi_binary="@bindir@/${program_prefix}php-cgi${program_suffix}${exe_extension}" ;; esac done # Determine which (if any) php binary is available if test "$php_cli_binary" != "NONE"; then php_binary="$php_cli_binary" else php_binary="$php_cgi_binary" fi # Remove quotes configure_options=`echo $configure_options | $SED -e "s#'##g"` case "$1" in --prefix) echo $prefix;; --includes) echo $includes;; --ldflags) echo $ldflags;; --libs) echo $libs;; --extension-dir) echo $extension_dir;; --include-dir) echo $include_dir;; --php-binary) echo $php_binary;; --php-sapis) echo $php_sapis;; --configure-options) echo $configure_options;; --man-dir) echo $man_dir;; --version) echo $version;; --vernum) echo $vernum;; *) cat << EOF Usage: $0 [OPTION] Options: --prefix [$prefix] --includes [$includes] --ldflags [$ldflags] --libs [$libs] --extension-dir [$extension_dir] --include-dir [$include_dir] --man-dir [$man_dir] --php-binary [$php_binary] --php-sapis [$php_sapis] --configure-options [$configure_options] --version [$version] --vernum [$vernum] EOF exit 1;; esac exit 0 ``` 可以看到這是一个bash脚本,PHP安装好以后,这个脚本会被移动到bin目录下,并且重名为php-config,这个脚本主要是获取PHP的安装的信息,在编译安装的时候会把下面的信息放到php-config脚本中: 1. PHP的安装的路径 2. PHP的版本 3. PHP源码的头文件目录:main,Zend,ext,TSRM中的头文件,编写扩展时会用到这些头文件,这些头文件保存在PHP安装位置/include/php目录下 4. LDFLAGS:外部库路径 5. 依赖的外部库:告诉编译器要链接哪些文件 6. 扩展存放的目录:扩展.so保存位置,安装扩展make install 时将安装到这个目录下面 7. 编译的SAPI: 比如 cli,fpm,cgi等等 8. PHP编译参数: 编译php时执行./configure时代的编译参数 上述这些信息,在PHP编译或者安装的时候,会使用到。执行编译的时候./configue --with-php-config=/path/to/php-config 生成MakeFile时,作为参数传入即可,它的作用就是提供给configure.in获取上面的几个配置生成Makefile.如果没有指定这个参数,将默认的PHP安装路径下搜索,因此,如果想在同一台机器上安装不同的php版本,在编译安装的时候,需要指定对应版本的php-config就可以了。 下面是我安裝後的文件的內容: ``` #! /bin/sh SED="/bin/sed" prefix="/home/darrykinger.com/php-7.1" datarootdir="/home/darrykinger.com/php-7.1/php" exec_prefix="${prefix}" version="7.1.0" vernum="70100" include_dir="${prefix}/include/php" includes="-I$include_dir -I$include_dir/main -I$include_dir/TSRM -I$include_dir/Zend -I$include_dir/ext -I$include_dir/ext/date/lib" ldflags="" libs="-lcrypt -lresolv -lcrypt -lrt -lrt -lm -ldl -lnsl -lxml2 -lxml2 -lxml2 -lcrypt -lxml2 -lxml2 -lxml2 -lcrypt " extension_dir='/home/darrykinger.com/php-7.1/lib/php/extensions/debug-non-zts-20160303' man_dir=`eval echo ${datarootdir}/man` program_prefix="" program_suffix="" exe_extension="" php_cli_binary=NONE php_cgi_binary=NONE configure_options=" '--prefix=/home/darrykinger.com/php-7.1/' '--enable-fpm' '--enable-debug'" php_sapis=" cli fpm phpdbg cgi" # Set php_cli_binary and php_cgi_binary if available for sapi in $php_sapis; do case $sapi in cli) php_cli_binary="${exec_prefix}/bin/${program_prefix}php${program_suffix}${exe_extension}" ;; cgi) php_cgi_binary="${exec_prefix}/bin/${program_prefix}php-cgi${program_suffix}${exe_extension}" ;; esac done # Determine which (if any) php binary is available if test "$php_cli_binary" != "NONE"; then php_binary="$php_cli_binary" else php_binary="$php_cgi_binary" fi # Remove quotes configure_options=`echo $configure_options | $SED -e "s#'##g"` case "$1" in --prefix) echo $prefix;; --includes) echo $includes;; --ldflags) echo $ldflags;; --libs) echo $libs;; --extension-dir) echo $extension_dir;; --include-dir) echo $include_dir;; --php-binary) echo $php_binary;; --php-sapis) echo $php_sapis;; --configure-options) echo $configure_options;; --man-dir) echo $man_dir;; --version) echo $version;; --vernum) echo $vernum;; *) cat << EOF Usage: $0 [OPTION] Options: --prefix [$prefix] --includes [$includes] --ldflags [$ldflags] --libs [$libs] --extension-dir [$extension_dir] --include-dir [$include_dir] --man-dir [$man_dir] --php-binary [$php_binary] --php-sapis [$php_sapis] --configure-options [$configure_options] --version [$version] --vernum [$vernum] EOF exit 1;; esac exit 0 ```
PHP 扩展开发学习笔记之一.扩展的介绍以及扩展的加载过程(php7.1.0源代码) 作者: lovingyu_er 时间: 2020-06-26 19:36:00 分类: 编程语言,PHP,C/C++ 评论 ####扩展介绍 1. 介入PHP的编译,执行阶段:可以介入PHP框架执行的那5个阶段,比如opcache 2. 提供内部函数: 可以定义内部函数扩充PHP的函数功能,比如:array,date等 3. 提供内部类 4. 实现RPC客户端:用来实现与外部服务的交互,比如:Redis,Mysql等。 5. 提升执行性能:PHP是解释型语言,在一些比较消耗CPU的操作使用C语言代替 ####扩展内部的实现 PHP的扩展通过```zend_module_entry```这个结构表示,这个结构用来保存扩展的基本信息,包括了扩展名字,扩展版本,扩展提供的函数列表,以及PHP四个执行阶段的hook函数等等,每个扩展都需要定义一个此结构的变量,而且这个变量的名称格式必须 ```module_name_module_entry``` ,内核正式通过这个结构来获取到扩展提供的功能的。 PHP中的扩展分为两类:PHP扩展(module),Zend扩展(extension),对于内核而言这两个分别称为模块(module),扩展(extension).这里主要介绍PHP扩展 扩展可以在编译PHP的时候一起编译(又被称为静态编译),也可以单独的编译(使用```phpize```)为共享库,共享库的形式需要将库的名称加入到php.ini配置中去,在```php_module_startup()```阶段PHP会根据php.ini的配置将对应的扩展共享库加载到PHP中去。 源碼文件:```main.c``` ``` int php_module_startup(... .... /* load and startup extensions compiled as shared objects (aka DLLs) as requested by php.ini entries these are loaded after initialization of internal extensions as extensions *might* rely on things from ext/standard which is always an internal extension and to be initialized ahead of all other internals */ php_ini_register_extensions(); zend_startup_modules(); ... ``` php在解析php.ini文件的時候,會把配置中的```extension=xxx.so```或者```zend_extension=xxx.so```解析到全局變量```extension_lists```,這個變量是一個```php_extension_lists```結構中,她的兩個成員類型都為zend_llist,分辨用來保存php.ini中配置的php擴展,zend擴展的so名稱。 ``` typedef struct _php_extension_lists { zend_llist engine; zend_llist functions; } php_extension_lists; ``` 在```php_ini_register_extensions()```中,將一次遍歷```php_extension_list.engine```,```php_extension_functions```,然後分別調用```php_load_zend_extension_cb()```和```php_load_php_extension_cb()```完成對Zend擴展加載和PHP擴展的加載。 願文件```main/php_ini.c```的代碼片段如下: ``` void php_ini_register_extensions(void) { //加載Zend擴展 zend_llist_apply(&extension_lists.engine, php_load_zend_extension_cb); //加載PHP擴展 zend_llist_apply(&extension_lists.functions, php_load_php_extension_cb); zend_llist_destroy(&extension_lists.engine); zend_llist_destroy(&extension_lists.functions); } ``` 從```php_ini.c```的函數中看出,PHP擴展通過```php_load_php_extension_cb```進行註冊,輸入的參數是擴展名稱的地址. 該函數的願代碼如下(main/php_ini.c): ``` /* {{{ php_load_php_extension_cb */ static void php_load_php_extension_cb(void *arg) { #ifdef HAVE_LIBDL php_load_extension(*((char **) arg), MODULE_PERSISTENT, 0); #endif } ``` 其中```HAVE_LIBDL```這個宏是根據```dlopen()```函數是否支持設置的,因為擴展的共享庫就是通過這個函數打開加載的。 在```php_load_php_extension_cb()```又調用了函數```php_load_extension()```最終完成了對於擴展的註冊。改函數的定義在```ext/standard/dl.c```中,代碼如下: ``` PHPAPI int php_load_extension(char *filename, int type, int start_now) { void *handle; char *libpath; zend_module_entry *module_entry; zend_module_entry *(*get_module)(void); int error_type; char *extension_dir; if (type == MODULE_PERSISTENT) { extension_dir = INI_STR("extension_dir"); } else { extension_dir = PG(extension_dir); } if (type == MODULE_TEMPORARY) { error_type = E_WARNING; } else { error_type = E_CORE_WARNING; } /* Check if passed filename contains directory separators */ if (strchr(filename, '/') != NULL || strchr(filename, DEFAULT_SLASH) != NULL) { /* Passing modules with full path is not supported for dynamically loaded extensions */ if (type == MODULE_TEMPORARY) { php_error_docref(NULL, E_WARNING, "Temporary module name should contain only filename"); return FAILURE; } libpath = estrdup(filename); } else if (extension_dir && extension_dir[0]) { int extension_dir_len = (int)strlen(extension_dir); if (IS_SLASH(extension_dir[extension_dir_len-1])) { spprintf(&libpath, 0, "%s%s", extension_dir, filename); /* SAFE */ } else { spprintf(&libpath, 0, "%s%c%s", extension_dir, DEFAULT_SLASH, filename); /* SAFE */ } } else { return FAILURE; /* Not full path given or extension_dir is not set */ } /* load dynamic symbol */ // 這裏的DL_LOAD()函數這個是定義的一個宏,在linux下展開就是dlopen()函數 handle = DL_LOAD(libpath); if (!handle) { #if PHP_WIN32 char *err = GET_DL_ERROR(); if (err && (*err != '\0')) { php_error_docref(NULL, error_type, "Unable to load dynamic library '%s' - %s", libpath, err); LocalFree(err); } else { php_error_docref(NULL, error_type, "Unable to load dynamic library '%s' - %s", libpath, "Unknown reason"); } #else php_error_docref(NULL, error_type, "Unable to load dynamic library '%s' - %s", libpath, GET_DL_ERROR()); GET_DL_ERROR(); /* free the buffer storing the error */ #endif efree(libpath); return FAILURE; } efree(libpath); // 調用dlsym獲取get_module的函數指針.這裏的DL_FETCH_SYMBOL 也是Linux定義的一個宏,展開後會是dlsym()函數。 get_module = (zend_module_entry *(*)(void)) DL_FETCH_SYMBOL(handle, "get_module"); /* Some OS prepend _ to symbol names while their dynamic linker * does not do that automatically. Thus we check manually for * _get_module. */ if (!get_module) { get_module = (zend_module_entry *(*)(void)) DL_FETCH_SYMBOL(handle, "_get_module"); } if (!get_module) { if (DL_FETCH_SYMBOL(handle, "zend_extension_entry") || DL_FETCH_SYMBOL(handle, "_zend_extension_entry")) { DL_UNLOAD(handle); php_error_docref(NULL, error_type, "Invalid library (appears to be a Zend Extension, try loading using zend_extension=%s from php.ini)", filename); return FAILURE; } DL_UNLOAD(handle); php_error_docref(NULL, error_type, "Invalid library (maybe not a PHP library) '%s'", filename); return FAILURE; } //調用擴展的get_module()函數 module_entry = get_module(); //檢查擴展使用的zend_API是否與當前PHP的版本保持一致 if (module_entry->zend_api != ZEND_MODULE_API_NO) { php_error_docref(NULL, error_type, "%s: Unable to initialize module\n" "Module compiled with module API=%d\n" "PHP compiled with module API=%d\n" "These options need to match\n", module_entry->name, module_entry->zend_api, ZEND_MODULE_API_NO); DL_UNLOAD(handle); return FAILURE; } if(strcmp(module_entry->build_id, ZEND_MODULE_BUILD_ID)) { php_error_docref(NULL, error_type, "%s: Unable to initialize module\n" "Module compiled with build ID=%s\n" "PHP compiled with build ID=%s\n" "These options need to match\n", module_entry->name, module_entry->build_id, ZEND_MODULE_BUILD_ID); DL_UNLOAD(handle); return FAILURE; } module_entry->type = type; //為擴展編號 module_entry->module_number = zend_next_free_module(); module_entry->handle = handle; //註冊擴展 if ((module_entry = zend_register_module_ex(module_entry)) == NULL) { DL_UNLOAD(handle); return FAILURE; } if ((type == MODULE_TEMPORARY || start_now) && zend_startup_module_ex(module_entry) == FAILURE) { DL_UNLOAD(handle); return FAILURE; } if ((type == MODULE_TEMPORARY || start_now) && module_entry->request_startup_func) { if (module_entry->request_startup_func(type, module_entry->module_number) == FAILURE) { php_error_docref(NULL, error_type, "Unable to initialize module '%s'", module_entry->name); DL_UNLOAD(handle); return FAILURE; } } return SUCCESS; } ``` 大概思路如下: 1. dlopen打開擴展的共享庫文件。 2. dlsym()獲取動態庫中的```get_module()```函數的地址,(改函數的作用就不多表述),會返回一個```zend_module_entry```結構的地址, 3. 檢查擴展版本是否支持,比如PHP7的擴展不能在PHP5.6下是沒有辦法使用的。 4. 註冊擴展,將擴展添加到```module_registry```中,這是一個全局的HashTable,用於全部擴展的zend_module_entry結構. 5. 如果擴展提供了內部函數,就將函數註冊到EG(function_table)符號表中去。 上述的代碼片段中,完成擴展的註冊,在擴展註冊完成以後,PHP將在不同的執行階段,依次調用每個擴展註冊的當前階段的hook函數。
Kubernetes(K8S)學習筆記之一Kubernetes的基本概念以及架构 作者: lovingyu_er 时间: 2020-06-23 00:35:00 分类: 编程语言, 技术品鉴,Ubuntu,Kubernetes-K8S,运维优化,Microservice-微服务 评论 ####Kubernetes 集群 Kubernetes 協調一個高可用計算機集群,每個計算機作為獨立單元互相連結工作。 特點: 1. 將容器化的應用部署到集群,不需要綁定到某個特定的獨立的計算器。 2. 应用需要以将应用与单个主机分离的方式打包:它们需要被容器化。 3. Kubernetes 以更高效的方式跨群集自动分发和调度应用容器。 Kubernetes 是一个开源平台,并且可应用于生产环境。 ####K8S 集群包含的類型: Master ,Nodes ,前者負責調度整個集群。Nodes 負責運行應用。 其結構圖如下:  **Master負責管理整個集群**。Master協調集群中的所有活動,這些活動包括調度應用,維護應用的所需狀態,應用擴容以及推出新的更新。 **Node 是一個虛擬機或者物理機,它在Kubernetes集群中充當工作機器的角色**,可以理解為實際幹活的人。 其整体的架构图如下:  使用Minikube工具创建一个Cluster,你可以在官方的终端命令行有模拟的工具。 参考文档: 1. K8s miniKube工具部署简单的集群:```https://kubernetes.io/zh/docs/tutorials/kubernetes-basics/create-cluster/cluster-interactive/``` 2. K8s 开源仓库地址(golang真香):```https://github.com/kubernetes/kubernetes``` 3. K8s 官方文档:```https://kubernetes.io/zh/docs/home/```