Blog about programming and product design
frankcai4real / blog Goto Github PK
View Code? Open in Web Editor NEWBlog about programming and product design
Blog about programming and product design
我们需要部署一台预发布机器,预发布机器和正式环境机器是在同一个局域网内,因为预发布机器需要和正式环境保持一致,故运维直接把正式机的redis.conf直接拷贝到预发布机器,然后使用systemd进行重启。
systemctl restart redis
该命令执行后,直接影响到正式环境的redis服务,redis被关闭了。
查看systemd具体会执行什么脚本
systemdctl status redis
结果如下:
● redis.service - LSB: start and stop Redis server
Loaded: loaded (/etc/rc.d/init.d/redis; bad; vendor preset: disabled)
Active: active (running) since Tue 2020-05-19 02:12:41 UTC; 1 weeks 0 days ago
Docs: man:systemd-sysv-generator(8)
Process: 28308 ExecStart=/etc/rc.d/init.d/redis start (code=exited, status=0/SUCCESS)
Memory: 4.4M
CGroup: /system.slice/redis.service
└─28318 /usr/bin/redis-server 127.0.0.1:6379
打开/etc/rc.d/init.d/redis脚本可以看到shutdown会执行的脚本:
shut="/usr/libexec/redis-shutdown"
打开/usr/libexec/redis-shutdown可以看到:
#!/bin/bash
#
# Wrapper to close properly redis and sentinel
test x"$REDIS_DEBUG" != x && set -x
REDIS_CLI=/usr/bin/redis-cli
# Retrieve service name
SERVICE_NAME="$1"
if [ -z "$SERVICE_NAME" ]; then
SERVICE_NAME=redis
fi
# Get the proper config file based on service name
CONFIG_FILE="/etc/$SERVICE_NAME.conf"
# Use awk to retrieve host, port from config file
HOST=`awk '/^[[:blank:]]*bind/ { print $2 }' $CONFIG_FILE | tail -n1`
PORT=`awk '/^[[:blank:]]*port/ { print $2 }' $CONFIG_FILE | tail -n1`
PASS=`awk '/^[[:blank:]]*requirepass/ { print $2 }' $CONFIG_FILE | tail -n1`
SOCK=`awk '/^[[:blank:]]*unixsocket\s/ { print $2 }' $CONFIG_FILE | tail -n1`
# Just in case, use default host, port
HOST=${HOST:-127.0.0.1}
if [ "$SERVICE_NAME" = redis ]; then
PORT=${PORT:-6379}
else
PORT=${PORT:-26739}
fi
# Setup additional parameters
# e.g password-protected redis instances
[ -z "$PASS" ] || ADDITIONAL_PARAMS="-a $PASS"
# shutdown the service properly
if [ -e "$SOCK" ] ; then
$REDIS_CLI -s $SOCK $ADDITIONAL_PARAMS shutdown
else
$REDIS_CLI -h $HOST -p $PORT $ADDITIONAL_PARAMS shutdown
fi
脚本会去拿配置文件bind的ip去连接redis服务,因为配置是直接从正式环境拿过来的,第一个ip正是正式环境reids的ip,然后发送shutdown命令,于是正式环境shutdown。
发送指令后,脚本会去检查redis进程是否还存在,如果存在则发送kill命令,因此本地reids也同时关闭。
吐槽点: 为什么redis的shutdown是通过客户端连接向server发送shutdown命令? 理论上我是一个普通用户,我不应该用kill,但是通过客户端连接的方式也不太对,我一个服务,每个连接的客户端都可以发送shutdown命令,那岂不很危险。这些命令应该要允许禁调。
通过改变名字来禁止:
rename-command CONFIG CONFIG_b9fc8327c4dee7
rename-command SHUTDOWN SHUTDOWN_b9fc8327c4dee7
rename-command FLUSHDB "" #禁用此命令
rename-command FLUSHALL "" #禁用此命令
如果需要填写redis.conf中bind的配置第一个请尽量填写127.0.0.1
在上面的脚本中(/usr/libexec/redis-shutdown)我们看到它在发送关闭命令后会去检测进程是否还存在。那么如果我每个服务都要写一套这样的逻辑,不就很麻烦,为什么没人把这种东西抽离出来?有的,就是它,systemd。systemd的介绍可以看如下:
http://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-commands.html
http://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-part-two.html
当使用systemd去操作redis,systemd会去对应的目录寻找redis.service脚本寻找对应需要执行的脚本。
ExecStop=/usr/libexec/redis-shutdown
#!/bin/bash
#
# Wrapper to close properly redis and sentinel
test x"$REDIS_DEBUG" != x && set -x
REDIS_CLI=/usr/bin/redis-cli
# Retrieve service name
SERVICE_NAME="$1"
if [ -z "$SERVICE_NAME" ]; then
SERVICE_NAME=redis
fi
# Get the proper config file based on service name
CONFIG_FILE="/etc/$SERVICE_NAME.conf"
# Use awk to retrieve host, port from config file
HOST=`awk '/^[[:blank:]]*bind/ { print $2 }' $CONFIG_FILE | tail -n1`
PORT=`awk '/^[[:blank:]]*port/ { print $2 }' $CONFIG_FILE | tail -n1`
PASS=`awk '/^[[:blank:]]*requirepass/ { print $2 }' $CONFIG_FILE | tail -n1`
SOCK=`awk '/^[[:blank:]]*unixsocket\s/ { print $2 }' $CONFIG_FILE | tail -n1`
# Just in case, use default host, port
HOST=${HOST:-127.0.0.1}
if [ "$SERVICE_NAME" = redis ]; then
PORT=${PORT:-6379}
else
PORT=${PORT:-26739}
fi
# Setup additional parameters
# e.g password-protected redis instances
[ -z "$PASS" ] || ADDITIONAL_PARAMS="-a $PASS"
# shutdown the service properly
if [ -e "$SOCK" ] ; then
$REDIS_CLI -s $SOCK $ADDITIONAL_PARAMS shutdown
else
$REDIS_CLI -h $HOST -p $PORT $ADDITIONAL_PARAMS shutdown
fi
我们可以看到这个shutdown脚本和上面的(service的方式)差不多,唯一不同的是这里没有kill进程的逻辑,其实是因为systemd帮我们做了这一步了,不需要每个服务都要去写kill进程的逻辑。
我们可以看看systemd对应的源码(文件地址:/src/core/service.c):
static void service_enter_stop(Service *s, ServiceResult f) {
int r;
assert(s);
if (s->result == SERVICE_SUCCESS)
s->result = f;
service_unwatch_control_pid(s);
(void) unit_enqueue_rewatch_pids(UNIT(s));
s->control_command = s->exec_command[SERVICE_EXEC_STOP];//这里会去执行stop命令对应的脚本
if (s->control_command) {
s->control_command_id = SERVICE_EXEC_STOP;
r = service_spawn(s,
s->control_command,
s->timeout_stop_usec,
EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT|EXEC_IS_CONTROL|EXEC_SETENV_RESULT|EXEC_CONTROL_CGROUP,
&s->control_pid);
if (r < 0)//检测服务进程是否还存在,存在的话则关闭失败则goto fail
goto fail;
service_set_state(s, SERVICE_STOP);
} else
service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_SUCCESS);
return;
fail://通过脚本关闭服务失败,则通过发kill信号到进程
log_unit_warning_errno(UNIT(s), r, "Failed to run 'stop' task: %m");
service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_RESOURCES);
}
1981年Bruce Jay Nelson发表论文《Implementing Remote Procedure Calls》,是rpc概念的发明者。
http://web.eecs.umich.edu/~mosharaf/Readings/RPC.pdf
发明rpc的目的
RPC(Remote Procedure Call)远程过程调用。在分布式环境中,程序员无需了解远程交互的细节,不需要关心调用的是本地程序还是远程程序。
RPC是IPC的一种形式,IPC可能在一台或多台计算机上运行。
//sample: 13年写的模块之间的远程调用(“rpc”雏形)
private function invoke($invokeParam)
{
$invokeInfo = serialize($invokeParam);
$url = $this->url;
$urlInfo = parse_url($url);
$ssl = array_key_exists('scheme', $urlInfo) ? ($urlInfo['scheme'] == 'https') : false;
$host = $urlInfo['host'];
$port = array_key_exists('port', $urlInfo) ? $urlInfo['port'] : ($ssl ? 443 : 80);
if ($this->proxy) {
$proxyInfo = parse_url($this->proxy);
$host = $proxyInfo['host'];
$port = $proxyInfo['port'];
}
$query = array_key_exists('query', $urlInfo) ? $urlInfo['query'] : false;
$requstHeaders = array(
'Host: ' . $urlInfo['host'],
'Content-Type: text/plain',
'Content-Length: ' . strlen($invokeInfo)
);
if (is_callable('gzdecode')) {
$requstHeaders[] = 'Accept-Encoding: compress, gzip';
}
$requestURI = (array_key_exists('path', $urlInfo) ? $urlInfo['path'] : '/') . ($query ? ('?' . $query) : '');
$socket = fsockopen(($ssl ? 'ssl://' : '') . $host, $port, $errno, $errstr, 10);
if ($errno == 0) {
$request = "POST {$requestURI} HTTP/1.1\r\n";
foreach ($requstHeaders as $header) {
$request .= $header . "\r\n";
}
$request .= "\r\n";
if ($invokeInfo) {
$request .= $invokeInfo;
}
fwrite($socket, $request);
$contentLength = 0;
$response = '';
$chunked = false;
$zip = false;
$line = trim(fgets($socket));
if ($line) {
list($protocol, $responseCode, $responseText) = explode(" ", $line);
while (($line = trim(fgets($socket))) != "") {
if (strstr($line, "Content-Length:")) {
list($cl, $contentLength) = explode(" ", $line);
}
if (strstr($line, "Content-Type:")) {
$responseContentType = $line;
}
if (strstr($line, "Transfer-Encoding")) {
$chunked = strstr($line, "chunked");
}
if (strstr($line, "Content-Encoding")) {
$zip = strstr($line, "gzip");
}
}
if ($contentLength > 0) {
$response = stream_get_contents($socket, intval($contentLength));
} else if ($chunked) {
$length = hexdec(fgets($socket));
while ($length > 0) {
$response .= stream_get_contents($socket, $length);
fgets($socket); // skip the \r\n of data block end
$length = hexdec(fgets($socket));
}
}
fclose($socket);
}
}
if ($response) {
return $zip ? unserialize(gzdecode($response)) : unserialize($response);
} else {
return false;
}
}
star数 | 协议(默认的、主推的) | 序列化方式 | 支持语言 | 作者 | 缺点 | 优点 | |
---|---|---|---|---|---|---|---|
gRPC | 30k | h2、gRPC Web | Protocol Buffer,支持扩展其他序列化方式 | 多语言 | google,17年加入CNCF基金会 | 1.官方没有提供PHP服务端 2. http协议相对更底层的私有协议不够高效 | IDL、社区活跃更受欢迎、文档和example更齐全、生态更好(nginx、thrift等都会对grpc支持) |
Thrift | 8.2k | 私有协议,Tsocket、TFramedTransport、TFileTransport、TMemory Transport、TZlibTransport | Binary、Compact(使用zigzag、varint压缩编码)、json | 多语言 | fb开发,贡献给ASF | IDL、支持的语言更丰富、时间长更成熟(vip osp在使用) | |
Tars | 9k | 私有协议 | 私有序列化方式https://github.com/TarsCloud/TarsTup | 多语言 | 腾讯,捐赠给Linux Foundation | IDL、带有服务治理 | |
Motan | 6k | 私有协议,Motan协议 | 多语言 | 新浪微博 | |||
Dubbo | 35k | 私有协议,Dubbo协议 | 开始只有java,现已支持 Go, Node.js, Python, PHP, Erlang | 阿里巴巴,捐赠给ASF | |||
hprose | 2k | http,fpm模式、私有协议 | 开始只有PHP,现在支持多语言 |
https://colobu.com/2020/01/21/benchmark-2019-spring-of-popular-rpc-frameworks/
{“name”: "test", "age": 18}, {“name”: "test", "age": 19}
---->
{
string name = 1;
int32 age = 2;
}
假如32位的整型存11(十进制),那么将会是00000000000000000000000000001011,那么前面的0都是浪费。
安装Protocol Buffer,https://grpc.io/docs/protoc-installation/
https://grpc.io/docs/languages/go/quickstart/
https://grpc.io/docs/languages/go/basics/
接手了一个电商的erp系统,业务逻辑复杂,为了提高代码的可读性、可维护性,应用分层很有必要。分层可以让我们专注上层业务,还可以让我们对代码进行分类。
erp WEB部署目录
├─application 应用目录
│ ├─common 公共模块目录
│ │ ├─command 命令行定义目录
│ │ ├─controller 控制器目录
│ │ ├─exception 异常目录
│ │ ├─model mysql目录
│ │ └─ ... 更多类库目录
│ │
│ ├─module_name 模块目录
│ │ ├─common.php 模块函数文件
│ │ ├─controller 控制器目录
│ │ ├─model 模型目录
│ │ ├─view 视图目录
│ │ └─ ... 更多类库目录
│ │
│ ├─command.php 命令行定义文件
│ ├─common.php 公共函数文件
│ └─tags.php 应用行为扩展定义文件
参考阿里的代码分层,具体可以查看《阿里巴巴java开发手册》。
erp WEB部署目录
├─application 应用目录
│ ├─common 公共模块目录
│ │ ├─command 命令行定义目录
│ │ ├─controller 控制器目录
│ │ ├─exception 异常目录
│ │ ├─model DAO层目录
│ │ ├─service Service目录
│ │ ├─manager Manager目录
│ │ └─ ... 更多类库目录
│ │
│ ├─module_name 模块目录
│ │ ├─common.php 模块函数文件
│ │ ├─controller 控制器目录
│ │ ├─model 模型目录
│ │ ├─view 视图目录
│ │ ├─service Service目录
│ │ ├─manager Manager目录
│ │ └─ ... 更多类库目录
│ │
│ ├─command.php 命令行定义文件
│ ├─common.php 公共函数文件
│ └─tags.php 应用行为扩展定义文件
虽然只有四层结构,但是在结构里面可以继续划分。例如:
很多时候我们的类需要一些配置数据,例如:充值渠道对接的类、第三方登陆对接的类需要拿到渠道商提供的验证url、商户ID、加解密所需要密钥等等。重构项目时候发现了很多不一样的写法,这些写法间有什么优缺点呢?
wiki的解释:
硬编码(hard code或hard coding)是指在软件实现上,将输出或输入的相关参数(例如:路径、输出的形式或格式)直接以常量的方式撰写在源代码中,而非在运行期间由外界指定的设置、资源、数据或格式做出适当回应。一般被认定是反模式或不完美的实现,因为软件受到输入数据或输出格式的改变就必须修改源代码,对客户而言,改变源代码之外的小设置也许还比较容易。
但硬编码的状况也并非完全只有缺陷,因某些封装需要或软件本身的保护措施,有时是必要的手段。除此之外,有时候因应某些特殊的需求,制作成简单的应用程序,应用程序可能只会运行一次,或者永远只应付一种需求,利用硬编码来缩短开发的时间也是一种不错的决策。
其实写在类的属性中,从类的角度来看,类已经把这些数据抽离出来类属性了,不算hard code。但是在整个系统的角度来说,这也属于一种hard code,需要更改配置的时候就需要更改源代码。
从以上角度来看,使用配置文件的方式比较优的。
我们会看到每一种方案都有优缺点,PHP是一门比较灵活的语言,因此大家写法会很多。
对于PHP项目开发来说,现在composer已经足够流行了,所以我们都比较了解,但是pear、pecl,你未必知道它是什么。其实他们都是PHP开发演化的产物,优胜劣汰,谁更适合这个环境谁就能生存下来,这是这是自然规律。
PEAR是PHP Extension and Application Repository的缩写,即php扩展和应用仓库。
PEAR将php程序开发过程中常用的功能编写成类库,涵盖了页面呈现、数据库访问、文件操作、数据结构、缓存操作、网络协议、webservice等许多方面,用户可以通过下载这些类库并适当的做一些定制以实现自己需要的功能。避免重复造车轮,PEAR的出现大大提高了php程序的开发效率和开发质量。pear package以phar、tar或zip发布。
后面还有pear2,是一代的pear代码仓库。
地址: https://pear.php.net/index.php
PECL是PHP Extension Community Library的缩写,即PHP扩展库。
PECL可以看作PEAR的一个组成部分,提供了与PEAR类似的功能。不同的是PEAR的所有扩展都是用纯粹的PHP代码编写的,用户在下载到PEAR扩展以后可以直接将扩展的代码包含到自己的PHP文件中使用。而PECL是使用C语言开发的,通常用于补充一些用PHP难以完成的底层功能,往往需要重新编译或者在配置文件中设置后才能在代码中使用。
通俗的表述是,PEAR是PHP的上层扩展,PECL是php的底层扩展。它们都是为特定的应用提供现成的函数或者类。
地址: http://pecl.php.net/
composer是php的包管理工具,优点在于仅需要提供一个composer.json文件,申明需要用到的第三方库,一个简单的命令就能将其依赖全部装好,也方便项目的部署和发布。还提供了自动加载的支持。(PSR-0规范)
地址: https://getcomposer.org/
pear(1999)-> pecl(2004)-> pear2(2009)-> composer(2012)
composer和pear功能是一样的,但是composer更方便好用,pear差不多被淘汰了。如果用扩展的就pecl,上层包的就用composer。
REST是REpresentational State Transfer的首字母缩写。它是分布式超媒体系统的架构风格(an Internet-scale distributed hypermedia system)。
The name "Representational State Transfer" is intended to evoke an image of how a well-designed Web application behaves: a network of web pages(a virtual state-machine), where the user progresses through the application by selecting links(state transitions), resulting in the next page(representing the next state of the application) being transferred to the user and rendered for their use.
The World Wide Web has succeeded in large part because its software architecture has been designed to meet the needs of an Internet-scale distributed hypermedia system. The Web has been iteratively developed over the past ten years through a series of modifications to the standards that define its architecture. In order to identify those aspects of the Web that needed improvement and avoid undesirable modifications, a model for the modern Web architecture was needed to guide its design, definition, and deployment
论文最后,作者这样写到:rest架构试图为现代web架构提供一个指导原则,它试图最小化网络延迟,同时最大化组件的独立性和可伸缩性,以满足分布式超媒体系统对于可伸缩网络的需要。
REST is a coordinated set of architectural constraints that attempts to minimize latency and network communication while at the same time maximizing the independence and scalability of component implementations. This is achieved by placing constraints on connector semantics where other styles have focused on component semantics. REST enables the caching and reuse of interactions, dynamic substitutability of components, and processing of actions by intermediaries, thereby meeting the needs of an Internet-scale distributed hypermedia system.)
在wiki上有一段关于Figurative art(具体艺术)的定义:
Figurative art, sometimes written as figurativism, describes artwork—particularly paintings and sculptures—that is clearly derived from real object sources, and are therefore by definition representational。
具象艺术,有时被写成具象主义,它所描述的艺术品--尤其是绘画和雕塑--是由真实对象派生而来的,他是通过表征定义的。
由此可知,表征指的就是一种描述方式,它准确定义了真实存在的事物,在具象艺术里,我们通过绘画和雕塑来定义真实的物体。这种描述就是一种表征,它可以被用来定义真实存在的事物,是一种“可重复的表象”,因为其被记录了下来。
在计算机领域“Representational”表述或表征的对象是一种资源,这里的资源具体一点可以指图片、视频、数据、库表字段等等。那么这种表述或表征就是定义这些资源的方式,说到这里恍然大悟,具体一点,就是json、xml等这些描述资源的东西。representational是个大概念,它包括一切的这些json、xml的子集。
允许双方独立发展。数据模块和显示模块分开(前后端分离),提高可移植性、提高伸缩性。
客户端负责维护上下文,服务端不负责。请求本身包含处理该请求所需要的状态,并且服务端不存储与会话相关的任何内容。每次请求都是独立的,服务端不会帮助客户端保存上下文(e.g., 用户的会话信息使用jwt会比维护session信息更优, 客户端不能告诉服务端需要下一页而是精确的告诉服务端第几页)。
要求将对请求的响应中的数据隐式或显式标记为可缓存或不可缓存。如果响应是可缓存的,则客户端缓存有权重用该响应数据以用于以后的等效请求。
要求接口的通用性,一旦开发人员熟悉你的某个api,他就应该能够对其他api采用类似的方法。无论设备或应用类型如何,都可以采用统一的方式和给定的服务端进行交互,并且接口满足一下4个约束(以下通过http协议的场景来介绍):
通过约束组件行为来使体系结构由分层组成,这样每个组件都不能“看到”超出与它们交互的直接层。e.g., 客户端访问服务器时候,并不知道它连接的事终端服务器还是沿途的负责转发的服务器,后端服务器可能会根据不同功能划分成多层。
rest允许通过小程序和脚本的形式下载和执行代码来扩展客户端功能。通过减少预先实现所需的功能数量来简化客户端。
我们现在大部分的架构都是负责REST架构的,客户端(IOS、Android、Angular...)与数据业务(Business Logic、Service)分离,这符合client-server。不像旧时代都挤在一块没有分离。
[POST]/users 新增用户
[PUT]/users/id 修改用户
[DELETE]/users/id 删除用户
[GET]/users 查找用户
问:我平时都是get、post走天下的,也用的好好的呀,新增用户为什么不能/addUser,删除用户为什么不能/deleteUser,感觉也很清晰呀?
答:url的定义是:统一资源定位符。也就是说url是用来表示资源在互联网上的位置的,所以url不应该包含动词,只能包含名词。对资源的操作应该体现在http method上面。你可以用deleteUser也可以用removeUser等等,具体含义只有设计api的人才能说清楚了。而使用DELETE这个方法,看到这个api就知道提供的是删除这个资源的方法,这就叫做语义化。
在jane的网站有一张小汽车的图片,地址是http://jane.com/img/car.jpg 现在想设计一个api,对这张图的删除。
[DELETE]http://jane.com/img/car
问:为什么没加资源的后缀.jpg?
答:严格地说,有些网址最后的".html"后缀名是不必要的,因为这个后缀名表示格式,术语"变现层"范畴,而url应该只代表"资源"的位置。它的具体表现形式,应该在http请求头的信息中用Accept和Content-Type字段指定,这两个字段才是对"表现层"的描述。所以这个例子里,我们可以通过在http header里指定content-type为image/jpeg来申明这个资源是一张jpg格式图片。
参考github提供的rest api:https://docs.github.com/cn/rest
rest是一种架构设计风格。restful是rest的形容词(就像helpful、useful),restful api指的是满足rest原则(6个约束)的接口。
HATEOAS是Hypermedia As The Engine Of Application State的缩写,从字面理解是“超媒体即是应用状态引擎”。
rest的设计者Roy Thomas Fielding在博客强调,非HATEOAS的系统不能称为restful(HATEOAS不是选项,如果没有实现则不是在做rest)
通过实现HATEOAS,每个资源能够描述针对自己的操作资源,动态的控制客户端,即便更改了url也不会破坏客户端。
GET /account/12345 HTTP/1.1
Host: somebank.org
Accept: application/xml
...
//将会返回
HTTP/1.1 200 OK
Content-Type: application/xml
Content-Length: ...
<?xml version="1.0"?>
<account>
<account_number>12345</account_number>
<balance currency="usd">100.00</balance>
<link rel="deposit" href="https://somebank.org/account/12345/deposit">
<link rel="withdraw" href="https://somebank.org/account/12345/withdraw">
<link rel="transfer" href="https://somebank.org/account/12345/transfer">
<link rel="close" href="https://somebank.org/account/12345/close">
</account>
//返回的body不仅包含了账号信息、账户编号:12345,账户余额100同时还有四个可执行的链接deposit, withdraw, transfer, close
//一段时间后再次查询用户信息时返回
HTTP/1.1 200 OK
Content-Type: appliction/xml
Content-Length: ...
<?xml versino="1.0"?>
<account>
<account_number>12345</account_number>
<balance currency="usd">-25.00</balance>
<link rel="deposit" href="https://somebank.org/account/12345/deposit">
</account>
//这时用户账户余额产生了赤字,可操作链接只剩下一个,其余三个在赤字情况下无法执行。
让api变得可读性更高,实现客户端和服务端的部分解耦。
对于不使用HATEOAS的REST服务,客户端和服务器的实现之间是紧密耦合的。客户端需要根据服务端提供的相关文档来了解所暴露的资源和对应的操作。当服务器发生变化时,例如修改了资源的uri,客户端也需要进行相应的修改。
而使用HATEOAS的rest服务中,都可以智能地发现可执行的操作,都需要动态的。
在博客的评论中,作者有回应这个问题:主要是当时论文还不是最完善的,描述HATEOAS的不多,但是HATEOAS才是最重要的。
To some extent, people get REST wrong because I failed to include enough detail on media type design within my dissertation. That’s because I ran out of time, not because I thought it was any less important than the other aspects of REST. Likewise, I suspect a lot of people get it wrong because they read only the Wikipedia entry on the subject, which is not based on authoritative sources.
However, I think most people just make the mistake that it should be simple to design simple things. In reality, the effort required to design something is inversely proportional to the simplicity of the result. As architectural styles go, REST is very simple.
REST is software design on the scale of decades: every detail is intended to promote software longevity and independent evolution. Many of the constraints are directly opposed to short-term efficiency. Unfortunately, people are fairly good at short-term design, and usually awful at long-term design. Most don’t think they need to design past the current release. There are more than a few software methodologies that portray any long-term thinking as wrong-headed, ivory tower design (which it can be if it isn’t motivated by real requirements).
And, of course, lately there has been a lot of “me too” activity around REST, as is the nature of any software buzzword.
现实情况是很少见有人使用,正如有个朋友说:HATEOAS很少人用,让他成为一个概念吧。但是我们可以看到github提供的接口有rest风格的,都是有遵循到HATEOAS原则的。很多人忽略了HATEOAS的重要性,但是这却是作者认为最重要的部分。
rest不是一个协议或者规范,只是一种风格的指导**,因此每个人对rest的理解都不尽相同,如何评判自己开发的接口是否restful?2008年,Leonard Richardson在QConTalk中,根据当时实现restful的uri、http、Hypermedia三个约束建立了一个堆叠模型,并将这三个约束的才用程度分成了4个等级,用以评估web服务涉及的好坏与否,而Martin Fowler在2010年进一步诠释,称为Richardson成熟模型。
https://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm
https://www.ics.uci.edu/~fielding/
https://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven
https://www.infoq.cn/article/2011/05/measuring-rest
https://zhuanlan.zhihu.com/p/37980590
http://www.ruanyifeng.com/blog/2014/05/restful_api.html
https://martinfowler.com/articles/richardsonMaturityModel.html
https://www.crummy.com/writing/speaking/2008-QCon/act3.html
一个用户,每次请求厂商发货api都是失败,补单却是成功。
正常请求的使用的方法是:
$params = json_encode($params);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
补单请求使用的方法是:
$client = new Client();
$result = $client->request('POST', $notifyUrl, ['json'=>$parameters])->getBody()->getContents();
正常请求:
这个用户的角色名称带有=号,对url来说是保留字符。
字符串'"r_name": "===NAME==="'会被解析成:
参数名: '"r_name": "=', 参数内容: '==NAME==="'
因此导致服务器接收方解析url有问题。
补单请求:
使用的是第三方包guzzle库,地址:https://github.com/guzzle/guzzle/releases
封装好的类会经过url编码:rawurlencode、urlencode
Content-Type决定了后端(nginx、服务端(php、go等等)、框架)的解析方式
Content-Type | 浏览器策略 | PHP解析body后支持的接收方式 |
---|---|---|
application/x-www-form-urlencoded | 默认模式,在发送到服务器之前,所有字符都会进行编码(空格转换为 "+" 加号,特殊符号转换为 ASCII HEX 值)。 | 三种都支持 |
multipart/form-data | 不对字符编码,在使用包含文件上传控件的表单时,必须使用该值 | $_ POST |
raw(例如: application/json、text/plain等) | 空格转换为"+"加号,但不对特殊字符编码 | php://input、$HTTP_RAW_POST_DATA |
HTTP协议是建立在TCP/IP协议之上的应用层规范,它把HTTP请求分为三个部分:请求行、请求头、消息主体。协议规定POST提交的数据必须放在消息体(entity-body)中,但协议并没有规定数据使用什么编码方式。服务端通常是根据请求头中Content-Type来获知请求中的消息主体是用何种方式编码的,再对消息主体进行解析。
$_ POST是获取表单POSt过来数据(body)的最常用的方法。
只有当请求头中Content-Type为application/x-www-form-urlencoded或multipart/form-data时,PHP才会将POST数据填充到全局变量 $_ POST数组中。
原生POST数据。
比如下面的key-value对:
name: Jonathan Doe
age: 23
formula: a + b == 13%!
会被编码成(也就是$HTTP_RAW_POST_DATA的值):name=Jonathan+Doe&age=23&formula=a+%2B+b+%3D%3D+13%25%21
所以$HTTP_RAW_POST_DATA就是PHP的一个预定义的变量,用来获取原始的POST数据。
之后PHP会解析这些原始的POST数据,并且格式化数组,填充到$_ POST中。
Array
(
[name] => Jonathan Doe
[age] => 23
[formula] => a + b == 13%!
)
注意:
1.PHP7已经取消了$HTTP_RAW_POST_DATA,请使用php://input
2.$HTTP_RAW_POST_DATA需要在php.ini中开启always_populate_raw_post_data = On
3.$HTTP_RAW_POST_DATA不支持enctype="multipart/form-data" 方式传递的数据,这种情况下,我们要用 $ _ POST 获取字段的内容,$_ FILES 来获取上传的文件信息。
php://,是访问各个输入/输出流(I/O streams)
php://input是可以访问请求的原始数据的只读流。POST请求的情况下,最好使用php://input来代替$HTTP_RAW_POST_DATA, 因为它不依赖于特定的php.ini指令。而且这样的情况下$HTTP_RAW_POST_DATA默认没有填充,比激活always_populate_raw_post_data潜在需要更少的内存。
注意:enctype="multipart/form-data"的时候php://input是无效的。
为什么需要原始的POST数据?
因为很多时候接收到的不是网页POST过来的数据,有格式规范,很可能其他方式POST过来的其他格式的数据,可能这些内容无法解析成$_POST数组,这个时候我们就需要对原始的POST数据进行处理。例如:没有经过url编码的数据,可能会有些格式上的问题,会导致解析成数组有问题。例如json、soap、text/xml,PHP不能够识别,就需要直接拿原始数据。
参考文档:
https://www.php.net/manual/zh/wrappers.php.php
https://blog.wpjam.com/m/post-http_raw_post_data-php-input/
https://blog.csdn.net/lamp_yang_3533/article/details/52384199
通常如果一样东西需要编码,说明这东西不适合传输。原因很多种,如size过大、包含隐私数据。对于url来说,之所要url编码是因为url中有些字符会引起歧义。
URL只能使用英文、阿拉伯数字和某些标点符号,不能使用其他文字和符号。这是网络标准(以前,RFC1738, 现在RFC3986)。
这意味着如果url中有汉字,就必须编码后使用。但是麻烦的是,RFC规范没有规定具体的编码方法,而是交给应用程序(浏览器)自己决定。这就导致了url编码成为了一个混乱的领域。
例如:网址路径的编码,用的是utf-8编码、查询字符串的编码用的是操作系统的默认编码等等,不同浏览器用的策略又不一样。
怎么办?
js先对url编码,再向服务器提交,不要给浏览器插手的机会。因为js的输出总是一致的,就保证了服务器得到的数据格式是统一的。
参考文档:
http://www.ruanyifeng.com/blog/2010/02/url_encoding.html
RFC3986文档规定,url中只允许包含英文字母、数字、-_ .~4个特殊字符以及所有保留字符(RFC3986中指定了以下字符为保留字符(英文字符): ! * ' ( ) ; : @ & = + $ , / ? # [ ])。RFC3986文档对url的编码问题做出了详细的建议,指出了哪些字符需要被编码才不会引起url语义的转变,以及对为什么这些字符需要编码做出了相应的解释。
一开始网络标准是RFC1738,现在更新为RFC3986。这只是网络标准,指明哪些是合法哪些是不合法,但是对于不合法的数据RFC没有指明使用什么具体的编码方法(可能utf-8可能其他),这导致url编码成为一个混乱的领域。
网络标准:uri网络标准,规定了什么是规范什么是合法的数据,RFC3986、RFC1738
编码类型:如何对表单进行编码,Content-Type:application/x-www-form-urlencoded、multipart/form-data、text/plain
具体编码方案:utf-8、GB2312等等。会稍有改动,它是十六进制。需要在前面加上%。比如"\ ",它的ascii码是92,92的十六进制是5c,所以"\ "的url编码是%5c。比如"迷"对应的utf-8编码是\xe8\xbf\xb7,那么它的url编码是%E8%BF%B7。如何解码?先去掉%,然后再进行utf-8解码(不一定utf-8,具体什么编码就什么解码)
参考文档:
https://www.php.net/urlencode/
https://www.php.net/manual/zh/function.rawurlencode.php
https://www.cnblogs.com/panchanggui/p/9436348.html
http://www.faqs.org/rfcs/rfc3986.html
http://www.ruanyifeng.com/blog/2010/02/url_encoding.html
按照RFC3986对url进行编码。
返回字符串中除了-_ .之外的所有非字母数字字符都会被替换成百分号(%)后跟两位十六进制数。这是在RFC3986中描述的编码,是为了保护原义字符以免其被解释为特殊的url界定符,同时保护url格式以免被传输媒体(像一些邮件系统)使用字符转换时弄乱。
注意:php5.3.0之前,rawurlencode根据RFC1738来编码波浪线(~),后面不需要了
返回字符串中除了-_ .之外的所有非字母数字字符都将被替换成百分号(%)后跟两位十六进制,空格则编码为加号(+)。由于历史原因,此编码在将空格编码为加号(+),与rawurlencode(RFC3986编码)不同。
和application/x-www-form-urlencoded、text/plain的类型编码方式一样,空格的编码为加号(+)
空格编码差异,分别是+(与post表单保持一样,也是空格编码为+)和%20(遵循RFC,%后面加十六进制数,RFC3986认为+是保留字符)。
所以用rawurlencode更加符合规范,推荐使用rawurlencode。只是一些历史遗留原因,urlencode才会使用。
post请求的时候,application/x-www-form-urlencoded、text/plain的类型编码,也是空格为+的,所以是不是我们需要用urldecode呢?其实一般我们不会接收原始数据,而是接收解析后的数据$_ POST,所以PHP已经处理这些数据(可能用urldecode),我们就不用关心了。
参考文档
https://stackoverflow.com/questions/996139/urlencode-vs-rawurlencode
https://www.jianshu.com/p/99c09270ad52
有时候一些框架会帮助我们做了url编码,或者一些类的封装就已经自动做了url编码,所以这个时候要注意,不然编码多次但是只是解码一次,则会得到不符合预期的结果。
uri是uniform resource identifier,统一资源标识符,用来唯一的标识一个资源。web上可用的每种资源:html文档、图像、视频片段、程序等都由一个通用资源标识符进行定位。
url是uniform resource locator,统一资源定位器。它是一种具体的uri,即url可以用来标识一个资源,而且还指明了如何locate这个资源。
urn是uniform resource name,统一资源命名,是通过名字来标识资源,也是一种具体的uri,比如mailto:[email protected]。
url、urn是uri的子集。uri是以一种抽象的,高层次概念定义统一资源标识,而url和urn则是具体的资源标识的方式。url和urn都是一种uri。
web上地址的基本形式是uri,它代表统一资源标识符。有两种形式:
url是目前uri最普遍形式,无处不在的url。
urn是url的一种更新形式,统一资源名称不依赖于位置。
如何区分uri中是否url,就看除了标识资源可用的位置外,是否描述了访问该资源的主要机制。所有uri中,不管其是否为url,需遵循形式scheme:[//authority][/path][?query][#fragment]
每部分描述如下:
scheme:对于url,是访问资源的协议名称,对与其他uri,是分配标识符的规范的名称
authority:可选的组成用户授权信息部分,主机及端口(可选)
path:用于在scheme和authority内标识资源
query:与路径一起的附加数据用于标识资源。对于url是查询字符串
fragment:资源特定部分的可选标识符
为了方便地标识特定的uri是否是url,我们可以检查它的scheme,每个url都必须从以下scheme开始:ftp,http,https,gopher,mailto,news,nntp,telnet,wais,file,prospero。如果不是以此开头,则不是url。
也就是说,任何东西,只要能够唯一标识出来,都可以说这个标识是uri。如果这个标识是一个可获取到上述对象的路径,那么同时它也可以是一个url。
例子:
ftp://ftp.is.co.za/rfc/rfc1808.txt (also a URL because of theprotocol)
http://www.ietf.org/rfc/rfc2396.txt (also a URL because of the protocol)
ldap://[2001:db8::7]/c=GB?objectClass?one (also a URL because of the protocol)
mailto:[email protected] (also a URL because of the protocol)
news:comp.infosystems.www.servers.unix (also a URL because of the protocol)
tel:+1-816-555-1212
telnet://192.0.2.16:80/ (also a URL because of the protocol)
urn:oasis:names:specification:docbook:dtd:xml:4.1.2
这些全都是URI, 其中有些是URL. 哪些? 就是那些提供了访问机制的.
参考文档:
https://segmentfault.com/a/1190000006081973
https://blog.csdn.net/neweastsun/article/details/81057868
https://zhuanlan.zhihu.com/p/32613313?utm_source=zhihu&utm_medium=referral&utm_campaign=293074141&utm_term=url
https://www.zhihu.com/question/19557151
两个场景不一样。大家都习惯了pc页面的上下滚动,如果出现左右翻页的,用户不习惯觉得体验很差。人们根深蒂固的使用习惯。
要了解控制反转(Inversion of Control),我觉得有必要先了解软件设计的一个重要**:依赖倒置原则(Dependency Inversion Principle)
假设我们设计一辆汽车:先设计轮子,然后根据轮子大小设计底盘,接着根据底盘设计车身,最后根据车身设计好整个汽车。这里就出现了一个"依赖"关系:汽车依赖车身,车身依赖底盘,底盘依赖轮子。
这样的设计看起来没问题,但是可维护性却很低。假设设计完工之后,上司却突然说根据市场需求的变动,要我们把车子的轮子设计都改大一码。这下就麻烦了:因为我们是根据轮子的尺寸设计的底盘,轮子的尺寸一改,底盘的设计就得修改,同样车身是根据底盘设计的,那么车身也要修改,同理汽车也要修改,整个设计几乎都得改。
现在换一种思路。我们先设计汽车的大概样子,然后根据汽车的样子来设计车身,根据车身来设计底盘,最后根据底盘来设计轮子。这时候,依赖关系就倒置过来了:轮子依赖底盘,底盘依赖车身,车身依赖汽车。
这时候,上司再说要改动轮子的设计,我们只需要改动轮子的设计,而不需要动底盘、车身、汽车的设计。
这就是依赖倒置原则---把原来的高层建筑依赖底层建筑"倒置"过来,变成底层建筑依赖高层建筑。高层建筑决定需要什么,底层去实现这样的需求,但是高层并不用管底层是怎么实现的。这样就不会出现前面的"牵一发动全身"的情况。
为了理解这几个概念,我们还是用上面汽车的例子。只不过这次换成代码。我们先定义四个class,车、车身、底盘、轮胎。
http://c.biancheng.net/view/1326.html
https://en.wikipedia.org/wiki/Dependency_inversion_principle
IOC容器给我们提供的最大便利之处就是更容易实现可插拔,可替换的组件。这也是接口驱动开发所带来的优势,根据接口可以提供更多灵活的子类实现,增强代码的健壮性和稳定性。
IOC管理的组件一般是实现了某些接口的类,这些组件又会使用其他的实现某些接口的组件,它们都不需要知道接口的具体实现,因为这个,组件间的替换才会如此容易。容器的任务就是帮我们创建组件的具体事例,并且把管理它们的依赖关系,把需要的具体依赖传给组件。
https://confluence.atlassian.com/pages/viewpage.action?pageId=223219957
这个模式是基于“版本发布”的,目标是一段时间后产出一个新版本。但是,很多网站项目是“持续发布”的,代码一有变动就部署一次。这时,master分支和develop分支的差别不大,没必要维护两个长期分支。
git flow的优点是清晰可控,缺点是相对复杂,需要同时维护两个长期分支。大多数工具都将master当做默认分支,可是开发是在develop分支进行,这导致经常要切换分支,非常烦人。
假如我一个feature提交到develop了,也在release了,但是发现有问题。需要修改。这个时候,另外一个feature提交到develop需要上线了,但是原来的feature还没修改完,这时候就影响上线了,所以需要回滚,这是非常麻烦的。(其实这并不麻烦,可以很多办法,回滚或者cherry-pick都可以,无论什么flow都会有这种问题,只能说应该在提交合并之前就要做好充分测试,减少这样的场景发生)
https://nvie.com/posts/a-successful-git-branching-model/
https://www.ruanyifeng.com/blog/2012/07/git.html
https://jeffkreeftmeijer.com/git-flow/
github flow最大的优点就是简单,对于“持续发布”的产品,是最合适的流程
问题:master分支的更新与产品的发布是一致的,也就是master是最新的分支,默认就是当前的线上代码。可是有些时候并非如此,我们可能不需要马上发布,可能是有版本控制,也可能是我的公司是有发布窗口的。
因此,只有master是不够用的,还需要另建一个production分支跟踪线上版本。
http://scottchacon.com/2011/08/31/github-flow.html
gitlab flow最大原则叫做,上游优先,既只存在一个主分支master,它是所有其他分支的“上游”。只有上游分支采纳的代码变化,才能应用到其他分支
对于持续发布的项目,它建议在master分支之外,再建立不同环境的分支。比如,“开发环境”分支是master,“预发布环境”分支是pre-production,“生产环境”分支是production
开发分支是预发布分支的上游,预发布分支又是生产分支的上游,代码的变化必须由上游向下游发展。比如,生产环境出现了bug,这时就要新建一个功能分支,先把它合并到master,确认没有问题,再cherry-pick到pre-production,这一步也没有问题,才进入production。只有紧急情况,才允许跳过上游,直接合并到下游分支
对于“版本发布”项目,建议的做法是每一个稳定版本,都要从master分支拉出一个分支,比如2-3-stable、2-4-stable
以后只有修补bug,才允许将代码合并到这些分支,并且此时要更新小版本号
每个人都从master分支开始工作,目标也是master分支
在master分支修正错误,其次再到发布分支
http://dockone.io/article/2350
https://about.gitlab.com/topics/version-control/what-are-gitlab-flow-best-practices/
如果只有production(就是我们印象中的master)是稳定的分支,其他都是临时的话,可以保证每次都是干净的,但是缺点是,每次都是临时,不方便我每个人的提交,也不方便我做测试。
没有特别说明是临时还是长期的,但是临时并没有什么好处。所以原则上,master、pre-production、production都应该是永久分支,只有自己的feature分支才是临时分支
官方没有说临时还是永久的问题,但是从图片可以看出,这三个分支都是永久分支,不是临时拉出来的。
永久的比较好,这样不需要每次都要建新分支,增加工作量和沟通成本。他们都是同一个分支出来只是存在不同环境,而且遵循上游优先的原则,理论上会最终一致的。
https://helpdesk.feishu.cn/hc/zh-CN/articles/360045255773
https://www.nngroup.com/articles/f-shaped-pattern-reading-web-content/
https://thenextweb.com/news/how-to-design-websites-that-mirror-how-our-eyes-work
https://youle.zhipin.com/questions/2ec8bdd8d884aaf1tnB43Nu9.html
https://wen.woshipm.com/question/detail/8646es.html
https://www.zhihu.com/question/344660865/answer/815159293
http://www.woshipm.com/pd/430151.html
http://www.woshipm.com/ucd/631142.html
"The scarcest resource is not oil, metals, clean air, capital, labor, or technology. It is our willingness to listen to each other and learn from each other and to seek the truth rather than seek to be right."
— Donella Meadows“最稀缺的资源不是石油、金属、清洁的空气、资本、劳动力或技术。而是我们愿意相互倾听、相互学习,寻求真理而不是追求正确。”
— 多内拉·梅多斯
重构技术的两位最早倡导者是Ward Cunningham和Kent Beck。他们很早就把重构作为软件开发过程的一块基石,并且在自己的开发过程中运用它。
好代码的检验标准就是人们是否能够轻而易举地修改它![Screenshot from 2021-12-01 22-35-32](/home/frank/Pictures/Screenshot from 2021-12-01 22-35-32.png)
所谓重构(refactoring)是这样一个过程:在不改变代码外在行为的前提下,对代码做出修改,以改进程序的内部结构。重构是一种千锤百炼形成的有条不絮的程序整理方法,可以最大限度地减少整理过程中引入错误的概率。本质上说,重构就是在代码写好之后改进它的设计。
名词:对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本
动词:使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构
过去我们都用"重构"来描述各种形式的代码清理,但是上面的定义,只是一种特定的方式。你可以用重写、结构调整(restructing)来泛指对各种形式的代码清理
Martin Fowler:每时每刻都在重构,我编程的每个小时,都会发生重构
Don Roberts的三次法则:第一次做某件事就去做,第二次做类似的事就会反感了,第三次再做就应该重构了。(也就是DRY原则,Don't repeat yourslef)
一开始开发的人就是这样是最好的设计,后面就不合适
https://item.jd.com/12584498.html
https://en.wikipedia.org/wiki/Martin_Fowler_(software_engineer)
前言
曾经遇到两个与mysql字段为null导致的问题,这都是团队现实中遇到的。
1. 一个字段允许null,程序A写进数据库为空字符串,程序B判断字段是否为空是通过is null,所以对“空”有不一样的判断,导致故障。
2. 一个字段允许null,在一次统计该字段某范围的数量时,比预期的少了很多,后面发现是字段为null,范围条件的作用不起效。
null在以上的业务字段其实并没有发挥特殊的作用,完全可以使用其他默认值代替,这样也就可以避免以上的问题出现。
空字符串empty string和null的区别
empty string 就是'',对于字符串类型的默认值我们一般会default ''或者default null。
使用null你可以区分写入的时候是没有写入还是写入了空字符串,如果使用empty string就区分不了了(但是我们一般不用区分。)
为什么这么多人在用NULL?
1. null是创建数据表时默认的,所以不知情或者不够了解或者怕麻烦的程序员不会注意到这一点
2. 有些程序员以为not null需要更多的空间
3. 更加糟糕的是,业务上根本没有null,default也不是null,但是还是允许null。
官方文档说明
NULL columns require additional space in the rowto record whether their values are NULL. For MyISAM tables, each NULL columntakes one bit extra, rounded up to the nearest byte.
《高性能mysql第二版》的观点:
Mysql难以优化引用可空列查询,它会使索引、索引统计和值更加复杂。可空列需要更多的存储空间,还需要mysql内部进行特殊处理。可空列被索引后,每条记录都需要一个额外的字节,还能导致MyISAM中固定大小的索引变成可变大小的索引。
不使用NULL的理由
为了性能
1. 如果字段中有null则索引不生效?
这是谣言。null列是可以用到索引的,不管是单列索引还是联合索引。如果where条件中出现is null、is not null、!=这些条件仍然可以使用索引,本质上都是优化器去计算一下对应的二级索引数量占所有记录数量的比值而已。
2. null值到非null的更新无法做到原地更新,更容易发生索引分裂,从而影响性能。但是注意:把null列改为not null带来的性能提示很小,除非确定它带来了问题,否则不要把它当成优先的优化措施,最重要的是使用的列的类型的是适当性。
节省空间(索引长度)
字段为null值的索引会需要额外的空间来存储,即每行1字节的大小。对于相同数据的表,字段中有null值得表比not null的大。
key_len的计算规则和三个因素有关:数据类型、字符编码、是否为NULL
key_len 62 = 203(utf8 3字节) + 2(存储varchar变长字符长度2字节,定长字段无需额外的字节)
key_len 83 = 204(utf8mb4 4字节) + 1(是否为null的标识)+2(存储varchar变长字符长度2字节,定长字段无需额外的字节)
所以索引字段最好不要用null,会使索引、索引统计和值更加复杂,并且需要额外一个字节的存储空间。
与我们期望不相符
1. 当你用用>、<、!=这些的时候注意字段有没有特殊内容,例如null,因为他是没办法比较,所以大于还是小于,都匹配不上。
2. NOT IN子查询在有null值得情况下返回永远为空结果,查询容易出错。例如,
select name from table1 where name not in(select name from table2 where id != 1)
1. count(), max(), min()是忽略null的,而count()是包含null值得,那么count(col)和count()结果是不一样的。
2. 两个字符串拼接,如果包含null值,则返回结果为null。例如,
select concat(name_not_null, name_null) from table; -- out: null
select concat(name_not_null, name_not_null2) from table; -- out:xxxx
对开发的影响
1. 将来将这个表的数据转为php程序的数据时,整数列有NULL会转为0吗,字符列会转为''吗?或者浮点型会转为0.0之类的吗,所以这个是不确定的。所以建议:在创建表时,每个字段都不要设置NULL。而应该为NOT NULL,然后使用default指定默认值。
2. null值是不相等的,对于业务表述可能会有影响。不能='null' 只能is null,不利于开发的编写。当然该null的时候还是要null,但是很多业务场景其实用不到的,获取其他办法有更好的实现的。可以这样说,所有使用null值的情况,都可以通过一个有意义的值来表示,这样有利于代码的可读性和维护性,并能从约束上增强业务数据的规范性。比如0, ''空字符串等,如果是datetime类型,可以设置为'1970-01-01 00:00:00'这样的特殊值。
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.