Php fastcgi_split_path_info配置PATH_INFO

fastcgi_split_path_info指令

fastcgi_split_path_info指令是Nginx的一个内置指令,用于将URI分割成两部分,并将其存储到两个变量中。这样,我们就可以在后续的处理中使用这些变量来获取和处理PATH_INFO的值。命令格式如下:

fastcgi_split_path_info 正则表达式;

该参数后面需指定正则表达式,且必须要有两个捕获,第一个捕获将会重新赋值给$fastcgi_script_name,第二个捕获将会重新赋值给$fastcgi_path_info。

注意表达式切割的是URI(不包含参数), 对应的nginx 变量是$uri, 而非$request_uri

对于某些使用path_info 做路由的框架,如thinkphp, laravel等, 如使用nginx 默认是没有设置PATH_INFO, 需要可以通过配置fastcgi_split_path_info来解析, 可以使用try_files 或 rewrite 来做处理。

如Thinkphp 配置示例:

location / {
                try_files $uri $uri/ /index.php$uri?$query_string;
			    index index.php index.html;
       }
location ~ \.php {
                fastcgi_pass 127.0.0.1:9001;
                fastcgi_split_path_info ^(.+.php)(.*)$;  #解析pathinfo  
                fastcgi_param PATH_INFO $fastcgi_path_info; #设计解析好的path info 
                fastcgi_param SCRIPT_FILENAME $document_root/index.php; 
		include fastcgi_params;
}

评审是不是在浪费时间?

项目实施流程中,评审是很重要的一环,并且复杂的流程会有各种评审,比如需求评审,概要设计评审,详细设计评审, 用例评审等。 为什么需要有这么多评审? 不评审会有什么影响? 为什么很多开发人员反感评审,认为很多评审浪费时间? 原因是对评审的目的和收益不理解,或其参与的评审已变质。 评审核心目的形成共识, 好的评审可以大大降低项目延期风险。

项目延期是工程研发过程中经常遇到的问题, 也是过程管理的一大难点,是体现项目经理管理能力的一大指标。 项目延期的原因有很多,根据个人几十个项目经验分析, 大部分原因是评审执行不到位。有的是评审退化为宣贯,有的是评审变成了批斗会, 还有的是根本取消了评审,其后果就是很多时候它就会在延期项目的反思会上会被拿来作为根因(很多时候再下一层的根因是不会去反思的)。个人遇到过的与评审有关延期理由有:

  • 与产品理解不一致,导致开发返工。
  • 产品逻辑不严谨,需要加需求。
  • 测试回归发现,与前产品产生兼容性或逻辑性问题。
  • 第三方原因导致。

以上几种或多或少都是因为没有共识,而一边项目中主要形成共识方式就是评审,因此这类延期大部分是与评审有关的, 有可能是没有评审(一般是小需求),或评审只是走形式退化为宣贯, 或评审组织出问题没有达到应用的目的。 那么什么时候该有评审,又如何组织评审呢。

我们都知道开发过程中很多阶段都是可以有评审的, 严格执行的话会导致评审形式化,评审成本增加,所以项目实际实施的时候确实很多评审会精简掉,到底哪些是需要保留呢?根据个人经验来看,存在以下情况需要保留对应的评审:

  • 需求涉及多人或多方式时,必须有需求评审, 除非连专门测试都不需要的小需求外,因为需求评审是达成需求共识的最佳方式。
  • 技术方案里涉及三方(调用或输出接口),或做了特殊解决方案时,必须要有技术方案评审。核心目的在于确认研发验证对需求理解
  • 新项目需要有独立概要设计评审, 这对共识技术架构方案, 规范的统一有重要作用。
  • 测试用例评审。 如果有测试接入,则用例评审是不可缺少。核心目的达成需求理解共识,确认测试的覆盖度。

个人认为开发过程中的评审不是能省则省的,而是要有尽有。 我们会发现项目里该有的评审都有,但还是经常出现理解偏差; 另外,是否项目里除了Leader 都方案评审。 这其实都评审实时问题, 为了让评审目的能够实现, 有几个方面是容易忽略却对评审目标达成有关键作用的。

  • 大范围的评审,评审资料需先进行组内,或组织相干专家先进行小范围讨论,确认无颠覆性问题。
  • 评审需要安排在整体项目开发计划中。 不是临时找时间,特别找非工作时间来安排, 评审的目的是发现问题达成共识,类似头脑风暴,是需要消耗参与的相干人员大量精力的。一般需要提前安排,且安排到上午,或下午上班1小时后。
  • 评审资料要提前将资料发送到参与评审的相干人,且要求核心相干人员(如明确的关联研发,产品,测试等)充分了解。 可以采用协同编辑的问题收集列表方式,即使没有问题的也需要填写”无相关问题“,被评审人不用评审前去核对问题列表,以免造成干扰。被评审人讲解完,需标记问题列表的问题是否还存在,且记录新产生的问题。需要避免相干人临时进会, 导致对于相干人来讲评审会变成宣贯会。
  • 评审会议不要变为问题解决会。 被评审人需要把握节奏,只做宣贯方案,解决理解偏差导致共识问题,其他问题如逻辑问题,遗漏问题等只做记录,避免直接讨论解决方案或修改方案。后续可小范围讨论解决, 解决方案通知各项目参与人,特别注意不要只解决不通知, 避免解决方案导致关联方出新问题。
  • 参与评审人员应关注实质性问题。 即提问题要核心关注当前评审内容, 不要关注一些非实质问题,如文档格式,措辞等等, 也不要提自己联想到的与当前评审内容无实质关系的问题。
  • 重视评审的组织。 首先相关负责人需要重视起来,注意组织工程,会议时间, 相干人选择, 通知, 场所都要有要求。 另外,除被评审人外,有一个主持人,主要把控会议流程,及时组织偏离目标的讨论。

总的来说,核心的问题还是要重视评审,特别是相关负责人,需要起到带头作用,带领并督促组员形成对评审重视的风气,不要让评审形式化,也不要评审变宣贯。

如何理解设计模式

不论是编码还是架构设计都逃不过设计模式, 因为合理使用一些设计模式可以怎么我们地图的扩展性,可维护性等,另外也简化了设计实现难度,有大量文章或代码可参考。然而很多开发发费了很多时间与经历去学习记忆GoF 23种设计模式, 但很少人在自己的实际项目中去应用这些设计模式,推脱的理由能找到很多,但大部分根本理由多设计模式只是学而没真正理解。 这部分人在学习设计模式的目的是不正确的, 常见有以下几种:

  • 考试或面试需要。 很多技术考试或面试会问到,需要应付考试或面试。
  • 增加知识面。 设计模式当知识点学习。
  • 跟风或被迫学习。 很多公司技术组有专题学习。

因为学习目的本就不是用的, 自然在实际编码过程中要嘛“忘了” 可以用,或“没想到” 能用,相信这种情况很多技术Leader在代码review是司空见惯的。 另外因为学习的目的与设计模式提出的目的不一致, 学习过程也是很痛苦的,需要记忆那么多类结构图。那么我们应该带什么样目的去学习设计模式呢?根据上文也可以猜出,个人认为最好目的是“用”,而要用好的前提是理解,知道什么时候适合用,什么时候不适合, 适合的是哪种或哪几种模式, 是否改变形或是否多模式组合使用等等。哪我们怎么去理解呢? 我觉得有两个方面: 什么是设计模式和设计模式起作用的原理是什么。

什么是设计模式?

都知道设计模式并不是软件开发工程专有的,各行特别是工程领域都有自己的设计模式。具体软件工程领域什么时候应用设计模式,不可知,但20世纪70年代GoF受建筑领域设计模式影响, 通过总结之前软件里的经验,写的一本著名的参考书《设计模式:可复用面向对象软件的基础》,极大的推动了设计模式被大家熟知并应用。GoF将设计模式定义为: 对被用来在特定场景下解决一般设计问题的类和相互对象的描述。我们可以适当泛化,理解为对某一类问题的通用解决方案。 明显,设计模式是来源于实践经验,是对之前最佳实践的提炼总结,是用来解决实际中特定问题的方案,而不是非常抽象的概念,是不能死记硬背,也不需要实际硬背的。如学习GoF 23种设计模式,我们应该深入理解各模式解决能解决问题是什么,了解使用该方案的好处是什么,而不是背诵概念与类图。

设计模式起作用的原理是什么?

我们在实际开发中经常遇到某些问题可以用设计模型,但是有好几种模式都可以,该如何选择;或者看着能用某种设计模式,但用了后又不能完全解决问题。这时候需要我们队设计模式原理有充分的了解,知道模式起作用的原理及代理优点和缺点,能通过灵活组合或变形模式来解决现有问题。 比如简单工厂模式,就是通过将对象创建与核心业务分离方式,降低核心业务的耦合度,是核心业务满足开闭原则,具有更好的扩展性并且实现简单,易理解等都是其优势。但也要认识到对整个系统来说其并未完全解决开闭问题, 扩展仍涉及到原有代码的修改。 如果系统对扩展有更高要求,比如需要指出第三方扩展,则应当适当扩展, 比如引入SPI。 其实,仔细分析下GoF各模式原理及其优劣,会发现没有完美的模式,只有适合的模式, 这些模式只是前人帮我们总结好的问题解决方案之一,不是唯一, 我们要学习不禁是这个答案,更多的是解决问题的思维方式。 相较来说,几大设计原则是更具通用性的, 我们在GoF 里的很多模式上都可以看到设计原则的体现。

总结来说, 个人认为学习设计模式的重点是理解原理, 各模式解决的问题有个印象,在遇到类似问题,能想起来有这样的模式, 能快速查找相关资料,并确定是否直接使用模式或变形使用。 对相关类图更是没有必要死记,现实工作中完全可以现查,并且往往现实业务中不能完全套用,而需在符合设计原则情况下优化调整。

如何理解高内聚、低耦合

首先需要理解高内聚、低耦合各自的意义。注意在不同的领域它们的意义是不同的。本文中主要描述的是它们在软件工程领域中的意义。

耦合:简单来讲可以理解为元素之间的依赖度。 这个依赖度往往决定于元素间进行依赖的方式。 如: 排课服务,排课时需要依赖老师的一些信息(职级,育龄等), 是通过教师服务接口依赖,还是公用库依赖,其耦合度是不一样的。

耦合越强,说明元素间关系越紧密, 元素之间越不能离开其依赖元素而存在或运行, 且替换其依赖越困难。另外耦合也是因存在依赖而存在, 所以软件工程中会说降低耦合,而不是去耦合。这里的元素可以是系统,服务、 组件, 模块,类,甚至是方法。软件工程中不同角色关注的元素不一样。如架构师主要关心系统、组件间耦合度、 初级开发主要关心类、方法的耦合。因此,耦合情况可以是评价软件工程各个角色工作质量的一个重要指标。耦合按从弱到强的顺序可分为以下几种类型

  • 非直接耦合:两模块间没有直接依赖关系,依赖完全是通过主控制模块对依赖模块调用来实现的。面向抽象编程, 能做到具体实现的变更,不影响主模块。
  • 数据耦合:一个模块访问另一模块,彼此间通过简单数据参数来交换输入、输出信息。这里的简单数据参数不同于控制参数、公共数据结构或外部变量。如Java 中方法参数为值时
  • 标记耦合:如一组模块通过参数表传递记录信息,就是标记耦合。这个记录是某一数据结构的子结构,不是简单变量。 如Java 中方法参数为类引用时。 
  • 控制耦合:一个模块通过传递开关、标志、名字等控制信息,明显的控制选择另一模块的功能。常见的有直接使用其他模块标志字段做逻辑控制。
  • 外部耦合:一组模块都访问同一全局简单变量而不是同一全局数据结构,而且不是通过参数传递该全局变量的信息。
  • 公共耦合:一组模块都访问同一个公共数据环境。该公共数据环境可以是全局数据结构、共享的通信区、内存的公共覆盖区等。 公共配置就是常见的公共耦合。 如Nacos 公共配置, 这个配置建立目的就是建设重复配置,简化各项目配置项,但当多个模块或多个服务都使用到该配置时,配置变更就需要关注各使用模块。如默认的数据库连接池配置,默认的注册中心配置等。
  • 内容耦合:一个模块直接修改另一个模块的数据,或直接转入另一个模块。最常见的就数据库表共用,如排课服务直接调用教师服务库中教师信息表来获取教师职级信息。这会导致教师服务变更需要考虑到排课服务,排课服务也可能要因为教师服务变更而变更。

在设计或编码过程,会发现要降低耦合,经常会增加实现的复杂度。如内容耦合,要降低耦合,就需要在模块A中提供符合模块B的业务能力的接口。所以在实际的编码和设计过程中,还是需要更加项目实际情况,选择适当耦合方式。

内聚: 可以简单理解为模块内各元素间是强耦合的, 其对外提供的能力是单一的,即职责单一。

单一,不是说一定是只有一个功能,也可以是一类。如: 一个排课服务,其提供排课申请,排查查询,排课变更等一类功能接口。

与耦合不同,内聚有“模块内”这个前提。内聚程度越高,说明模块内各元素关联度越高,元素间关系越强,说明模块划分的越合理,这里的模块同样可以是系统,服务、 组件, 模块,类,甚至是方法。内聚也有按程度低到高几种分类:

  • 偶然内聚: 指一个模块内的各处理元素之间没有任何联系。
  • 逻辑内聚: 指模块内执行几个逻辑上相似的功能,通过参数确定该模块完成哪一个功能。
  • 时间内聚: 把需要同时执行的动作组合在一起形成的模块为时间内聚模块。
  • 通信内聚: 指模块内所有处理元素都在同一个数据结构上操作(有时称之为信息内聚),或者指各处理使用相同的输入数据或者产生相同的输出数据。
  • 顺序内聚: 指一个模块中各个处理元素都密切相关于同一功能且必须顺序执行,前一功能元素输出就是下一功能元素的输入。
  • 功能内聚: 最强的内聚,指模块内所有元素共同完成一个功能,缺一不可。与其他模块的耦合是最弱的。

从分类可以看出,内聚是评估我们划分服务,模块,类甚至方法是否合理的重要指标。高内聚可以提升模块的鲁棒性,可靠性,可重用性,可读性等优点。

所以我们在说高内聚低耦合时,实际指的是模块间耦合性低, 但模块内的内聚度高。从软件生命周期可知,很多软件的维护期是远远高于其他周期的,而系统的维护的难易程度很大程度决定于系统的可扩展性,可重用性,及可测试性。而符合高内聚低耦合的系统,直观表现上有模块划分合理,模块本地的重用性好, 模块间耦合度低,替换难度低, 易扩展,易测试。

另外,需注意的一点从耦合、内聚定义可知,做好系统的高内聚低耦合,不是仅取决于系统架构师, 而是所有参与系统设计、研发编码的所有人员有关, 只是架构师或系统设计人员的影响会更大点而已。 那如何做好高内聚低耦合呢, 就技术本身没有固定方法,往往需要根据实际需求将高内聚低耦合作为目标,选择或组合适当的设计模式来实现。 对于开发来说,学习设计模式比较重要,但熟悉设计模式六大原则,然后根据设计原则灵活组合设计模式更重要。另外对于实际项目来说,内聚耦合不仅依赖于开发或架构技术水平, 还于项目的周期长短,可投入资源多少,项目性质有关系, 这也是为什么要求设计人员有实际项目经验的原因。

Java 关键字 – 基础数据类型

Java 是面向对象语言, 但有几个特例,就是基础数据类型。

Java定义了八种基本的数据类型,分为四类:

整数类型:

  • byte:占用1个字节(8位),取值范围从-128到127。
  • short:占用2个字节(16位),取值范围从-32,768到32,767。
  • int:占用4个字节(32位),取值范围从-2^31到2^31-1。这是最常用的整数类型。
  • long:占用8个字节(64位),取值范围从-2^63到2^63-1。

浮点数类型:

  • float:占用4个字节(32位),是单精度浮点数类型。
  • double:占用8个字节(64位),是双精度浮点数类型。这是最常用的浮点数类型。

字符类型:

  • char:占用2个字节(16位),用于表示单个Unicode字符。

布尔类型:

  • boolean:理论上占用1个比特,但实际大小取决于虚拟机实现。只有两个取值:true和false。
//下面是一些声明基本类型变量的例子:
byte byteVar = 100;
short shortVar = 1000;
int intVar = 10000;
long longVar = 100000L; // 注意末尾的L,表示这是一个long类型的字面量

float floatVar = 10.0F; // 注意末尾的f,表示这是一个float类型的字面量
double doubleVar = 10.0;

char charVar = 'A';
boolean booleanVar = true;


基本类型有一些特点:

  • 它们的大小是固定的,不会因为不同的机器而改变。
  • 它们不是对象,因此不具备对象的属性和方法。 因此他们间比较时需要直接使用 ==
  • 基本类型都有对应的包装类,包装类和基础类型间可以进行自动装箱和拆箱。例如:int的包装类是Integer,double的包装类是Double等。
Integer a = 1; //自动装箱,将a 转换为 包装类。
Integer b = 2;
int b = a;   //自动拆箱
if(b > a ){//自动拆箱
//...
}

Windows PHP 开发调试环境安装配置

PHP是一种非常容易学习和使用的一门语言,它的语法特点类似于C语言,但又没有C语言复杂的地址操作,而且又加入了面向对象的概念,再加上它具有简洁的语法规则,使得它操作编辑非常简单,实用性很强。但是由于语言发展较快,版本较多,应用过程中,要求开发人员对软件版本、特性等一定熟悉度,否则容易造成冲突,使配置问题难以处理。因此,PHP 开发者常被建议避免单独进行环境搭配,而使用容器(如docker),或封装好的环境安装包(如LAMP)。也因此很多PHP 开发者可能已经参与过多个项目开发,却没有搭建过PHP 环境。

环境搭建不仅是为了进行开发方便,也是帮助开发者特别初学者加深对语言运行原理理解的良好方法。本文将整个搭建过程分为两部分:

PHP Web服务器环境搭建(nginx, php) 和 PHP 开发调试环境搭建(xdebug ,PhpStorm)。

一、PHP Web服务器环境搭建

我们都知道PHP是解释型语言,支持多种运行模式,比较常见的五大运行模式包括cli、cgi 、fast-cgi、isapi、apache模块的DLL。

目前最为流行的nginx+php-fpm 模式,即fast-cgi 模式,下面是其运行原理:

  1. Web服务器启动时,初始化FastCGI 的程序执行环境。例如Nginx 服务器对应的 ngx_http_fastcgi_module 模块加载。
  2. FastCGI进程管理器自身初始化,启动多个CGI解释器进程并等待来自Web服务器的连接。
  3. 当客户端请求到达Web服务器时,Web服务器将该请求采用socket方式转发到 FastCGI 主进程, FastCGI 主进程选择并连接到一个CGI解释器(关联 FastCGI 子进程),然后,Web服务器将CGI环境变量和标准输入发送到 FastCGI 子进程。
  4. FastCGI 子进程完成处理后,将标准输出和错误信息从同一socket连接返回给Web服务器。最后FastCGI 子进程关闭与Web服务器之间的连接。
  5. FastCGI 子进程继续等待并处理下一个来自Web服务器的连接。

因为只是开发环境,为简化安装配置,我们使用CGI模式(PHP 自带php-cgi.exe),其原理相对更简单:

1)  每次当web server收到index.php这种类型的动态请求后,会启动对应的CGI程序(PHP的解析器);

2)PHP解析器会解析php.ini配置文件,初始化运行环境,然后处理请求,处理完成后将数据按照CGI规定的格式返回给web server然后退出进程;

3)最后web server再把结果返回给浏览器;

下面是具体安装步骤:

  1. PHP 官网下载Windows 版本zip 包(留意版本号,是否nts, 及是否X64), 下载后解压即可。本文使用 E:\\php7
  2. nginx 官网下载nginx zip包, 下载后解压即可,如:E:\\nginx
  3. PHP 根目录(E:\\php7)下复制php.ini-development ,粘贴到同目录下,重命名为php.ini
  4. 启动php-cgi.exe, PHP根目录下右键启动 CMD, 执行 php-cgi.exe -b 127.0.0.1:9001 -c E:\\php7\\php.ini
  5. nginx配置目录下(E:\\nginx\\conf)编辑nginx.conf. 在server 节点中添加如下配置, 保存后双击E:\\nginx\\nginx.exe 启动

location ~ \.php$ { 

               root E:/phpwork/blog/public; #php 项目根目录

                fastcgi_index index.php;  #默认的php脚本

                fastcgi_pass 127.0.0.1:9001;  #转发到的php-cgi 服务地址和端口

                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 

               include fastcgi_params;

       }

至此 php 的web 服务环境搭建完成(后续项目步骤中简称为服务端),可以在php 项目根目录创建 index.php, 任意输入点内容,访问 http://localhost/index.php 验证。

二、PHP 开发调试环境搭建

有了web 服务环境,就可以进行代码编写及运行了,但对于开发来说好用的IDE和调试工具无疑是可以大大提高开发效率的。接下来我们将在本地搭建一套开发调试环境,因为PHP本身是不支持单步调试的,需要安装相应的扩展,本文采用Xdebug 扩展(php 扩展,支持按行执行)。首先简单介绍下Xdebug 与PhpStorm调试原理:

1)PhpStorm集成了一个遵循BGDp的Xdebug插件。启动该插件会启动一个监听服务,负责监听远程服务器发过来的debug信息。

2)服务端PHP收到需要进行debug的请求(带debug标识或cookie,如XDEBUG_SESSION_START) 后通知Xdebug扩展

3)Xdebug扩展会向来源配置的客户端(即PhpStorm的Xdebug服务)发送一个debug请求,收到响应后启动调试。

4)PHP开始执行代码,每执行一行都会让Xdebug过滤一下。

5)Xdebug过滤代码时,会暂停代码的执行,然后向客户端发送该行代码的执行情况,等待客户端的决策。

6)客户端收到Xdebug发送过来的执行情况,判定对应行是否存在断点,如存在则在IDE展示相关信息,等待开发者发送操作调试指令(单步,还是执行到下个断点)。

7)开发者发生调试指令后,客户端相应给服务端,重复4-7步。

从以上原理可以知道, PhpStorm 及服务端相互依赖的,且需要在双方做关联配置。其中PhpStorm 需要知道服务端地址与端口, 同时服务端也需要客户端Xdebug服务的地址与端口。

因为上面服务端调试完成,我们已经知道了服务端的地址和端口,我们先配置PhpStorm,启动PhpStorm 新建或载入一个项目,接下来开始配置:

  • File –> settings 设置Xdebug 客户端服务端口, 改端口需要配置到服务端。
  • File –> settings 设置DBGP, IDE key 任意填写,需要配置到服务端。
  • File –> settings 设置servers, 填写服务端地址与端口, debugger 选择 Xdebug
  • run –> debug configurations 设置debug 配置, 添加一个PHP Web page, server 选择3 步配置的server
  •  在服务端Xdebug 扩展安装配置好并在IDE 设置好断点后启动调试, IDE 自动使用第4步配置的浏览器打开Start URL
  • 进行调试。

服务端Xdebug 扩展安装与配置

  1. 下载安装Xdebug 扩展,需要特别注意你服务端使用的php 版本。不请求的可以复制phpinfo() 输出页面源码到https://xdebug.org/wizard 会自动跳转到对应版本下载地址。
  2. 下载的dll 复制到php 的ext 目录下(非必须),如E:\\php7\\ext
  3. 修改php.ini ,增加以下配置.  注意:以下配置是基于3.0.3版本,3.0 版本以前配置稍有不同,参考:https://xdebug.org/docs/upgrade_guide 

;xdebug库文件

zend_extension=php_xdebug-3.0.3-7.3-vc15-nts-x86_64.dll

;开启远程调试xdebug.mode=debug

;客户机ip

xdebug.client_host=”127.0.0.1″

;客户机xdebug监听端口和调试协议

xdebug.client_port=9011

;IDEkey 区分大小写

xdebug.idekey=”PHPSTORM” 

    4. 重启 php-cgi.exe , CMD 命令行运行 php -m  查看扩展是否加载成功,在phpinfo 页面可以查看详细的xdebug 生效配置。

至此服务端配置完成, 可以在IDE 中点击调试进行调试。

总的来说整个安装过程还是比较简单,容易出错的地方不多,主要有:

  • PHP 和扩展版本未对应
  • 配置出错,特别是Xdebug 扩展配置,不过对原理有了解后, 对于各配置项也就更能理解。

安装步骤大家没必要记,因为不同系统或版本可能都有不同, 所以更希望大家通过环境安装能够加深对相关原理理解。

附:

Xdebug linux 环境安装要更简单一点,官方安装文档也比较完善。下面是源码安装的步骤和命令示例

注意检查PHP版本,源码安装只需找到对应版本号就可以,如php 7.1 只能找3.0以下的
wget https://xdebug.org/files/xdebug-2.9.8.tgztar -xzvf xdebug-2.9.8.tgz
cd xdebug-2.9.8.tgz
phpize
./configure
makemake install  #成功会显示安装位置, 如:/opt/bitnami/php/lib/php/extensions/
正常情况会自动配置php.ini (php --ini 查看ini 位置), 可以使用 php -m 查看是否能成功加载。
按上文修改php.ini 配置,填入正确的客服端信息即可

由Xdebug 调试原理可知,如果远程调试,则要求客户端(IDE Xdebug服务)和服务端网络是互通,
不仅客户端能访问服务端, 服务端也要能够访问客户端。

linux anaconda 及jupyter 安装问题记录

1. InvalidVersionSpecError: Invalid version spec: =2.7

anaconda 版本低导致, 下载了官网最新版本后解决。

2. anaconda SSLError(MaxRetryError(‘HTTPSConnectionPool(host=\’mirr

在命令行中输入conda config --set ssl_verify false修改设置,或者在文件~/.condarc末尾添加一行ssl_verify: false(有则修改即可), 或将配置的源https改成http

3. jupyter连接不到内核, 后台400错误

 nginx 转发导致, 增加 web socket 支持
location  /api/kernels/ {
                proxy_pass            http://notebook;
                proxy_set_header      Host $host;                
                proxy_http_version    1.1;  # websocket support
                proxy_set_header      Upgrade "websocket";
                proxy_set_header      Connection "Upgrade";
                proxy_read_timeout    86400;
        }
        location  /terminals/ {
                proxy_pass            http://notebook;
                proxy_set_header      Host $host;                
                proxy_http_version    1.1;  # websocket support
                proxy_set_header      Upgrade "websocket";
                proxy_set_header      Connection "Upgrade";
                proxy_read_timeout    86400;
        }

4. AttributeError: type object IOLoop has no attribute initialized

conda install -c conda-forge jupyter-lsp

5. 404 GET /api/kernels/

conda install -c conda-forge ipywidgets

conda install -c conda-forge jupyter-collaboration

conda install -c anaconda pyzmq

conda update setuptools

Java 关键字 – for, while

for, while都是循环结构关键字,作用为支持重复执行相同代码,掌握了这两个关键字的使用,基本各种数据循环都可以实现。

示例: 按序输出26个字母

for(char c='a'; a<='z'; a++){
     System.out.println(a);
}
char c='a';
while (a<='z'){
   System.out.println(a);
}

for

for 语法结构有两种:条件for 及for-each 结构

//for 结构
for (初始化表达式; 条件表达式; 步进表达式) {
    // 循环体:当条件表达式为true时执行
}。

初始化表达式 一般为循环控制变量的初始化,只在循环开始时执行一次, 可为空。
条件表达式 循环控制表达式,可以为空,如果有则必须为布尔表达式,每次循环前都会执行,且只有表达为true 时,循环才会执行
步进表达式 循环体执行完后执行的表达式, 一般用户循环控制变量的修改。 可为空。

for (int i = 1; i <= 5; i++) {
    System.out.println(i);
}
for (;;) {
    System.out.println("-");
    break;
}

注意: 这个三个表达式可以部分为空,也可以都为空的,都为空时,则循环的结束需在循环体中控制, 如使用break 或return 进行退出循环,等效while(true){//循环}

for-each 结构, 这是Java 5 引入了一种特殊类型的for循环,称为”增强for循环”或”for-each循环”,它简化了对数组或集合的遍历。增强for循环的语法如下:

for (数据类型 变量名 : 数组或实现了 Iterable 接口的任何集合类) {
    // 使用变量名进行操作
}

for-each 循环可以被视为Java语法糖。语法糖(Syntactic Sugar)是指在编程语言中添加的某种语法,这种语法让人们写代码时更加方便,但它并不提供新的功能,而是提供了一种更简洁易读的方法来表达已有的操作。也就是所有for-each 实现的代码都可以转换为for 循环结构实现,只是代码会复杂点。实际底层实现也是这么做的,如果遍历对象是实现了 Iterable 接口的任何集合类, 则使用了 Iterator 进行元素的遍历。如果你使用的是数组,它就会被编译成一个常规的 for 循环,使用索引来访问每个元素。

// 使用 for-each 循环
for (String item : listOfStrings) {
    System.out.println(item);
}
// 等价的使用 Iterator 的代码
for (Iterator<String> i = listOfStrings.iterator(); i.hasNext(); ) {
    String item = i.next();
    System.out.println(item);
}
// 如果是数组,则等价于常规的 for 循环
String[] arrayOfStrings = new String[] {"a", "b", "c"};
for (int i = 0; i < arrayOfStrings.length; i++) {
    System.out.println(arrayOfStrings[i]);
}

while

也有两种语法: while 及 do…while , 不过都是比较简单的。

while (condition) {
    // 循环体
    // 在这里编写需要重复执行的代码
}
do {
    // 循环体
    // 在这里编写需要重复执行的代码
}while(condition);

condition 是一个布尔表达式。在每次循环开始之前,都会评估这个条件。如果条件为 true,循环体内的代码块将被执行。完成循环体内的代码块后,条件将再次被评估,如果仍为 true,循环将继续执行。这个过程会一直重复,直到条件表达式的结果为 false

如果条件从一开始就是 false,那么循环体内的代码块一次也不会执行。

continue, break

这几个关键字出现在循环体,会影响循环体的执行。

continue (label): 执行到continue, 则结束本次循环,直接开始下次(label)循环。 label 为空则退出当前最近的循环。

break (label): 执行到break, 则结束退出(label)指定的循环, label 为空则退出当前最近的循环。

label 是可选的,通常循环嵌套时,用于退出多个循环时。

     first: for(int i =0; i< 10; i++){
          for (int j = 0; j < 10; j++){
              if(j== 5){
                  continue first;
              }
              if(j == 9){
                  break;
              }
              System.out.println("i="+i+",j="+j);
          }
      }

基于SpringBoot Actuator存活就绪检查

什么是存活就绪检查?

存活检查: 检查一个服务是否存活, 如果不存活,需要启动新的服务进行补充替换。对于JAVA 服务,如在Java 进程异常退出、 Java 进程假死等情况等不可自动恢复的情况,就任务是不存活的。

就绪检查: 检查一个服务是否准备好,对外提供服务。 主要应用在新应用启动后,不立刻提供对外服务,而需要通过就绪检查确定提供的服务的资源都准备好后才提供对外的服务。

为什么需要存活、就绪检查?

对于一个被线上使用的服务来说,降低服务的可用性的原因(不考虑编码层面问题)主要有:

  • 发布不平滑。 发布时不能平滑替换服务,导致部分请求报错,降低可用性。
  • 服务故障修复不及时。 一般是服务过载情况下, 服务缺失故障自动修复能力,或自动扩展能力不足,导致服务不可用。
  • 没有备份。 单机或单区运行,在服务器异常或区域性依赖异常时,导致服务不可用。

就绪检查主要会用在保障发布时保障服务可用时再提供对外服务使用,是平滑发布保障的关键一环。

存活检查主要用在服务过程中,服务发生不可恢复故障时,发布工具能够自动及时的进行补救。

如何实现存活就绪检查?

根据存活就绪检查目的, 实现方法一般会对应用依赖重要组件进行检查,汇总后提供检查结果输出。 可以看出来这是比较适合进行封装的动作,SpringBoot Actuator 就这样一个包,虽然存活就绪只是其功能之一。如果需要自己实现,SpringBoot Actuator 是一个很好的参考。

SpringBoot Actuator 做为存活就绪检查步骤

SpringBoot Actuator提供的健康检查端点 /actuator/health ,虽然也可以做为存活和就绪检查使用,通过对存活和就绪作用及原理的了解,知道其满足不了存活就绪分离检测。 不过在Spring Boot 2.3及以上版本,Actuator新增了分组功能,通过对健康端点的各组件进行分组,通过分组自定义组内包含的检测组件,实现不同作用的检测。如可以设置liveness和readiness 两个分组, 然后通过/actuator/health/liveness和/actuator/health/readiness来作为检测接口。

使用SpringBoot Actuator 基本上包含大部分组件的检查端点实现, 通过配置可以灵活的配置需要使用的端点。配置示例如下:

management:
  endpoints:
    web:
      exposure:
        include: "health,info,env"  #开放访问的接口
      base-path: /actuator     #自定义基础路径, 默认/actuator
  endpoint:
    health:
      enabled: true            #是否开启,默认true
      show-details: ALWAYS     #health 接口是否返回详情信息,方便排查一般返回
      group:
        readiness:               #就绪检查组,访问地址 /health/readiness
          include: "ping"   #就绪组检查的组件
        liveness:                #存活检查组,访问地址 /health/liveness
          include: "ping"        #存活组要检查的组件
  health:
    defaults.enabled: false      #关闭自动启用组件检查,改由手动启用
    ping.enabled: true           #defaults.enabled 为false 情况下, 手动指定开启的检查端点
    mysql.enabled: true          

如何确定哪些组件需要加入存活或就绪检测

一般项目不复杂,引入组件很少或没有重要影响系统运行的三方组件情况下,可以使用

Spring Boot Actuator提供的默认设置即可满足需求。但因为默认设置会默认加载所有引入组件包中带的Actuator 健康监测端点,而不管这个端点检测的组件是否使用,是否重要。为防止一些无效的包或不重要的组件检测故障导致存活或就绪检查失败,推荐自定义配置来定义设置线上项目的health 端点, 即把defaults.enabled 设置为false, 然后手工开启需要进行检测的组件。

具体项目中包含哪些可用端点呢, 可以通过设置defaults.enabled 设置为true,

开启actuator 健康检查,将health.defaults.enabled 设置为true的情况下,访问/actuator/health 就能查看到当前项目所有检查的端点。

如果项目没有需要检测的组件,或组件不适合作为就绪或存活检查项, 可以启用ping 组件做空白验证。对于有重要组件,但没有自带的检测端点,也可以自实现端点接口HealthIndicator。 示例:

public class MemHealthIndicator implements HealthIndicator {

    private final Logger LOGGER = LoggerFactory.getLogger(MemHealthIndicator.class);

    @Override
    public Health health() {
        MemoryMXBean memoryMbean = ManagementFactory.getMemoryMXBean();
        MemoryUsage usage = memoryMbean.getHeapMemoryUsage();
        Long max = usage.getMax();
        Long used = usage.getUsed();
        double useRate = used.doubleValue()/max.doubleValue();
        String useInfo = toString(usage);
        if(useRate > 0.9){
            LOGGER.info("jvm 内存使用超过90%,使用率{}",useInfo);
            return Health.down().withDetail("内存使用率",usage).build();
        }
        LOGGER.info("jvm 内存使用{}",useInfo);
        return Health.up().withDetail("内存使用率",usage).build();
    }

}

//其他配置类中
...
    @Bean
    @ConditionalOnEnabledHealthIndicator("mem") // 定义端点名称,配置中可以使用mem.enable:true 启用
    public HealthIndicator memHealthIndicator(){
        return new MemHealthIndicator();
    }
....

Java 关键字 — switch

Java 中的 switch 语句是一种多分支选择结构,它允许基于不同的情况执行不同的代码块。通常结合break, default, case 等关键字一起使用。

switch (expression) {
    case value1:
        // 代码块
        break; // 可选
    case value2:
        // 代码块
        break; // 可选
    // 可以有任意数量的case语句
    default: // 可选
        // 默认情况下的代码块
}

expression:这是一个必须的表达式,它的结果被用来与 case 语句中的值进行比较。这个表达式可以是 byte、short、char、int 类型,枚举(Java 5 开始支持),或者是 String 类型(Java 7 开始支持),
任意类型(Java 12).

case value:这是与 expression 结果进行匹配的值。如果匹配成功,紧随其后的代码块将被执行。
value 必须为一个常量表达式。  JAVA 12 开始支持类型推断。
JAVA 21 开始支持 when.

break:这是一个可选关键字,用来终止 switch 语句的执行。如果没有 break,程序将会继续执行下一个 case 中的代码,直到遇到 break 或者 switch 语句的结尾。

default:这也是一个可选部分,当没有任何 case 与 expression 的值匹配时,将执行 default 部分的代码。
示例:
final static class User{public String name;}

	private static final char m = 'm';
	private static final User user = new User();
	
	static void switchSen(Object v) {
		Character c = 1;
		User u = new User();
		switch(v) {
			case Integer i: System.out.println(i);
				break;
			//case c: System.out.println(1);
			//case 1: System.out.println(1);
			case User us: System.out.println(us);
			default: 
				System.out.println("default");
		}
		
		
		switch(c) {
			case 2: System.out.println(2);
			case m: System.out.println(m);
			default: 
				System.out.println("default");
		}
		
		switch(u) {
			case User i when u.name.equals("abc"): System.out.println(i);
	//		case user: System.out.println(2);
			default: 
				System.out.println("default");
		}
		
	}

switch 表达式
在 Java 12 及以后的版本中,switch 可以用作表达式,这意味着它可以返回一个值。switch 表达式使用 -> 来代替case 后的 :. 另外每个case 不需要使用break , 只返回因为表达式是有返回值的,因此其中的case 必须涵盖传入表达式的所有值。

switch (expression) {
    case value1 -> {
        // 代码块
        yield value; // 可选
    }
    case value2 -> value:
    // 可以有任意数量的case语句
    default -> default value // 可选
        // 默认情况下的代码块
}

case value:这是与 expression 结果进行匹配的值。如果匹配成功,紧随其后的代码块将被执行。
value 必须为一个常量表达式。 JAVA 12 开始支持类型推断。
JAVA 21 开始支持 when.

示例:

static String switchWhen(Object v) {
		//int when = 1; 
		//int yield = 3;//任然可以作为变量

		return switch(v) {
			case Integer i ->  i.toString();
			case String s when s.equalsIgnoreCase("yes") ->{
				yield s.toUpperCase();
			}
			case String s when s.equalsIgnoreCase("yeap") -> "yep";
			default ->  "EMPTY";
		};
	}
	
	static String switchNull(Object v) {
		return switch(v) {
			case Integer i ->  i.toString();
			case String s when s.equalsIgnoreCase("yes") ->{
				yield s.toUpperCase();
			}
			case String s -> "NO";
			case null,default ->  "EMPTY";
		};
	}
	
	static String switchString(CharSequence v) {
		return switch(v) {
			case String s -> "String";
			case CharSequence s -> "CS";
//			case String s when s.equalsIgnoreCase("yes") ->{
//				yield s.toUpperCase();
//			}
//			default ->  "EMPTY";  //前面两个case 已处理所有情况
		};
	}


为什么使用Switch
   从使用语法可知,switch 能实现的功能,if else 也是能实现的,但使用switch 有以下几大优势:


可读性: 当你需要基于同一个变量的不同值来执行不同的代码块时,switch语句比多个if-else语句更加清晰和易读。

效率: 对于包含许多case的情况,switch语句通常比if-else语句更高效,因为switch语句可以转化为跳转表,这意味着不需要按顺序检查每个条件。

可维护性: 当需要修改或者增加新的条件分支时,switch语句的结构使得这些变更更加集中和简单。
京ICP备12026520号-3