服务器程序源代码分析之二:php-fpm
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了服务器程序源代码分析之二:php-fpm相关的知识,希望对你有一定的参考价值。
参考技术Aphp作为排名top2 互联网开发工具,非常流行,可以参考:中国最大的25个网站采用技术选型方案
php这个名称实际上有两层含义
直接定义:
php-fpm从php5.3.3开始已经进入到php源代码包,之前是作为patch存在的
很少人会去读php本身源代码,我6年前解决php内存泄露问题的时候做了些研究,最近再查看了一番,发现php的开发者很有诚意,这是一款非常出色的服务器软件,支持如下
在linux服务器上,如果不设置 events.mechanism ,那么默认就是采用epoll,所以
php-fpm的IO模型&并发处理能力和nginx是完全一致
nginx以性能卓越闻名,大部分程序员都认为php效率低下,看了源代码,才知道这是传奇啊
在高性能部署的时候,大家往往会针对性的优化nginx 。我自己之前部署php程序也犯了错误,8G内存的server,php-fpm的max children都会设置128+,现在看来太多了,参考nginx的部署:
php-fpm配置为 3倍 cpu core number就可以了
php-fpm稳定性比nginx稍差 这是因为php-fpm内置了一个php解析器,php-fpm进程就和php程序捆绑了,如果php脚本写得不好,有死循环或者阻塞在某个远端资源上,会拖累加载它的php-fpm进程
而nginx和后端应用服务器之间通过网络连接,可以设置timeout,不容易堵死的
php-fpm的fastcgi是短连接 我原以为是长连接的,看了代码才知道也是短连接,处理一个request就关闭掉
php-fpm接口采用fastcgi 非常遗憾,php-fpm和fastcgi完全绑定了,无法独立使用 。只能部署在支持http-fcgi协议转换程序背后(nginx)。其实可以考虑在php-fpm代码包里面引入http协议支持,这样php-fpm可以独立运行,让nodejs无话可说
php-fpm等同于OpenResty OpenResty是一个国人开发的nginx模块,就是在nginx引入lua解释器. 实际上,它和php-fpm的唯一差别就是一个采用php语法,一个用lua,所以OpenResty要作为nginx增强包使用还可以,要选择它作为一个主要编程工具,没有任何必要
从架构上来说,php-fpm已经做到最好,超过大多数 python部署工具,我再也不黑它了
Openfire分析之二:主干程序分析
引言
宇宙大爆炸,于是开始了万物生衍,从一个连人渣都还没有的时代,一步步进化到如今的花花世界。
然而沧海桑田,一百多亿年过去了….
好复杂,但程序就简单多了,main()函数运行,敲个回车,一行Hello World就出来了,所以没事多敲敲回车,可以练手感….
一、程序入口
Java的程序入口是main方法,Openfire也不例外。可以全局检索一下”void main”,可以看到,Openfire的main函数有两个:
(1)org.jivesoftware.openfire.launcher.Launcher类,以图形界面的形式启动
public static void main(String[] args) throws AWTException {
new Launcher();
}
(2)org.jivesoftware.openfire.starter.ServerStarter类,以服务的形式启动
public static void main(String [] args) {
new ServerStarter().start();
}
一般Openfire都做为服务的形式运行,所以我们重点关注ServerStarter类即可。如果是用eclipse调试Openfire工程,Run Configure中的Main Class也是设置这个类的完整路径。
org.jivesoftware.openfire.starter.ServerStarter类
|-- main()方法
|-- start()
ServerStarter.start( )方法加载配置,并用类加载的方法,实例化org.jivesoftware.openfire.XMPPServer。
XMPPServer
XMPPServer实例化之后,直接调用本类中的start( )方法,于是系统开始跑起来。运行逻辑如下:
|-- XMPPServer()构造方法
|-- start()
|-- 加载 conf/openfire.xml,设置host、xmpp.domain等全局参数
|-- 验证数据库是否可用
|-- 加载、初始化、并启动openfire Module
|-- 加载并启动 plugins
|-- 启动监听服务:listener.serverStarted()
下面从XMPPServer.start( )方法开始,跟一下Openfire在启动的过程中,处理了哪些业务,以此来分析Openfire启动流程。
三、服务启动流程分析
1、XMPPServer.start()方法
添加了部分注释,如下:
public void start() { try { //初始化配置 initialize(); // 插件管理 File pluginDir = new File(openfireHome, "plugins"); pluginManager = new PluginManager(pluginDir); if (!setupMode) { // 验证数据库是否可用,验证方法也很简单,就是执行一句sql语句,没有异常表示可用。 verifyDataSource(); //加载module loadModules(); //初始化module initModules(); //启动modeule startModules(); } // 服务器流量计算类,用来计算服务器写入和读取的字节数,包括C-S,S-S或扩展的组件和连接的流量 ServerTrafficCounter.initStatistics(); //启动插件 pluginManager.start(); //打印启动日志 String startupBanner = LocaleUtils.getLocalizedString("short.title") + " " + version.getVersionString() + " [" + JiveGlobals.formatDateTime(new Date()) + "]"; logger.info(startupBanner); System.out.println(startupBanner); started = true; //通知其他的监听服务,服务器已启动 for (XMPPServerListener listener : listeners) { listener.serverStarted(); } } catch (Exception e) { e.printStackTrace(); logger.error(e.getMessage(), e); System.out.println(LocaleUtils.getLocalizedString("startup.error")); shutdownServer(); } }
从上面代码和注释的内容,可以确确看出,启动的过程,主要就做了这几件事:
(1)初始化
(2)加载启动各个模块
(3)加载启动各个插件
(4)启动监听
下面,分别括概性的分析这几个部分,目的是使读者对Openfire的启动有个大致的印象,即可。
2、初始化,initialize()方法
在Openfire的安装目录,有一个openfire.xml配置文件,初始化主要是将配置文件中的信息载入系统,并处理一些与进程和缓存等相关的工作。
private void initialize() throws FileNotFoundException { //用于确定openfire的工作目录,以及配置文件,并构造相应的File实例 //这里不做深入,将处理的内容列举如下: //1. openfire配置文件所在的相对路径(conf/openfire.xml)。 //2. 获取openfire工作目录的绝对路径homeProperty //3. 来获取配置文件的File实例verifyHome locateOpenfire(); startDate = new Date(); //获取计算机名 try { host = InetAddress.getLocalHost().getHostName(); } catch (UnknownHostException ex) { logger.warn("Unable to determine local hostname.", ex); } if (host == null) { host = "127.0.0.1"; } version = new Version(4, 0, 3, Version.ReleaseStatus.Release, -1); if ("true".equals(JiveGlobals.getXMLProperty("setup"))) { setupMode = false; } if (isStandAlone()) { //设置服务器异常关机时执行的函数ShutdownHookThread(),当服务器异常关机时,必然会执行shutdownServer函数,关闭一些模块、执行一些监听函数等等 logger.info("Registering shutdown hook (standalone mode)"); Runtime.getRuntime().addShutdownHook(new ShutdownHookThread()); //启动一个定时线程,该线程监听控制台的输入,如果为“exit”,则调用System.exit退出openfire进程。 TaskEngine.getInstance().schedule(new Terminator(), 1000, 1000); } loader = Thread.currentThread().getContextClassLoader(); try { //初始化了org.jivesoftware.util.cache.DefaultLocalCacheStrategy实例,该实例和缓存有关 CacheFactory.initialize(); } catch (InitializationException e) { e.printStackTrace(); logger.error(e.getMessage(), e); } //migrateProperty()方法用于将数据从xml载入到数据库,并处理一些配置信息 JiveGlobals.migrateProperty("xmpp.domain"); name = JiveGlobals.getProperty("xmpp.domain", host).toLowerCase(); JiveGlobals.migrateProperty(Log.LOG_DEBUG_ENABLED); Log.setDebugEnabled(JiveGlobals.getBooleanProperty(Log.LOG_DEBUG_ENABLED, false)); // Update server info //最后初始化了XMPPServerInfoImpl实例 xmppServerInfo = new XMPPServerInfoImpl(name, host, version, startDate); initialized = true; }
3、Module和Plugin 的加载
从XMPPServer.start( )的方法执行的内容来看,主要加载两大主体,一个是module,一个是plugin,这两部分可以说是整套系统的所有功能实现。下面对这两个部分,先做一个简述。具体的机制,在后续另起章节描述分析。
(1)Module
Openfire的核心功能都依靠module实现,所有的module都继承自BasicModule,而BasicModule实现了Module接口。
Module接口类定义了如下方法列表:
String getName(); void initialize(XMPPServer server); void start(); void stop(); void destroy();
从方法名可以看出,它描述了所有的module在整个生命周期内应调用的方法。
而BaseModule则对Module进行了空实现,所有的module对BaseModule中的方法选择性覆写。
各个module在XMPPServer启动之初,被装载在一个容器中:
Map<Class, Module> modules
通过递归的方式,调用module所覆写的initialize()、start()、stop()、destroy()等方法,实现对module的管理。
module的加载,有一点需要留意下:ConnectionManager是在最后加载,源码中有如下代码段及注释:
// Load this module always last since we don‘t want to start listening for clients // before the rest of the modules have been started loadModule(ConnectionManagerImpl.class.getName());
Openfire的连接管理、端口监听,都在ConnectionManager这个模块中进行处理,这也是它为何要放在最后一个加载的原因。
(2)plugin
plugin的启动,是在module之后。
pluginManager.start()方法中启动了PluginMonitor线程:
public void start() { executor = new ScheduledThreadPoolExecutor(1); // See if we‘re in development mode. If so, check for new plugins once every 5 seconds. // Otherwise, default to every 20 seconds. if (Boolean.getBoolean("developmentMode")) { executor.scheduleWithFixedDelay(pluginMonitor, 0, 5, TimeUnit.SECONDS); } else { executor.scheduleWithFixedDelay(pluginMonitor, 0, 20, TimeUnit.SECONDS); } }
线程的执行run()方法如下:
@Override public void run() { ...... try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(pluginDirectory, new DirectoryStream.Filter<Path>() { @Override public boolean accept(Path pathname) throws IOException { String fileName = pathname.getFileName().toString().toLowerCase(); return (fileName.endsWith(".jar") || fileName.endsWith(".war")); }})) { for (Path jarFile : directoryStream) { ...... if (Files.notExists(dir)) { unzipPlugin(pluginName, jarFile, dir); } ...... } } ...... // Load all plugins that need to be loaded. for (Path dirFile : dirs) { if (Files.exists(dirFile) && !plugins.containsKey(dirFile.getFileName().toString())) { loadPlugin(dirFile); } } ...... // Trigger event that plugins have been monitored firePluginsMonitored(); ...... }
PluginMonitor线程的主要处理:解压插件目录下所有拓展名为jar和war的插件,用loadPlugin( )装载该插件,最后通过firePluginsMonitored( )函数调用插件的监听函数。
firePluginsMonitored( )方法中,调用插件的监听函数pluginsMonitored( ):
private void firePluginsMonitored() { for(PluginManagerListener listener : pluginManagerListeners) { listener.pluginsMonitored(); } }
PluginManagerListener.pluginsMonitored()监听函数,在ConnetionMamagerImpl模块启动时实现。
在ConnetionMamagerImpl.startListeners()方法,省略一些无关的代码,如下:
private synchronized void startListeners() { PluginManager pluginManager = XMPPServer.getInstance().getPluginManager(); if (!pluginManager.isExecuted()) { pluginManager.addPluginManagerListener(new PluginManagerListener() { public void pluginsMonitored() { ...... } }); return; } ...... }
也就解释了插件的启动是在module启动之后。
事实上也可以理解:modules为openfire自带模块,plugins我们可以称为外来者。openfire需要对plugins进行管理、以及各种响应,那么自然需要其自身各个模块首先运作起来,这可以理解为一个主次顺序。
4、最后,启动监听服务
// Notify server listeners that the server has been started for (XMPPServerListener listener : listeners) { listener.serverStarted(); }
通知PubSubModule、PresenceManagerImpl、MultiUserChatServiceImpl等module监听启动。
至此,openfire完成了启动。
四、服务关闭
讲了系统的启动,接下来稍微提一下系统的stop。
服务关闭相对就简单一些,当收到控制能上的exit指令、或者启动过程之中出现了异常时, 就会调用关闭程序,通知其他的服务模块关闭监听、所有的模块和插件都停止并注销、关闭数据库资源、关闭线程的监听等。
这里贴一下代码:
private void shutdownServer() { shuttingDown = true; ClusterManager.shutdown(); // Notify server listeners that the server is about to be stopped for (XMPPServerListener listener : listeners) { try { listener.serverStopping(); } catch (Exception ex) { logger.error("Exception during listener shutdown", ex); } } // If we don‘t have modules then the server has already been shutdown if (modules.isEmpty()) { return; } logger.info("Shutting down " + modules.size() + " modules ..."); // Get all modules and stop and destroy them for (Module module : modules.values()) { try { module.stop(); module.destroy(); } catch (Exception ex) { logger.error("Exception during module shutdown", ex); } } // Stop all plugins logger.info("Shutting down plugins ..."); if (pluginManager != null) { try { pluginManager.shutdown(); } catch (Exception ex) { logger.error("Exception during plugin shutdown", ex); } } modules.clear(); // Stop the Db connection manager. try { DbConnectionManager.destroyConnectionProvider(); } catch (Exception ex) { logger.error("Exception during DB shutdown", ex); } // Shutdown the task engine. TaskEngine.getInstance().shutdown(); // hack to allow safe stopping logger.info("Openfire stopped"); }
OK,主干程序就分析到此。Openfire中的消息机制是怎么样的,各个模块是如何协作,插件又该怎么编写,在后续的章节中解答。
希望这一系列的文章对您有所帮助,Over!
以上是关于服务器程序源代码分析之二:php-fpm的主要内容,如果未能解决你的问题,请参考以下文章
opencv源代码分析之二:cvhaartraining.cpp