JBOSS EAP实战(2)-集群、NGINX集成、队列与安全
JBOSS HTTP的Thread Group概念
JBOSS是一个企业级的J2EE APP Container,因此它和任何一种成熟的企业级中间件一样具有Thread Group的概念。
所谓Thread Group就是一个HTTP队列机制,利用Thread Group在JBOSS内可以设置如“阻断”,“升级”,“降级”等机制。
来看一个这样的实际应用场景:
当你的JBOSS连着一堆核心应用时,此时突然你的HTTP的并发请求在某一个点激增,如果把这些HTTP请求都放进后台,那么将意味着你所有的核心模块将会受到严重的影响,因此一般来说对于这样的场景我们会采取如下的几种措施:
- 阻断,除去定额的请求,其它请求进不进来,即抛弃策略
- 队列,除去定额的请求,其它请求排队,排队排到前端超时,出错
- 自动扩展,即给定一个定额的最大请求数,当超过这个请求数后Container会根据自身服务器的能力自动扩展
JBOSS的Thread Group的种类
在JBOSS里使用的正是Thread Group来支持这几种机制的,我们来看一下这几种机制在JBOSS中的实现:
- unbounded-queue-thread-pool
- bounded-queue-thread-pool
- blocking-bounded-queue-thread-pool
- queueless-thread-pool
- blocking-queueless-thread-pool
- scheduled-thread-pool
做过开发的相信一眼就看出来,其实这6种JBOSS提供的HTTP Thread Group正是JAVA多线程内ConcurrentThreadPool的线程种类。
本文对6种线程不做一一探讨,他们的区别从字面上相信读者可以马上理解,在配置时参数略有不同,对于每个不同种类的Thread Group读者可以自行通过JBOSS官方文档(如下URL)获取它们的区别:
https://developer.jboss.org/wiki/ThreadPoolConfiguration
本文只作JBOSS EAP6中如何进行Thread Group的配置。
动手配置一个Thread Group
打开$JBOSS_HOME/standalone/configuration/standalone.xml文件
找到<subsystem xmlns=”urn:jboss:domain:threads:1.1“>这句,把它改成:
<subsystem xmlns="urn:jboss:domain:threads:1.1"> <thread-factory name="http-connector-factory" group-name="jboss1-thread-pool" thread-name-pattern="HTTP-%t" priority="9"/> <unbounded-queue-thread-pool name="jboss1-thread-pool"> <max-threads count="250"/> <keepalive-time time="20" unit="seconds"/> <thread-factory name="http-connector-factory"/> </unbounded-queue-thread-pool> </subsystem>
找到<subsystem xmlns=”urn:jboss:domain:web:2.2“ default-virtual-server=”default-host“ native=”false“>这句,再往下找到:
<connector name=“http” protocol=“HTTP/1.1”这一句,把这一句改成:
<connector name="http" protocol="HTTP/1.1" scheme="http" socket-binding="http" executor="jboss1-thread-pool" max-connections="250"/>
重启JBOSS即可
用Security Domain保密认证信息
为什么要加密? 我们来看下面这个例子:
<datasource jndi-name="java:/comp/env/jdbc/jbpm" pool-name="DroolsDS" enabled="true" use-java-context="true"> <connection-url>jdbc:mysql://192.168.0.101:3306/guvnor</connection-url> <driver>mysql</driver> <pool> 。。。 </pool> <user-name>drools</user-name> <password>password_1</password> </datasource>
虽然,你可以说这个配置文件是位于企业内网的,内网就安全了?万一有有心人。。。因为安全的宗旨是“宁可信其有(小偷),不可信其无”。
因此一般来说在生产环境我们是需要对这个配置文件中的敏感信息进行加密的。
JBOSS对配置文件中的加密有两种方式:
1. Picketbox(推荐全部用这种方式)
2. Vault(不适合于DOMAIN模式、很繁琐)
用Security Domain保密认证信息-Picketbox方式
确保你使用的是JBOSS EAP6.2-6.4或者更高版本,在Linux中启动一个shell,敲入如下命令行:
export JBOSS_HOME=/opt/jboss export CLASSPATH=${JBOSS_HOME}/modules/system/layers/base/org/picketbox/main/picketbox-4.1.1.Final-redhat-1.jar:${JBOSS_HOME}/modules/system/layers/base/org/jboss/logging/main/jboss-logging-3.1.4.GA-redhat-2.jar:$CLASSPATH java org.picketbox.datasource.security.SecureIdentityLoginModule EncryptedPassword
此处的”EncryptedPassword”就是我们需要加密的“明文”,接下去它会输出如:
Encoded password: -6e9c0a6a3bb1e4c0
这边的“Encoded password:”后的就是被加密后的密文
然后,我们需要建立一个Security-Domain,我们在<profile name=“full”>段下找到
<subsystem xmlns="urn:jboss:domain:security:1.2">
<security-domains>
由于我们用的是domain模式启动的jboss,因此,你不能找到一个”security-domains”就去改,因为domain模式是根据profile name来区分配置段的,如果你的security-domain加在default下,而启动使用的是full模式那么你的配置是无效的。
我们在
<subsystem xmlns="urn:jboss:domain:security:1.2">
<security-domains>
段下加入
<security-domain name="encrypted-ds" cache-type="default"> <authentication> <login-module code="org.picketbox.datasource.security.SecureIdentityLoginModule" flag="required"> <module-option name="username" value="drools"/> <module-option name="password" value="-6e9c0a6a3bb1e4c0"/> </login-module> </authentication> </security-domain>
有了security-domain我们就可以在我们的配置中应用它了,如我们在datasource配置处使用security-domain
<datasource jndi-name="java:/comp/env/jdbc/jbpm" pool-name="DroolsDS" enabled="true" use-java-context="true"> <connection-url>jdbc:mysql://192.168.0.101:3306/guvnor> <driver>mysql</driver> <pool> 。。。 </pool> <security> <security-domain>encrypted-ds</security-domain> </security> </datasource>
用Security Domain保密认证信息-vault模式
使用vault模式要求我们先需要在JBOSS中建立自己的VAULT,我们使用如下命令:
$JBOSS_HOME/bin/vault.sh
出现如下字符界面:
JBoss Vault
JBOSS_HOME: /Volumes/Disk01/appservers/center/jboss-eap-6.1
JAVA: java
============================================================
**********************************
**** JBoss Vault ***************
**********************************
Please enter a Digit::
0: Start Interactive Session 1: Remove Interactive Session 2: Exit
按选0
Enter directory to store encrypted files:[输入] ../vault/
Enter Keystore URL:[输入] ../vault/ymkeyes.keystore
Enter Keystore password: [输入]aaaaaa
Enter Keystore password again: [输入]aaaaaa
Enter 8 character salt: [输入]12345678
Enter iteration count as a number (Eg: 44): [输入]44
Enter Keystore Alias: [输入] ymkeyes
Initializing Vault
2013-7-18 11:08:02 org.picketbox.plugins.vault.PicketBoxSecurityVault init
INFO: PBOX000361: Default Security Vault Implementation Initialized and Ready
Vault Configuration in AS7 config file:
********************************************
</extensions>
<vault>
<vault-option name="KEYSTORE_URL" value="../vault/ymkeyes.keystore"/>
<vault-option name="KEYSTORE_PASSWORD" value="MASK-1TWrTaqRsEC"/>
<vault-option name="KEYSTORE_ALIAS" value="ymkeyes"/>
<vault-option name="SALT" value="11136231"/>
<vault-option name="ITERATION_COUNT" value="8"/>
<vault-option name="ENC_FILE_DIR" value="../vault/"/>
</vault> ...
********************************************
Vault is initialized and ready for use
Handshake with Vault complete
Please enter a Digit::
0: Store a secured attribute 1: Check whether a secured attribute exists 2: Exit
按选0
把上面从<vault>开始到</valult>这一块复制进standalone.xml文件中,放于</extensions>段后
对于屏幕中输出的如下信息,注意红色加粗字体即是你用vault加密后的加密串。
Secured attribute value has been stored in vault.
Please make note of the following:
********************************************
Vault Block:ds_MegaeyesDS
Attribute Name:password
Shared Key:MjY5Nzc0OWItMmI2OS00NWMyLTlkZTYtOWY0MWE5YzAyOGMwTElORV9CUkVBS21lZ2FleWVz
Configuration should be done as follows:
VAULT::ds_oracleds::password::MjY5Nzc0OWItMmI2OS00NWMyLTlkZTYtOWY0MWE5YzAyOGMwTElORV9CUkVBS21lZ2FleWVz
********************************************
有了VAULT,你就可以在你的配置文件中如我们在<datasource>中,如下配置:
<datasources> <datasource jndi-name="java:/comp/env/jdbc/jbpm" pool-name="DroolsDS" enabled="true" use-java-context="true"> <connection-url>jdbc:mysql://192.168.0.101:3306/guvnor</connection-url> <driver>mysql</driver> <pool> <min-pool-size>10</min-pool-size> <max-pool-size>25</max-pool-size> <flush-strategy>IdleConnections</flush-strategy> </pool> <security> <user-name>drools</user-name> <password>${VAULT::ds_oracleds::password::1}</password> </security> </datasource>
注意加密串还要前后加上${加密串}才可生效
可以看出,使用这种方式的加密,太繁琐,而且它不支持DOMAIN模式即不可使用在集群环境中。
JBOSS集群
啊。。。集群,又是集群。
JBOSS集群是从3.X开始那差不多是在14年前就开始支持集群了。
目前的JBOSS EAP6.2-6.4或者是JBOSS8.0(WILDFLY)对于集群的支持已经到了登峰造极的地步了,而且最主要的是JBOSS的集群具有以下特色:
- 任意横向、纵向折分
- JBOSS VM-JBOSS自己可以虚拟出一堆的JBOSS来
- 集中管控,即Controller,利用这一点它可以做到一点布署多点应用,这种集中统一布署的功能只有在IBM的WAS中才具有,即Domain Management的功能
JBOSS集群-横向纵向折分
试想一下这种架构:
如果在这个架构中任何一个SLAVE发生了宕机,那么对于整个集群来说用户的访问是依旧可以用的。
但是,我们试想一下,如果JBOSS MASTER都宕机了呢?
于是,我们有了如下的架构:
这样,在任意一点发生不可预料的宕机时,我们的整个集群的服务还是持续可用的只要不是所有的节点都宕掉。
所谓横向、纵向
JBOSS可以纵横交错,任意划分出无数台“JBOSS虚拟机”从而构成一个“网状”结构
由于JBOSS采用了类似于IBM WAS的域控理念,因此它可以做到“集中统一布署”,即只要在主控端发布一个程序,该主控端下属所有的MASTER、SLAVER全部会自动布署。而这个自动化“分发”的过程,是不需要人为干涉的。它意味着我们的运维开发不需要一个个手工COPY war包到每个J2EE APP SERVER上去了。
JBOSS虚拟机
JBOSS也引入了时下最时髦的“VM”技术,即你可以启动一个JBOSS Instance,然后在这个JBOSS的Instance中为每一个不同的JBOSS子Container指定它们的参数、甚至JVM核心参数
JBOSS集群搭建
说了这么多,我们就来搭建一个JBOSS集群吧!
先来规划我们的JBOSS集群,在我们的例子中我们不使用JBOSS虚拟机,我们使用2个真正的JBOSS Instance即启动两个不同JVM进程的JBOSS来实现MASTER-SLAVER的模式。
我们先来规划我们的JBOSS集群。
我们建立两个JBOSS实例,使其分别名为:
JBOSS_MASTER1
JBOSS_SLAVE1
JBOSS集群一定用的是DOMAIN模式来去运行的,即$JBOSS_HOME/bin/domain.sh来运行的,因此我们记得把两个JBOSS的$JBOSS_HOME/domain/configuration/host.xml文件中的下面这段:
<management-interfaces> <native-interface security-realm="ManagementRealm"> <socket interface="management" port="${jboss.management.native.port:9999}"/> </native-interface> <http-interface security-realm="ManagementRealm"> <socket interface="management" port="${jboss.management.http.port:9990}"/> </http-interface> </management-interfaces>
以上配置中的9999与9990确保两个JBOSS Instance中的这两个端口一定不能一样,如我的slave中的此处配置为:
<management-interfaces> <native-interface security-realm="ManagementRealm"> <socket interface="management" port="${jboss.management.native.port:19999}"/> </native-interface> </management-interfaces>
你也可以把slave中的host.xml文件COPY一份然后使用host-slave.xml改名成host.xml文件
MASTER和SLAVE的host.xml文件的区别还有一点,来看SLAVE的host.xml文件下面这一处:
<domain-controller> <remote host="${jboss.domain.master.address:192.168.0.101}" port="${jboss.domain.master.port:9999}" security-realm="ManagementRealm"/> </domain-controller>
从这边可以看到slave中有一个<domain-controller>段,在这个段内所指向的 ManagementRealm的IP与端口必须是它的master即Master主机中host.xml文件中的ManagementReal所设的IP与PORT。
注意host.xml文件中的头部信息
<?xml version='1.0' encoding='UTF-8'?> <host name="slave1" xmlns="urn:jboss:domain:1.7">
不要忘了把<host后加入一个”name”,这边的name很重要!!!
JBOSS集群有一个很重要的步骤,即:
加入每个MASTER的SLAVE,是以JBOSS中的用户名和密码来“认证”的,即每个SLAVE要加入Master,Master必须允许。
因此,此处的host name=“”就是SLAVE 用户名,那么密码呢?
请在JBOSS MASTER所在的目录中运行add-user.sh命令,然后建一个用户名,这个用户名必须=host后的name,另外,在建完用户后把该用户的密码(显示为MD5值)记下,然后在slave所在的host.xml文件中把密码设进去,按照如下例子:
因为JBOSS是以GROUP的概念来管理它的集群的,如下图:
所以我们需要把我们的8080与8081这两台主机加入到一个GROUP中去。
在slave的host.xml文件中增加入下内容
<servers> <server name="slave1" group="kie-server-group" auto-start="true"> <socket-bindings socket-binding-group="full-sockets" port-offset="1"/> </server> <servers>
- group,如果不同的host属于同一个group,那么它们将同属于一个master管理
- full-sockets:JBOSS中有ha, full, full-ha 3种profile,我们在此使用的是full模式,相对应的socket为full-sockets
- port-offset 为端口偏移量,在刚才的步骤中我们对于ManagementRealm中的两个端口是手设的,可是还有其它一堆的端口,多达10几个,我们总不至于一个个去手改吧,因此这边的port-offset为“除去managementreal”中的端口号其它所有端口号(当然不包括一些应用自己在内部开设的一些端口)+1,比如说:master的web端口为8080,那么slave1的web端口就会变为8081, slave2设成port-offset=2那么slave2的web端口就为8082了,是不是很方便?
相应的,在master机上的host.xml文件中也需要有这么一句:
<servers> <server name="master1" group="kie-server-group" auto-start="true"> </server> </servers>
其实domain.xml文件中主要的是对JBOSS“资源”的配置,如:
datasource,system properties, jvm。
由于JBOSS使用的是Domain Controller模式,因此对于JBOSS中一切资源的使用只需要在Controller上配置即可。
你可以把JBOSS的controller相像成一个“狮子座”,它的占有欲极强。
由于在本例中我们使用的是一台MASTER,一台SLAVE,因此这台MASTER又同时是CONTROLLER,当然在机器富足的情况下你也可以考虑CONTROLLER挂一台MASTER,然后MASTER下再挂一台或者是多台SLAVE。
此处还需要注意的是domain.xml文件很长,它里面有这样的开头:
<profiles> <profile name="default">
而这样的profile会有3处重复,所不同的是profile name=“full”, profile name=“full ha”,因此你的<server group>设的是什么profile,你的需要设置的资源也需要在domain.xml文件中放在哪个profile段。 此例中我们使用的是full,因此我们的DataSource, System Properties等就都放在了这个段里。
我们之前在MASTER和SLAVE的host.xml文件中分别把2台主机都加入到了一个叫kie-server-group的段,因此我们需要在domain.xml文件中加入一个server group的声明段,如下示例:
<server-groups> <server-group name="kie-server-group" profile="full"> <socket-binding-group ref="full-sockets"/> </server-group> </server-groups>
因为JBOSS启动实例用的是domain.sh+domain.conf中JVM所设的JVM参数,而JBOSS真正运行起来后用于RUN相关的布署的应用用的却是JBOSS内的“Domain JVM”,而你在domain.conf中设置的JVM参数只是给JBOSS启动时所使用的,并不是这个JBOSS实例如:运行某个布署的.war包时所使用的真正的JVM,而一旦该JBOSS实例加入了某个group时,当这个实例开始布署或者是运行某个.war包或者是.ear包时,如果你没有设过domain jvm参数,那么它的实际可用JVM堆默认只有256MB。
这就是为什么外面很多人说,JBOSS集群一旦启动,发觉本来在单节点运行的好好的web应用在集群环境下却会报out of memory或者是gc overhead limit。
让我们在MASTER和SLAVE上的host.xml文件中为我们的2个JBOSS实例配置虚拟JVM参数吧
Master上的host.xml文件中的设置
<server name="master1" group="kie-server-group" auto-start="true"> <jvm name="master1" debug-enabled="false"> <heap size="6144m" max-size="6144m"/> <permgen size="512m" max-size="768m"/> <jvm-options> <option value="-XX:-UseGCOverheadLimit"/> </jvm-options> </jvm> <socket-bindings socket-binding-group="full-sockets" port-offset="0"/> </server>
Slave上的host.xml文件中的设置
<server name="slave1" group="kie-server-group" auto-start="true"> <socket-bindings socket-binding-group="full-sockets" port-offset="1"/> <jvm name="slave1" debug-enabled="false"> <heap size="6144m" max-size="6144m"/> <permgen size="512m" max-size="768m"/> <jvm-options> <option value="-XX:-UseGCOverheadLimit"/> </jvm-options> </jvm> </server>
全部配完后分别使用:
/opt/jboss_master1/bin/domain.sh
/opt/jboss_slave1/bin/domain.sh
命令把master和slave启动起来吧。
启动后我们打开主控端的WEB管理界面http://192.168.0.101:9990,可以看到如下网络拓扑结构。
其实,嘿嘿!
刚才那一段繁琐枯燥的配置,你也完全可以把MASTER先启动起来后通过http://192.168.0.101:9990界面进入后可视化进行操作,看到这儿有人可能会想“打我”了,可是。。。如果你拿图形界面配过无数次后也能像我一样手配XML文件了,而且手配XML文件。。。速度更快!
在JBOSS集群中布署应用
布署一个应用,在这边我也想用xml文件来讲解。。。可是又怕有人“打我”,但是在集群环境下的布署分为:
.war包
.war格式的目录(exploded war)
而JBOSS的自带的http://192.168.0.101:9990只支持图形化布署.war包。。。不支持布署.war格式的目录,因此我们还是用xml文件配置的方式来说吧(你们最终反正是要把我打一顿的,?)
先来说一下集群环境下的.war包的布署吧,我们以petstore.war来举例,我们把master-slave结构搭完后,直接按照如下步骤操作
JBOSS会把这个变成一个两进制文件并以GUID的方式重命名该文件,并把它置于MASTER的/domain/servers/master_name/data目录。
我们注意这个$MASTER_JBOSS_HOME/domain/server目录,这下面还会有一个slave1的目录,这是JBSOS自动生成的,因此MASTER所挂集群下所有节点的日志、启动信息、布署都必须在此目录内寻找,而不是再手工跑到每个JBOSS节点中去寻找了。这也正是JBOSS的domain controller集中管控思想的体现。
在此步骤,你只是把需要deploy的文件上传到了主控域上且并未开始布署,为了完成真正的布署,你需要点击【Assign】按钮
一旦你点击了Assign按钮后,你会得到一个弹出界面,在此界面内会提示你要把这个.war Assign给哪一个group,此时,一旦当你把一个war Assign给了某个group并点击了【确定】后,JBOSS会作如下的事情:
- 从Controller开始先布署MASTER
- 再布署该Controller下所挂的SLAVER、SLAVER中的MASTER,S`SLAVE
如果你的Controller下挂的节点是一颗很复杂的“树”,它也会一键完成所有节点的deploy。
布署完后,我们来查看一下我们的集群应用:
看,2个地址都可以访问了。
使用NGINX来对JBOSS集群作“分发”
网上很多教程使用的都是Apache的mod_模块,相当的繁琐。
与Nginx相比Apache弱到爆了,我们来看一下Nginx是怎么完成对jboss集群的分发的吧。
http { rewrite_log on; default_type application/octet-stream; charset utf-8; sendfile on; tcp_nopush on; keepalive_timeout 60; tcp_nodelay on; client_header_buffer_size 4k; large_client_header_buffers 4 32k; server_names_hash_bucket_size 128; upstream j2eeserver { server 192.168.0.101:8080; server 192.168.0.101:8081; } include mime.types; log_format main '$remote_addr $remote_user [$time_local] "$request" $http_host ' '$status $upstream_status $body_bytes_sent "$http_referer"' '"$http_user_agent" $ssl_protocol $ssl_cipher $upstream_addr' '$request_time $upstream_response_time'; access_log logs/access.log main; gzip on; gzip_min_length 1k; gzip_buffers 4 16k; gzip_http_version 1.1; gzip_comp_level 2; gzip_types text/plain application/x-javascript text/css application/xml; gzip_vary on; server { listen 80; server_name 192.168.0.101 localhost; charset utf-8; #access_log logs/host.access.log main; location / { proxy_set_header X-Real-IP $remote_addr; proxy_set_header Host $host; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Server $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://j2eeserver; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } }
我们来实验一下吧。
cd /usr/local/nginx
./nginx –c /usr/local/nginx.conf
启动后访问:http://192.168.0.101/petstore (此时不用加端口号了)
然后我们在后台,随意杀掉master或者是slave进程
我们来杀master进程吧
重新访问http://192.168.0.101/petstore, look,还是跑得刚刚的!我们的集群成功了。
这边我们也不用作什么动静分离了,就让NGINX全部转发JBOSS的请求即可,因为你公司也不差这点内存、CPU,做什么动静分离。。。non sense!
JBOSS集群中如何布署exploded war
要布署这样的文件目录形式其实很简单,你只要打开主控域上的domain.xml文件,在其中加入这样的语句,.war目录可以随便置放在何处, wherever u want:
<deployments> <deployment name="petstore.war" runtime-name="petstore.war"> <fs-exploded path="/opt/jboss_master1/domain/data/petstore.war"/> </deployment> </deployments>
然后回到http://ip:9990 这个图形化界面中就会看到在主控域上有一个.war已经被Deploy,但还没有被Assign,此时你只要做相应的Assign操作即可完成整个JBOSS集群的布署了。
附上Nginx的安装
Nginx需要依赖下面3个包
1. gzip 模块需要 zlib 库 ( 下载: http://www.zlib.net/ ) zlib-1.2.8.tar.gz
2. rewrite 模块需要 pcre 库 ( 下载: http://www.pcre.org/ ) pcre-8.21.tar.gz
3. ssl 功能需要 openssl 库 ( 下载: http://www.openssl.org/ ) openssl-1.0.1.tar.gz
注意:如果用源码安装的话,后面nginx安装的时候需要指定 --with-pcre 对应的压缩包路径
openssl:
tar -xzvf openssl-1.0.1.tar.gz cd openssl-1.0.1 ./config(注意) && make && make install
pcre:
tar -xzvf pcre-8.21.tar.gz cd pcre-8.21 ./configure && make && make install
zlib:
tar -xzvf zlib-1.2.8.tar.gz cd zlib-1.2.8 ./configure && make && make install
通过 http://nginx.org/download/ 下载nginx最新版,本例中使用的是nginx-1.8.1.tar.gz,下载解压后进入nginx源码目录使用下面命令进行一键式编译安装:
./configure --sbin-path=/usr/local/nginx/nginx --conf-path=/usr/local/nginx/nginx.conf --pid-path=/usr/local/nginx/nginx.pid --with-http_ssl_module --with-pcre=../pcre-8.38 --with-zlib=../zlib-1.2.8 --with-openssl=../openssl-1.0.2g --with-http_stub_status_module --user=nginx --group=nginx
全部成功后,在/usr/local目录下就会生成一个nginx目录,nginx就安装在此目录内了。