
一、简介 WordPress是一款能建立出色网站、博客或应用的开源软件。其设计美观、功能强大,同时开源免费。35%的网站都在使用WordPress,小到兴趣博客,大到新闻网站(官网数据)。 二、环境准备 系统: centos7 LAMP Centos7 + Apache2.4 + php5.6 + mysql5.7 + phpMyAdmin4.8.3 LNMP Centos7 + Nginx 1.13.7 + php5.6 + mysql5.7 + phpMyAdmin4.8.3 注意事项 Nginx和Apache两者在不修改默认配置时,均占用80端口,因而不能共存,端口冲突。本次安装采用Apache作为web服务。有LAMP、LNMP的一键安装集成环境,也可以自行安装,为深入学习环境安装、命令操作、软件配置等,本次安装采用自行安装方式安装环境。 系统升级 如需升级系统已安装的包、软件、内核等,可使用如下命令升级。 yum -y update 软件卸载 如操作系统中安装有Apache、php、php相关插件库如php-fpm、php-pdo、php-mysql,mysql等软件,则需要先自行卸载,或者准备一个纯洁系统,以备后用。 三、安装apache 安装apache yum -y install httpd 注意: 该方法安装完毕后,Apache配置文件路径为 /etc/httpd/conf/httpd.conf 可使用命令查看Apache服务状态 service httpd status 或者 systemctl status httpd.service 开启服务 systemctl start httpd.service 关闭服务 systemctl stop httpd.service 重启服务 systemctl restart httpd.service 设置开机启动 systemctl enable httpd.service 防火墙相关操作 注意,这里需要用到一些防火墙的相关操作,主要命令如下,以供参考。 #开启防火墙 systemctl start firewalld.service #关闭防火墙 systemctl stop firewalld.service #防火墙状态 systemctl status firewalld.service #重启防火墙 firewall-cmd --reload #查看端口的开放情况 firewall-cmd --list-all 开启80端口 由于我们安装了Apache服务,后续要使用80端口,所以需要防火墙放开80端口权限控制,而不是粗暴的直接关闭防火墙。 配置防火墙使得HTTP流量、HTTPS流量能够顺利通过防火墙,并阻挡其他可疑流量 firewall-cmd --add-service=http --permanent firewall-cmd --add-service=https --permanent firewall-cmd --add-port=80/tcp --permanent 注意:参数--permanent代表永久有效,不加这此参数的话,防火墙重启后之前所做配置就失效了1:修改系统防火墙配置文件,在第五行配置中增加允许80端口监听外来ip iptables -I INPUT 5 -i eth0 -p tcp --dport 80 -j ACCEPT 2:查看配置文件的内容, 看到刚刚加进去的内容 iptables --line -vnL 访问 安装完成之后,访问Apache服务,出现默认首页,说明安装成功。 如出现如图所示界面,则代表Apache服务安装成功。注意两个红色方框所圈示内容,/var/www/html为Apache服务页面所在路径。/etc/httpd/conf.d/welcom.conf可配置欢迎页相关内容。 四、安装php 安装php 1 换源 由于wordpress5.2.3需要PHP7.0以上版本,Centos7.4标准环境只提供到5.4版本,我们需要更换rpm源来实现安装PHP7.0以上版本。 在terminal执行以下命令 #CentOs 7.X: rpm -Uvh https://mirror.webtatic.com/yum/el7/epel-release.rpm rpm -Uvh https://mirror.webtatic.com/yum/el7/webtatic-release.rpm 2安装 安装PHP7.2及相关插件 yum install -y php72w php72w-cli php72w-common php72w-devel php72w-embedded php72w-fpm php72w-gd php72w-mbstring php72w-mysqlnd php72w-opcache php72w-pdo php72w-xml php72w-ldap php72w-mcrypt 3 测试 php -v 环境安装到此阶段,php环境已基本安装完毕,可测试是否可用。注意前文提到的关键路径/var/www/html,在此目录下创建info.php文件,并编辑内容。 vim /var/www/html/info.php 文件内容如下: <!DOCTYPE html> <html> <body> <?php phpinfo(); ?> </body> </html> 重启 systemctl restart httpd.service 电脑端浏览器输入http://ip/info.php,会出现php的相关配置信息,即可代表环境安装成功。 五、安装MySQL 添加源 rpm -Uvh http://dev.mysql.com/get/mysql57-community-release-el7-9.noarch.rpm #或 wget http://dev.mysql.com/get/mysql57-community-release-el7-9.noarch.rpm rpm -ivh mysql57-community-release-el7-9.noarch.rpm 安装 yum -y install mysql-community-server 查找默认密码 安装完成之后,安装程序为root 生成了一个默认密码,该密码输出到了 /var/log/mysqld.log 文件中,可以通过如下方式找到该日志文件中的root 用户默认密码,进而可以登录mysql数据库进行后续其它配置操作。 # 启动Mysql systemctl start mysqld # 查找默认密码 grep 'temporary password' /var/log/mysqld.log 输出结果如下: 配置mysql 登录 mysql -u root -p onlM*Lt=3lx5 //刚才查询的 然后输入密码即可进入mysql控制台 修改默认密码: ALTER USER 'root'@'localhost' IDENTIFIED BY 'new password'; 或 set password for 'root'@'localhost'=password('new password'); mysql5.7 版本默认安装了密码安全检查插件(validate_password),默认密码检查策略要求密码必须包含:大小写字母、数字和特殊符号,并且长度不能少于8位。否则会提示 ERROR 1819 (HY000): Your password does not satisfy the current policy requirements 错误。 开启远程访问权限: use mysql; update user set host = '%' where user = 'root'; FLUSH PRIVILEGES; #查看 select host,user from user; exit; 开放3306端口 firewall-cmd --zone=public --add-port=3306/tcp --permanent #重新加载防火墙 firewall-cmd --reload 此时,远端已可以通过root用户访问mysql服务器,本地亦可以通过 Navicat等客户端工具连接。 六、创建配置WordPress数据库 登入mysql mysql -u root -p 输入root账户密码即可进入 创建wordpress数据库 建立wordpress数据库 CREATE DATABASE wordpressdb; 创建wordpress数据库账户&&设置密码 CREATE USER 用户名@localhost IDENTIFIED BY '密码'; #CREATE USER wordpress@localhost IDENTIFIED BY '密码'; 将”用户名”替换成你自己的用户名;将”密码”替换成你自己的密码;记下用户名和密码 授予wordpress数据库账户在wordpress数据库上所需权限 授予权限 GRANT ALL PRIVILEGES ON 数据库名.* TO 用户名@localhost; #GRANT ALL PRIVILEGES ON wordpressdb.* TO wordpress@localhost; 刷新数据库缓存 FLUSH PRIVILEGES; 退出数据库 exit 七、安装wordpress WordPress官网下载安装源码,也可以在中文官网下载中文版:https://cn.wordpress.org/download/。 下载最新wordpress版本 使用wget下载最新wordpress wget http://wordpress.org/latest.zip 解压文件,并且将其复制到/var/www/html目录下 #解压文件 unzip -q latest.zip #复制 wordpress文件夹下所有文件到html目录下 cp -rf wordpress/* /var/www/html/ #修改文件夹权限 赋予apache对相关文件夹的权限 修改文件夹权限 chown -R apache:apache /var/www/html/ chmod -R 755 /var/www/html/ mkdir -p /var/www/html/wp-content/uploads chown -R :apache /var/www/html/wp-content/uploads 编辑WordPress配置文件 编辑配置文件 cd /var/www/htmlcp wp-config-sample.php wp-config.phpvim wp-config.php 找到define(‘DB_NAME’, ‘wordpressdb’); 将 wordpressdb 修改为你创建的wordpress数据库名 找到define(‘DB_USER’, ‘wordpressuser’); 将 wordpressuser 修改为你创建的数据库用户名 找到define(‘DB_PASSWORD’, ‘123456’); 将 123456 修改你创建数据库用户的密码 输入:wq!保存 重启相关服务 重启相关服务systemctl restart httpd.service systemctl restart mysqld.service systemctl start mysqld.service systemctl stop mysqld.service 进入Web页面设置 访问http://你的域名/wp-admin进入wordpressWEB安装配置界面,按照要求设置用户名密码,就可完成wordpress最后设置安装完成
一、 下载Maven 从Maven官方地址:http://maven.apache.org/download.cgi下载最新版本apache-maven-xxx-bin.tar.gz。 二、将Maven 添加进环境变量 Maven 下载完毕后,解压到环境变量集合的位置,将其解压在/usr/local/maven目录下。 然后在终端中,执行如下命令 $ open ~/.bash_profile 或者 ¥ vim /etc/profile 在里面添加如下的maven 配置: # 添加Maven到环境变量 export M3_HOME=/usr/local/maven/maven3.6.x export PATH=$M3_HOME/bin:$PATH 添加了Maven 配置后的文件内容如下(如果你还配置了别的变量可能不止这么点): 保存,并关闭该文件。 四、使最新的环境变量生效 执行如下命令: $ source ~/.bash_profile 可以让上面新添加的环境变量生效。 五、测试Maven 是否安装成功 可以先输出Maven 环境地址 echo $M3_HOME echo $PATH 如果输出类似这样的值: /usr/local/maven/maven3.3.9 /usr/local/maven/maven3.3.9/bin:/usr/local/Cellar/mongodb/3.4.2/bin:/Users/harvey/.nvm/versions/node/v4.6.1/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin 就表明配置没问题。 接下来用maven 的命令查看 maven 版本,鉴定Maven 环境是否安装成功。 mvn -version 成功时,输出的日志如下: HarveydeMac-mini:~ harvey$ mvn -version Apache Maven 3.3.9 (bb52d8502b132ec0a5a3f4c09453c07478323dc5; 2015-11-11T00:41:47+08:00) Maven home: /usr/local/maven/maven3.3.9 Java version: 1.7.0_79, vendor: Oracle Corporation Java home: /Library/Java/JavaVirtualMachines/jdk1.7.0_79.jdk/Contents/Home/jre Default locale: zh_CN, platform encoding: UTF-8 OS name: "mac os x", version: "10.12.3", arch: "x86_64", family: "mac" 这样,Mac下Maven的环境就配置好了。
检查是否安装tcl,expect 如果没有安装,使用yum安装: yum install -y tcl yum install -y expect expect参数 启用选项 -c:执行脚本前先执行的命令,可多次使用。 -d:debug模式,可以在运行时输出一些诊断信息,与在脚本开始处使用exp_internal 1相似。 -D:启用交换调式器,可设一整数参数。 -f:从文件读取命令,仅用于使用#!时。如果文件名为"-",则从stdin读取(使用"./-"从文件名为-的文件读取)。 -i:交互式输入命令,使用"exit"或"EOF"退出输入状态。 --:标示选项结束(如果你需要传递与expect选项相似的参数给脚本时),可放到#!行:#!/usr/bin/expect --。 -v:显示expect版本信息。 expect的4个命令 Expect中最关键的四个命令是send,expect,spawn,interact。 命令 说明 send 用于向进程发送字符串 expect 从进程接收字符串 spawn 启动新的进程 interact 允许用户交互 常用命令 `# 命令行参数` `# $argv,参数数组,使用[lindex $argv n]获取,$argv 0为脚本名字` `# $argc,参数个数` `set` `username [lindex $argv 1]` `# 获取第1个参数` `set` `passwd` `[lindex $argv 2]` `# 获取第2个参数` `set` `timeout 30` `# 设置超时` `# spawn是expect内部命令,开启ssh连接` `spawn` `ssh` `-l username 192.168.1.1` `# 判断上次输出结果里是否包含“password:”的字符串,如果有则立即返回,否则就等待一段时间(timeout)后返回` `expect` `"password:"` `# 发送内容ispass(密码、命令等)` `send` `"ispass\r"` `# 发送内容给用户` `send_user` `"$argv0 [lrange $argv 0 2]\n"` `send_user` `"It's OK\r"` `# 执行完成后保持交互状态,控制权交给控制台(手工操作)。否则会完成后会退出。` `interact` 命令介绍 close:关闭当前进程的连接。 debug:控制调试器。 disconnect:断开进程连接(进程仍在后台运行)。 定时读取密码、执行priv_prog `send_user` `"password?\ "` `expect_user -re` `"(.*)\n"` `for` `{} 1 {} {` `if` `{[fork]!=0} {``sleep` `3600;``continue``}` `disconnect` `spawn priv_prog` `expect Password:` `send` `"$expect_out(1,string)\r"` `. . .` `exit` `}` exit:退出expect。 exp_continue [-continue_timer]:继续执行下面的匹配。 exp_internal [-f file] value: expect范例 1.远程登录并创建文件后退出 #!/usr/bin/expect ##注意路径,使用 [whereis expect] 查看 set user "hadoop" ##设定参数,注意",'的区别 set pwd "yangkun" set host "48.93.36.144" set timeout -1 ##;号可有可无 spawn ssh -p 2020 $user@$host expect { ##expect后有空格 "*yes/no" {send "yes\r";exp_continue} "*password:" {send "$pwd\r"} } expect "]*" ## 通配符,使用 ]* 有效, 使用 *# 无效 send "touch /home/hadoop/aa.txt\r" expect "]*" send "echo hello world >> /home/hadoop/aa.txt\r" expect "]*" [interact] ##人为交互 send "exit\r" ##退出 2.配置免密登录并安装JDK #!/bin/bash #!/usr/bin/expect SERVERS="114.114.114.114" ##数组以空格分隔,可以为目标ip 或者hostName PASSWORD="yangkun" ## 实现免密登录配置的函数 auto_ssh_copy_id() { expect -c "set timeout -1; spawn ssh-copy-id \"-p 2020 $1\"; ## 这里要注意,使用'或\'不可行 expect { *(yes/no)* {send -- yes\r;exp_continue;} *password:* {send -- $2\r;exp_continue;} eof {exit 0;} }"; } ## 循环执行,配置主机到从节点所有免密 ssh_copy_id_to_all() { for SERVER in $SERVERS ## 取值需要加$ do auto_ssh_copy_id $SERVER $PASSWORD done } ## 调用循环配置函数 ssh_copy_id_to_all ## 批量部署 for SERVER in $SERVERS do scp install.sh root@$SERVER:/root ssh root@$SERVER /root/install.sh done 读取文件中的host配置 让脚本自动读取slaves文件中的机器名来批量安装 cat slaves | while read host do echo $host expect -c "set timeout -f spawn ssh-copy-id $host" done 3.批量配置JDK,install.sh #!/bin/bash BASE_SERVER=master BASE_PATH=/home/hadoop/soft TARGET_PATH=/usr/local JAVA_PATH=$TARGET_PATH/java ## 1.判断是否存在文件夹,不存在则创建soft文件夹 #if [ ! -d "$BASE_PATH" ]; then # mkdir "$BASE_PATH" #fi ## 2.从指定host拷贝jdk到目标机器上(已经拷贝文件夹) scp -r $BASE_SERVER:$BASE_PATH $BASE_PATH ## 2.解压jdk到指定目录 if [ ! -d "$JAVA_PATH" ]; then sudo -S mkdir -p "$JAVA_PATH" fi ## 赋予权限 sudo -S chmod -R hadoop:hadoop $JAVA_PATH tar -zxvf $BASE_PATH/jdk1.8.0_121.tar.gz -C $JAVA_PATH #### 3.配置环境变量 sudo -S cat>>/etc/profile<<EOF export JAVA_HOME=$JAVA_PATH/jdk1.8.0_121 export PATH=\$PATH:\$JAVA_HOME/bin EOF 自动telnet会话 #!/usr/bin/expect -f set ip [lindex $argv 0 ] # 接收第1个参数,作为IP set userid [lindex $argv 1 ] # 接收第2个参数,作为userid set mypassword [lindex $argv 2 ] # 接收第3个参数,作为密码 set mycommand [lindex $argv 3 ] # 接收第4个参数,作为命令 set timeout 10 # 设置超时时间 # 向远程服务器请求打开一个telnet会话,并等待服务器询问用户名 spawn telnet $ip expect "username:" # 输入用户名,并等待服务器询问密码 send "$userid\r" expect "password:" # 输入密码,并等待键入需要运行的命令 send "$mypassword\r" expect "%" # 输入预先定好的密码,等待运行结果 send "$mycommand\r" expect "%" # 将运行结果存入到变量中,显示出来或者写到磁盘中 set results $expect_out(buffer) # 退出telnet会话,等待服务器的退出提示EOF send "exit\r" expect eof 4.自动建立FTP会话 #!/usr/bin/expect -f set ip [lindex $argv 0 ] # 接收第1个参数,作为IP set userid [lindex $argv 1 ] # 接收第2个参数,作为Userid set mypassword [lindex $argv 2 ] # 接收第3个参数,作为密码 set timeout 10 # 设置超时时间 # 向远程服务器请求打开一个FTP会话,并等待服务器询问用户名 spawn ftp $ip expect "username:" # 输入用户名,并等待服务器询问密码 send "$userid\r" expect "password:" # 输入密码,并等待FTP提示符的出现 send "$mypassword\r" expect "ftp>" # 切换到二进制模式,并等待FTP提示符的出现 send "bin\r" expect "ftp>" # 关闭ftp的提示符 send "prompt\r" expect "ftp>" # 下载所有文件 send "mget *\r" expect "ftp>" # 退出此次ftp会话,并等待服务器的退出提示EOF send "bye\r" expect eof 自动登录ssh执行命令 #!/usr/bin/expect set IP [lindex $argv 0] set USER [lindex $argv 1] set PASSWD [lindex $argv 2] set CMD [lindex $argv 3] spawn ssh $USER@$IP $CMD expect { "(yes/no)?" { send "yes\r" expect "password:" send "$PASSWD\r" } "password:" {send "$PASSWD\r"} "* to host" {exit 1} } expect eof 5.自动登录ssh #!/usr/bin/expect -f set ip [lindex $argv 0 ] # 接收第1个参数,作为IP set username [lindex $argv 1 ] # 接收第2个参数,作为username set mypassword [lindex $argv 2 ] # 接收第3个参数,作为密码 set timeout 10 # 设置超时时间 spawn ssh $username@$ip # 发送ssh请求 expect { # 返回信息匹配 "*yes/no" { send "yes\r"; exp_continue} # 第一次ssh连接会提示yes/no,继续 "*password:" { send "$mypassword\r" } # 出现密码提示,发送密码 } interact # 交互模式,用户会停留在远程服务器上面 6.批量登录ssh服务器执行操作范例,设定增量的for循环 #!/usr/bin/expect for {set i 10} {$i <= 12} {incr i} { set timeout 30 set ssh_user [lindex $argv 0] spawn ssh -i .ssh/$ssh_user abc$i.com expect_before "no)?" { send "yes\r" } sleep 1 expect "password*" send "hello\r" expect "*#" send "echo hello expect! > /tmp/expect.txt\r" expect "*#" send "echo\r" } exit 7.批量登录ssh并执行命令,foreach语法 #!/usr/bin/expect if {$argc!=2} { send_user "usage: ./expect ssh_user password\n" exit } foreach i {11 12} { set timeout 30 set ssh_user [lindex $argv 0] set password [lindex $argv 1] spawn ssh -i .ssh/$ssh_user root@xxx.yy.com expect_before "no)?" { send "yes\r" } sleep 1 expect "Enter passphrase for key*" send "password\r" expect "*#" send "echo hello expect! > /tmp/expect.txt\r" expect "*#" send "echo\r" } exit 8.另一自动ssh范例,从命令行获取服务器IP,foreach语法,expect嵌套 #!/usr/bin/expect # 使用方法: script_name ip1 ip2 ip3 ... set timeout 20 if {$argc < 1} { puts "Usage: script IPs" exit 1 } # 替换你自己的用户名 set user "username" #替换你自己的登录密码 set password "yourpassword" foreach IP $argv { spawn ssh $user@$IP expect \ "(yes/no)?" { send "yes\r" expect "password:?" { send "$password\r" } } "password:?" { send "$password\r" } expect "\$?" # 替换你要执行的命令 send "last\r" expect "\$?" sleep 10 send "exit\r" expect eof } 9.批量ssh执行命令,用shell调用tclsh方式、多进程同时执行 * tclsh - Simple shell containing Tcl interpreter #!/bin/sh # -*- tcl -*- \ exec tclsh $0 "$@" package require Expect set username [lindex $argv 0] set password [lindex $argv 1] set argv [lrange $argv 2 end] set prompt "(%|#|\\$) $" foreach ip $argv { spawn ssh -t $username@$ip sh lappend ids $spawn_id } expect_before -i ids eof { set index [lsearch $ids $expect_out(spawn_id)] set ids [lreplace $ids $index $index] if [llength $ids] exp_continue } expect -i ids "(yes/no)\\?" { send -i $expect_out(spawn_id) yes\r exp_continue } -i ids "Enter passphrase for key" { send -i $expect_out(spawn_id) \r exp_continue } -i ids "assword:" { send -i $expect_out(spawn_id) $password\r exp_continue } -i ids -re $prompt { set spawn_id $expect_out(spawn_id) send "echo hello; exit\r" exp_continue } timeout { exit 1 } 10.ssh登录过程常规提示文字 The authenticity of host '192.168.17.35 (192.168.17.35)' can't be established. RSA key fingerprint is 25:e8:4c:89:a3:b2:06:ee:de:66:c7:7e:1b:fa:1c:c5. Are you sure you want to continue connecting (yes/no)? Warning: Permanently added '192.168.17.35' (RSA) to the list of known hosts. Enter passphrase for key '/data/key/my_dsa': Last login: Sun Jan 26 13:39:37 2014 from 192.168.11.143 [root@master003 ~]# root@192.168.16.90's password: Last login: Thu Jan 23 17:50:43 2014 from 192.168.11.102 [root@lvsmaster ~]# 11.ssh自动登录expect脚本:ssh.expect #!/usr/bin/expect -f # Auther:YuanXing # Update:2014-02-08 if {$argc < 4} { send_user "Usage:\n $argv0 IPaddr User Passwd Port Passphrase\n" puts stderr "argv error!\n" sleep 1 exit 1 } set ip [lindex $argv 0 ] set user [lindex $argv 1 ] set passwd [lindex $argv 2 ] set port [lindex $argv 3 ] set passphrase [lindex $argv 4 ] set timeout 6 if {$port == ""} { set port 22 } #send_user "IP:$ip,User:$user,Passwd:$passwd,Port:$port,Passphrase:$passphrase" spawn ssh -p $port $user@$ip expect_before "(yes/no)\\?" { send "yes\r"} expect \ "Enter passphrase for key*" { send "$passphrase\r" exp_continue } " password:?" { send "$passwd\r" exp_continue } "*\[#\\\$]" { interact } "* to host" { send_user "Connect faild!" exit 2 } timeout { send_user "Connect timeout!" exit 2 } eof { send_user "Lost connect!" exit } 12.Mikrotik backup script using ssh and expect #!/bin/bash # TAG: mikrotik, ssh, expect, lftp BACKUP_DIR="/var/backups" HOSTNAME="192.168.88.1" PORT="22" USER="admin" PASS="123456" TMP=$(mktemp) TODAY=$(date +%F) FILENAME="$HOSTNAME-$TODAY" PATH="/usr/local/sbin:/usr/sbin:/sbin:/usr/local/bin:/usr/bin:/bin" # create expect script cat > $TMP << EOF #exp_internal 1 # Uncomment for debug set timeout -1 spawn ssh -p$PORT $USER@$HOSTNAME match_max 100000 expect -exact "password:" send -- "$PASS\r" sleep 1 expect " > " send -- "/export file=$FILENAME\r" expect " > " send -- "/system backup save name=$FILENAME\r" expect " > " send -- "quit\r" expect eof EOF # run expect script #cat $TMP # Uncomment for debug expect -f $TMP # remove expect script rm $TMP # download and remove backup files # "xfer:clobber on" means overwrite existing files cd ${BACKUP_DIR} echo " set xfer:clobber on get ${FILENAME}.rsc rm ${FILENAME}.rsc get ${FILENAME}.backup rm ${FILENAME}.backup" | lftp -u $USER,$PASS $HOSTNAME
1.申请免费阿里证书 填写相应的信息,域名信息即可.成功后:根据自己的网站服务器来选择下载不同的ssl证书包括: 1_root_bundle.crt # 证书文件 2_xxx.xxx.xxx.crt # 证书文件 3_xxx.xxx.xxx.key # 私钥文件 2. 配置证书 在这里,我假设你已经会配置基本的/etc/apache2/sites-available/000-default.conf这个文件来达到已经可以通过 http 的方式来访问你的站点。 在/etc/apache2这个目录下,有两个有关的目录sites-available和sites-enabled,我们进入sites-enabled目录下可以发现,里面有一个文件000-default.conf $ ll lrwxrwxrwx 1 root root 35 Dec 28 15:24 000-default.conf -> ../sites-available/000-default.conf 实质上这个文件是/etc/apache2/sites-available/000-default.conf这个文件的软链接。 我们要配置另 ssl 证书,要依靠另一个文件,也就是default-ssl.conf,首先我们需要设置一个软链接,把这个文件链接到sites-enabled这个文件夹中: ln -s /etc/apache2/sites-available/default-ssl.conf /etc/apache2/sites-enabled/000-default-ssl.conf 然后去修改这个文件000-default-ssl.conf,因为已经做了软链接,其实这时候修改000-default-ssl.conf或default-ssl.conf都一样。 这个文件没有做任何修改前长这样子(去除自带的注释之后): <IfModule mod_ssl.c> <VirtualHost _default_:443> ServerAdmin webmaster@localhost DocumentRoot /var/www/html ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined SSLEngine on SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key <FilesMatch "\.(cgi|shtml|phtml|php)$"> SSLOptions +StdEnvVars </FilesMatch> <Directory /usr/lib/cgi-bin> SSLOptions +StdEnvVars </Directory> </VirtualHost> </IfModule> 然后把从阿里云上面下载好的证书(3个文件)传到你自定义的目录中 然后我们需要修改一下,修改成这样: <IfModule mod_ssl.c> <VirtualHost _default_:443> ServerAdmin 你的邮箱 DocumentRoot /var/www/你的目录 ServerName 你的域名 ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined SSLEngine on # 注意,需要添加这三行 SSLCertificateFile 你自定义的路径/2_xxx.xxx.xxx.crt SSLCertificateKeyFile 你自定义的路径/3_xxx.xxx.xxx.key SSLCertificateChainFile 你自定义的路径/1_root_bundle.crt <FilesMatch "\.(cgi|shtml|phtml|php)$"> SSLOptions +StdEnvVars </FilesMatch> <Directory /usr/lib/cgi-bin> SSLOptions +StdEnvVars </Directory> </VirtualHost> </IfModule> 重要的三个参数的作用如下表: 配置文件参数 说明 SSLEngine on 启用 SSL 功能 SSLCertificateFile 证书文件 SSLCertificateKeyFile 私钥文件 SSLCertificateChainFile 证书链文件 改好之后保存。 然后这时,我们加载一下 Apache2 的 SSL 模块: sudo a2enmod ssl #加载模块 sudo service apache2 restart # 重启服务 这时,在浏览器输入https://你的域名应该已经可以通过 https 的方式来访问网站了,这时浏览器那里应该也已经有了一个绿色的小锁。 但是,但是…这还不够,因为我们如果不主动输入https://的话,直接输入域名,还是会直接跳转到 80 端口的普通的 http 方式访问,所以我们需要强制使用 https 来访问 强制使用https 我们只需要打开/etc/apache2/sites-available/000-default.conf这个文件,在你的这个标签内随便一个地方加上三行: RewriteEngine on RewriteCond %{HTTPS} !=on RewriteRule ^(.*) https://%{SERVER_NAME}$1 [L,R] 然后保存,然后启动 Apache2 的重定向: sudo a2enmod rewrite 然后再重启 Apache2,至此大功告成: sudo service apache2 restart 然后,打开浏览器直接输入域名,就会自动跳转到 https 的方式。
1.Apache安装 首先安装apache服务器,ubuntu下面使用apt-get来下载安装软件。 sudo apt-get install apache2 输入密码后,便开始下载安装了,安装好后打开浏览器,输入localhost查看是否安装成功 如果如上显示的话,说明安装成功了。 2.PHP安装 这里我准备安装PHP7.0版本的,在命令行输入: sudo apt-get install php7.0 安装完成后输入:php -v 查看PHP是否安装成功 php -v PHP和Apache都安装好后就需要让Apache能够识别解析PHP文件,我们先搜一下有没有适合PHP7的插件,输入命令: apt-cache search libapache2-mod-php 可以看到搜出来的结果里面有一个是PHP7.0版本的,我们就安装这个: sudo apt-get install libapache2-mod-php7.0 下面我们就可以随便写一个php文件看是否可以解析访问。输入命令: cd /var/www/html 切换到apache项目目录下,新建文件: sudo vim test.php 内容: <?php phpinfo(); ?> 保存后浏览器访问:localhost/test.php 如果一切正常的话,就会看到php的一些信息。 3.Mysql安装 接下来就是安装数据库Mysql了,数据库需要装服务端和客户端两个,输入命令: sudo apt-get install mysql-server mysql-client 安装过程中会提示设置root账号的登录密码,输入后选择OK继续安装 安装完成后,输入mysql -V 查看安装的版本信息 mysql -V 同样的,我们还需要让mysql能够和php互动,安装php的mysql插件: sudo apt-get install php7.0-mysql 最后我们还可以安装一些常用的php扩展 sudo apt-get install php7.0-gd php7.0-mbstring php7.0-xml 到此关于lamp的软件就安装完成了,最后还可以安装一下composer: sudo apt-get install composer 安装好后输入命令:composer 查看是否成功 composer 如图显示,安装过程就全部完成了。 php 环境相关问题: 准备通过phpinfo()获取php的所有配置文件路径test.php 内容 <?php phpinfo(); ?> 1.curl 未开启,请先开启mbstring扩展 解决方法: 安装插件 sudo apt-get install curl libcurl4 libcurl3-dev php7.2-curl 开启权限 sudo sudo vim /etc/php/7.2/apache2/php.ini php.ini当中的 ;extension=php_curl.dll (去掉注释) 改成 extension=php_curl.dll 重启服务 sudo /etc/init.d/apache2 restart 2.mbstring 未开启,请先开启mbstring扩展 解决方法: 安装插件 sudo apt-get install php7.2-mbstring 开启权限 sudo sudo vim /etc/php/7.2/apache2/php.ini ;extension=php_mbstring.dll 改为 extension=php_mbstring.dll 去掉分号的注释作用 重启服务 sudo /etc/init.d/apache2 restart 3.cache 无效,请先设置目录读写权限,请联系您的空间或者服务器提供商 Linux php根目录下: cd /var/www/html/ mkdir cache chmod 777 cache chmod 777 index.php
2021年01月
会的
不可以吧
以后可能会的
里面总共包括几个主要的工程吧:hbase-common,hbase-client,hbase-prefix-tree,hbase-server,test-hbase,hbase-compact*,hbase-thrift,hbase-protocol等工程。
其中hbase-common是基础的,工程里面的jar包都放在它下面的lib目录,hbase-compact*那两个工程一直有问题,我在hbase-server下面的lib目录放了两个jar包,没引用这两个工程,就是放一些监控信息的,可以删掉。
前言
之前因为仅仅是把HBase当成一个可横向扩展并且具有持久化能力的KV数据库,所以只用在了指标存储上,参看很早之前的一篇文章基于HBase做Storm 实时计算指标存储。这次将HBase用在了用户行为存储上,因为Rowkey的过滤功能也很不错,可以很方便的把按人或者内容的维度过滤出所有的行为。从某种意义上,HBase的是一个有且仅有一个多字段复合索引的存储引擎。
虽然我比较推崇实时计算,不过补数据或者计算历史数据啥的,批处理还是少不了的。对于历史数据的计算,其实我是有两个选择的,一个是基于HBase的已经存储好的行为数据进行计算,或者基于Hive的原始数据进行计算,最终选择了前者,这就涉及到Spark(StreamingPro) 对HBase的批处理操作了。
整合过程
和Spark 整合,意味着最好能有Schema(Mapping),因为Dataframe 以及SQL API 都要求你有Schema。 遗憾的是HBase 有没有Schema取决于使用者和场景。通常SparkOnHBase的库都要求你定义一个Mapping(Schema),比如hortonworks的 SHC(https://github.com/hortonworks-spark/shc) 就要求你定义一个如下的配置:
{
"rowkey":"key",
"table":{"namespace":"default", "name":"pi_user_log", "tableCoder":"PrimitiveType"},
"columns":{"col0":{"cf":"rowkey", "col":"key", "type":"string"},
"col1":{"cf":"f","col":"col1", "type":"string"}
}
}
看上面的定义已经还是很容易看出来的。对HBase的一个列族和列取一个名字,这样就可以在Spark的DataSource API使用了,关于如何开发Spark DataSource API可以参考我的这篇文章利用 Spark DataSource API 实现Rest数据源中使用,SHC大体实现的就是这个API。现在你可以这么用了:
val cat = "{n"rowkey":"key","table":{"namespace":"default", "name":"pi_user_log", "tableCoder":"PrimitiveType"},n"columns":{"col0":{"cf":"rowkey", "col":"key", "type":"string"},n"28360592":{"cf":"f","col":"28360592", "type":"string"}n}n}"
val cc = sqlContext
.read
.options(Map(HBaseTableCatalog.tableCatalog -> cat))
.format("org.apache.spark.sql.execution.datasources.hbase")
.load()
不过当你有成千上万个列,那么这个就无解了,你不大可能一一定义,而且很多时候使用者也不知道会有哪些列,列名甚至可能是一个时间戳。我们现在好几种情况都遇到了,所以都需要解决:
自动获取HBase里所有的列形成Schema,这样就不需要用户配置了。
规定HBase只有两个列,一个rowkey,一个 content,content 是一个map,包含所有以列族+列名为key,对应内容为value。
先说说第二种方案(因为其实第一种方案也要依赖于第二种方案):
{
"name": "batch.sources",
"params": [
{
"inputTableName": "log1",
"format": "org.apache.spark.sql.execution.datasources.hbase.raw",
"path": "-",
"outputTable": "log1"
}
]
},
{
"name": "batch.sql",
"params": [
{
"sql": "select rowkey,json_value_collect(content) as actionList from log1",
"outputTableName":"finalTable"
}
]
},
首先我们配置了一个HBase的表,叫log1,当然,这里是因为程序通过hbase-site.xml获得HBase的链接,所以配置上你看不到HBase相关的信息。接着呢,在SQL 里你就可以对content 做处理了。我这里是把content 转化成了JSON格式字符串。再之后你就可以自己写一个UDF函数之类的做处理了,从而实现你复杂的业务逻辑。我们其实每个字段里存储的都是JSON,所以我其实不关心列名,只要让我拿到所有的列就好。而上面的例子正好能够满足我这个需求了。
而且实现这个HBase DataSource 也很简单,核心逻辑大体如下:
case class HBaseRelation(
parameters: Map[String, String],
userSpecifiedschema: Option[StructType]
)(@transient val sqlContext: SQLContext)
extends BaseRelation with TableScan with Logging {
val hbaseConf = HBaseConfiguration.create()
def buildScan(): RDD[Row] = {
hbaseConf.set(TableInputFormat.INPUT_TABLE, parameters("inputTableName"))
val hBaseRDD = sqlContext.sparkContext.newAPIHadoopRDD(hbaseConf, classOf[TableInputFormat], classOf[ImmutableBytesWritable], classOf[Result])
.map { line =>
val rowKey = Bytes.toString(line._2.getRow)
import net.liftweb.{json => SJSon}
implicit val formats = SJSon.Serialization.formats(SJSon.NoTypeHints)
val content = line._2.getMap.navigableKeySet().flatMap { f =>
line._2.getFamilyMap(f).map { c =>
(Bytes.toString(f) + ":" + Bytes.toString(c._1), Bytes.toString(c._2))
}
}.toMap
val contentStr = SJSon.Serialization.write(content)
Row.fromSeq(Seq(UTF8String.fromString(rowKey), UTF8String.fromString(contentStr)))
}
hBaseRDD
}
}
那么我们回过头来,如何让Spark自动发现Schema呢?大体你还是需要过滤所有数据得到列的合集,然后形成Schema的,成本开销很大。我们也可以先将我们的数据转化为JSON格式,然后就可以利用Spark已经支持的JSON格式来自动推倒Schema的能力了。
总体而言,其实并不太鼓励大家使用Spark 对HBase进行批处理,因为这很容易让HBase过载,比如内存溢出导致RegionServer 挂掉,最遗憾的地方是一旦RegionServer 挂掉了,会有一段时间读写不可用,而HBase 又很容易作为实时在线程序的存储,所以影响很大。
Hbase 是一个always-available的服务,在机器故障的时候保持可用性,集群中的机器都运行regionserver daemons。但一个regionserver出现故障,或者机器掉线,那么保存在上面的regions也同样掉线。Hbase中MTTR的能够检测异常,尽可能早的恢复对掉线region的访问。
文章解释了hbase如何管理MTTR,并且介绍了一些hbase和hdfs的设置。
Hbase是一致性时对故障的如何保持弹性
Hbase通过让一个单独的server负责数据的子集,也就是说,一个region在同一时刻只能被一个region server进行管理。
Hbase对失败的弹性,归功于hdfs,因为在写数据的时候,数据会被复制到到不通的节点。
Hbase将数据写入到hfiles中,hfile保存在hdfs上面,hdfs完成对hfiles的block的副本(replica)。默认情况下是副本数是3.
Hbase使用commit log(或者称之为Write-Ahead-log,WAL),提交日志也同样写在HDFS上面,默认副本数为3.
Hbase在故障检测以及访问恢复的步骤:
识别宕机的节点(Identifying that a node is down):由于过载或者直接死掉,节点会不再响应。
在write操作进行中的时候进行恢复(Recovering the writes in progress):通过读取commit log,恢复还没有被flush的edit。
重新分配region:失败的regionserver之前在正在处理一些region,这些region需要被重新分配给其他的RS(regionSever),这个分配过程要根据每台RS不通的workload的情况
那上面三个过程中哪一个步骤最痛苦?在检测以及恢复步骤发生的时候,客户端会被阻塞。MTTR的作用就是加速这个处理过程,让客户端对数据downtime的感知时间尽可能的短。
检测失败节点:
一个RS的失败原因很多:正常的情况下,可能是由于clean stop,比如管理员关闭了某个节点,这就允许RS能安全适当的关闭region,然后告诉HMaster,正在关闭。这种情况下,commit log会被清除,然后HMaster会立刻安排region的重新分配。
另外的regionserver关闭的的情况:比如运行RS的计算机静默死亡(silent death),比如网络原因。导致region server不能够发出告警,这种情况有Zookeeper进行处理。 每一个RS都会连接到Zookeeper上面,而Master监测这些连接,Zookeeper自己管理heartbeat。所以timeout出现的时候,Hmaster会申明region server 已经死亡,启动一个恢复处理过程
恢复正在进行中的写操作。
当一个RS 出现了宕机,那么commit logs的恢复工作就发生了。这个恢复工作是并行开始的。
第一步:随机的RS会提取commit logs(从设置好的commit log 目录中),并且按照region来分割日志成多个文件,保存在HDFS上面,然后region会被重新分配给其他任意的RS,
然后每一个被分配的RS的都会去读取已经前面分割好的对应的region 日志文件,并且进行将该region恢复到正确状态。
当出问题的是一个node 失败了,而不是一个进程崩溃了,那么问题就会出现了。
出现进程崩溃的的regionserver,会将数据写入到处于同一台机上面的datanode上面。假设副本因素为3,那么当一个node丢失的时候,你丢失的不仅仅是一个region server,并且还有这个数据的一个副本。 进行split,就意味着要读取block,而1/3的副本已经死了,那么对于每一个block,你会有三分之一的几率被引导到错误的副本上面。还有,split操作需要创建新的文件。每一个文件都会有3个副本,这些副本可能会被指派给已经丢失的datanode,而这个write数据的过程由于datanode已经死亡,会在经过一个timeout之后失败,而重新回转到另外一个datanode上面,这就延缓了数据的恢复的过程。
重新分配region
分配region的工作会进行的很快,这依赖于Zookeeper,需要通过Zookeeper完成master和region server的异步工作。
MTTR带来的提升(The MTTR improvements)
检测失败节点:
首先,可以减小默认的timeout时间。Hbase 被配置成3分钟的Zookeepertimeout时间,这就保证了GC不会介入进来(GC parse会导致ZK的timeout,会导致错误的failure检测。)
对于生产系统,关注MTTR的话,设置timeout时间为1分钟,或者30秒,是很有必要的。
一个合理的最小设置时20秒。所以你可以设置hbase.zookeeper.timeout=60000
你同样设置你GC(incremental, generational GC with good figures for the young and old generations, etc., this is a topic by itself) ,这样使GC的暂停时间不要超过ZK timeout。
恢复正在进行中的写操作。
在正常情况下,会有足够的活跃的RS来并行的完成commit log files的split工作,所以问题的就转到能否直接找到HDFS上面仅存的副本。该问题的解决方案是,配置HDFS,是hdfs对于故障的检测快于hbase的检测。那就是说,如果hbase的timeout为60s,HDFS应该设置成20s(就是设置成20s之后就认为node已经死亡)。在这里我们要描述一下HDFS是如何处理dead node。HDFS的故障检测也同样是依赖于heartbeat和timeout,在HDFS中,如果一个node被申明为dead,那么保存在该datanode上面的replicas将会被复制到其他活跃的datanode上面去,而这个是一个消耗很大的过程,并且,如果多个datanode同事死亡,那么这就会引发“replication storms”,replication storms指所有的副本被重新拷贝,这会加剧系统负载,从而导致某些节点不响应,进而导致这些节点被NN视为已经死亡,而这些节点上面block又要重新被复制,周而复始,这样的replication storms实在是可怕了。
因此,HDFS在开始恢复过程之前会等待一段时间,会比10分钟长一点。而这一点,对于低延时系统来说就是一个问题:访问dead datanode就会促发timeout。在HDFS versions 1.0.4 or 1.2, and branches 2 and 3中,引入一个新的状态:stale。当一个datanode不再发送hearbeat,并且这个时间持续到一个指定的时间,那么datanode处于stale状态。 处于stale状态的节点就是在读写过程中最后一个选择(a last resort for reads),所以启用这种性质,会是恢复更加快。
设置启用stale的方法:修改hdfs-site.xml
dfs.namenode.avoid.read.stale.datanode
true
dfs.namenode.avoid.write.stale.datanode
true
dfs.namenode.write.stale.datanode.ratio
1.0f
dfs.namenode.check.stale.datanode
true
注:我在cloudera cdh-4.1.2 提供的doc的配置项目文档中没有找到该参数,看了源码才找到这些参数。
具体参考HDFS-3912, HDFS-4350:
重新分配region
Region分配的是纯粹hbase的内部实现,在Hbase 0.94+的版本中,region 分配过程的处理被优化了,允许在很短的时间异步分贝更多的region。
具体可以参考[example - Apache jira HBASE-7247].
结论。
在Hbase中没有global failure,如果一个region server 失败了,其他的region server 仍然可用,对于给定的一个数据集,MTTR 通常是 10分钟左右。
该经验数值是从是从比较普遍的情况是中得到的,因为需要使用dead datanode的节点上面副本数,恢复就需要时间。HDFS需要花费10分钟的时间来申明datanode死亡了。 当引入了stale状态的时候,这就不再是一个问题了。这个时候恢复时间就变成Hbase本身的问题,如果你需要考虑MTTR,那么你采用这里的设置,从节点真的出现失败,到数据在其他RS上面又重新变得可用,这个过程只需要2分钟,或者更少。
引用计数
netty中使用引用计数机制来管理资源,当一个实现ReferenceCounted的对象实例化时,引用计数置1.
客户代码中需要保持一个该对象的引用时需要调用接口的retain方法将计数增1.对象使用完毕时调用release将计数减1.
当引用计数变为0时,对象将释放所持有的底层资源或将资源返回资源池.
内存泄露
按上述规则使用Direct和Pooled的ByteBuf尤其重要.对于DirectBuf,其内存不受VM垃圾回收控制只有在调用release导致计数为0时才会主动释放内存,而PooledByteBuf只有在release后才能被回收到池中以循环利用.
如果客户代码没有按引用计数规则使用这两种对象,将会导致内存泄露.
内存使用跟踪
在netty.io.util包中含有如下两个类
ResourceLeak 用于跟踪内存泄露
ResourceLeakDetector 内存泄露检测工具
在io.netty.buffer.AbstractByteBufAllocator类中有如下代码
[java] view plaincopy在CODE上查看代码片派生到我的代码片
//装饰器模式,用SimpleLeakAwareByteBuf或AdvancedLeakAwareByteBuf来包装原始的ByteBuf
//两个包装类均通过调用ResourceLeak的record方法来记录ByteBuf的方法调用堆栈,区别在于后者比前者记录更多的内容
protected static ByteBuf toLeakAwareBuffer(ByteBuf buf) {
ResourceLeak leak;
//根据设置的Level来选择使用何种包装器
switch (ResourceLeakDetector.getLevel()) {
case SIMPLE:
//创建用于跟踪和表示内容泄露的ResourcLeak对象
leak = AbstractByteBuf.leakDetector.open(buf);
if (leak != null) {
//只在ByteBuf.order方法中调用ResourceLeak.record
buf = new SimpleLeakAwareByteBuf(buf, leak);
}
break;
case ADVANCED:
case PARANOID:
leak = AbstractByteBuf.leakDetector.open(buf);
if (leak != null) {
//在ByteBuf几乎所有方法中调用ResourceLeak.record
buf = new AdvancedLeakAwareByteBuf(buf, leak);
}
break;
}
return buf;
}
下图展示了该方法被调用的时机.可见Netty只对PooledByteBuf和DirectByteBuf监控内存泄露.
内存泄露检测
下面观察上述代码中的AbstractByteBuf.leakDetector.open(buf);
实现代码如下
[java] view plaincopy在CODE上查看代码片派生到我的代码片
//创建用于跟踪和表示内容泄露的ResourcLeak对象
public ResourceLeak open(T obj) {
Level level = ResourceLeakDetector.level;
if (level == Level.DISABLED) {//禁用内存跟踪
return null;
}
if (level.ordinal() < Level.PARANOID.ordinal()) {
//如果监控级别低于PARANOID,在一定的采样频率下报告内存泄露
if (leakCheckCnt ++ % samplingInterval == 0) {
reportLeak(level);
return new DefaultResourceLeak(obj);
} else {
return null;
}
} else {
//每次需要分配 ByteBuf 时,报告内存泄露情况
reportLeak(level);
return new DefaultResourceLeak(obj);
}
}
其中reportLeak方法中完成对内存泄露的检测和报告,如下面代码所示.
[java] view plaincopy在CODE上查看代码片派生到我的代码片
private void reportLeak(Level level) {
//......
// 报告生成了太多的活跃资源
int samplingInterval = level == Level.PARANOID? 1 : this.samplingInterval;
if (active * samplingInterval > maxActive && loggedTooManyActive.compareAndSet(false, true)) {
logger.error("LEAK: You are creating too many " + resourceType + " instances. " +
resourceType + " is a shared resource that must be reused across the JVM," +
"so that only a few instances are created.");
}
// 检测并报告之前发生的内存泄露
for (;;) {
@SuppressWarnings("unchecked")
//检查引用队列(为什么通过检查该队列,可以判断是否存在内存泄露)
DefaultResourceLeak ref = (DefaultResourceLeak) refQueue.poll();
if (ref == null) {//队列为空,没有未报告的内存泄露或者从未发生内存泄露
break;
}
//清理引用
ref.clear();
if (!ref.close()) {
continue;
}
//通过错误日志打印资源的方法调用记录,并将其保存在reportedLeaks中
String records = ref.toString();
if (reportedLeaks.putIfAbsent(records, Boolean.TRUE) == null) {
if (records.isEmpty()) {
logger.error("LEAK: {}.release() was not called before it's garbage-collected. " +
"Enable advanced leak reporting to find out where the leak occurred. " +
"To enable advanced leak reporting, " +
"specify the JVM option '-D{}={}' or call {}.setLevel()",
resourceType, PROP_LEVEL, Level.ADVANCED.name().toLowerCase(), simpleClassName(this));
} else {
logger.error(
"LEAK: {}.release() was not called before it's garbage-collected.{}",
resourceType, records);
}
}
}
}
综合上面的三段代码,可以看出, Netty 在分配新 ByteBuf 时进行内存泄露检测和报告.
DefaultResourceLeak的声明如下
[java] view plaincopy在CODE上查看代码片派生到我的代码片
private final class DefaultResourceLeak extends PhantomReference
//......
public DefaultResourceLeak(Object referent) {
//使用一个静态的引用队列(refQueue)初始化
//refQueue是ResourceLeakDecetor的成员变量并由其初始化
super(referent, referent != null? refQueue : null);
//......
}
//......
}
可见DefaultResourceLeak是个”虚”引用类型,有别于常见的普通的”强”引用,虚引用完全不影响目标对象的垃圾回收,但是会在目标对象被VM垃圾回收时被加入到引用队列中.
在正常情况下ResourceLeak对象会所监控的资源的引用计数为0时被清理掉(不在被加入引用队列),所以一旦资源的引用计数失常,ResourceLeak对象会被加入到引用队列.例如没有成对调用ByteBuf的retain和relaease方法,导致ByteBuf没有被正常释放(对于DirectByteBuf没有及时释放内存,对于PooledByteBuf没有返回Pool),当引用队列中存在元素时意味着程序中有内存泄露发生.
ResourceLeakDetector通过检查引用队列来判断是否有内存泄露,并报告跟踪情况.
首先,从客户端考虑,其实就是要保证Region下线不可服务期间,读写请求能够在集群恢复后继续,具体可以采取如下措施:
1) 对于写端,可以将未写入成功的记录,添加到一个客户端缓存中,隔一段时间后交给一个后台线程统一重新提交一次;也可以通过 setAutoFlush(flase, false)保证提交失败的记录不被抛弃,留在客户端writeBuffer中等待下次writeBuffer满了后再次尝试提交,直到提交成功为止。
2)对于读端,捕获异常后,可以采取休眠一段时间后进行重试等方式。
3)当然,还可以根据实际情况合理调整hbase.client.retries.number和hbase.client.pause配置选项。
然后,从服务端考虑,需要分别针对Region Split和Region Balance进行解决:
1) 由于建表时,我们已经考虑到了数据在不同Region Server上的均匀分布,而且预先在不同Region Server上创建并分配了相同数目的Region,那么考虑到为了集群能够在实际线上环境下提供稳定的服务,可以选择关掉HBase的Region自动 Balance功能,当然关掉后可以选择在每天读写压力小的时候(如凌晨后)触发执行一次Balance操作即可。
2)接下 来,Region总是被创建,不能被复用的问题该如何解决呢?根本原因是rowkey中包含了timestamp字段,而每时每刻timestamp总是 向上增长的。但是,使用方确实需要能够根据timestamp字段进行顺序scan操作,因此,timestamp字段必须保留。据此,这里给出两种解决 思路:
一种常用方法是将表按照时间分表,例如按天进行分表,这样可以通过预先建表创建好Region分区,避免实 际读写过程中频 繁触发Region Split等过程,但是这一方法的缺点是每天需要预先建好表,而这一DDL过程可能出现问题进而导致读写出现问题,同时跨天时读写端也需要做出适应,调整 为读写新创建的表。
其实,我们可以换一种思路,通过修改表的rowkey结构,将timestamp字段改成一 个周期循环的 timestamp,如取timestamp % TS_MODE后的值,其中TS_MODE须大于等于表的TTL时间周期,这样才能保证数据不会被覆盖掉。经过这样改造后,即可实现Region的复用, 避免Region的无限上涨。对于读写端的变更也较小,读写端操作时只需将timestamp字段取模后作为rowkey进行读写,另外,读端需要考虑能 适应scan扫描时处理[startTsMode, endTsMode]和[endTsMode, startTsMode]两种情况。
可以啊 , 百个维度 算少的 维度了
期待吧
将任务 分批 , 批量 get API
ssh 配置
Spark读取HBase数据分析 并将结果存入HBase分析结果表 hbase只是数据
一.准备阶段
1.准备2套能正常运行的hbase集群(new cluster:222|oldcluster:226)
2.2套集群的hosts文件内容都需要包含对方的主机地址
3.zookeeper可以单独部署2个集群,也可用一个zookeeper集群管理2套hbase集群,就是不能用hbase自带的zookeeper集群做管理
4.hadoop、hbase等组件版本号保持一致
二.配置阶段
1.修改222集群的hbase-site.xml文件
添加如下内容:
hbase.replication
true
2.在222集群上添加peer
./hbase shell add_peer'1','172.16.205.226:2181:/hbase' 执行该命令会报错,但是不影响执行结果,如果不想让其有报错提示,可进入zookeeper将peerid删除,再执行此命令就行了
3.启动复制 ./hbase shellstart_replication 执行该命令也会报错,不予理会。若想看看状态是否被开启,同样进入zookeeper查看state
4.创建表 在两套集群创建同样的表(结构需要完全一样)
5.在226上添加replication属性,并刷新其结构
disable 'your_table'
alter 'your_table', {NAME =>'family_name', REPLICATION_SCOPE => '1'}
enable 'your_table'
6.测试数据同步
在226上put一条数据进hbase
222上将能在随后被scan到
Phoenix
第一步:MAVEN配置
 Â
org.apache.spark
spark-core_2.11
1.6.0
org.apache.spark
spark-mllib_2.11
1.6.0
 org.apache.spark
 spark-sql_2.11
  1.6.0
org.scala-lang
scala-library
2.11.0
org.scala-lang
scala-compiler
2.11.0
org.scala-lang
scala-reflect
2.11.0
第二步:Spring配置
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
classpath:jdbc.properties
classpath:spark.properties
第三步:新增属性文件 spark.properties
spark.master=local
spark.url=jdbc:mysql://192.168.0.202:3306/spark?useUnicode=true&characterEncoding=UTF-8
spark.table=testtable
spark.username=root
spark.password=mysql
第四步:写代码
/**
*
*/
package com.harleycorp.service.impl;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import javax.annotation.Resource;
import org.apache.log4j.Logger;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.sql.DataFrame;
import org.apache.spark.sql.SQLContext;
import org.apache.spark.sql.SaveMode;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import com.harleycorp.pojo.SparkUser;
import com.harleycorp.service.ISparkUpperService;
/**
* @author kevin
*
*/
@Service
public class SparkUpperServiceImpl implements ISparkUpperService {
private Logger logger =Logger.getLogger(SparkUpperServiceImpl.class);
@Value("${spark.master}")
public String master ; // = "local"
@Value("${spark.url}")
public String url ;//= "jdbc:mysql://192.168.0.202:3306/spark?useUnicode=true&characterEncoding=UTF-8";
@Value("${spark.table}")
public String table ; //= "testtable"
@Value("${spark.username}")
public String username ;// = "root";
//@Value("${spark.password}")
public String password = "mysql";
@Resource
public SQLContext sqlContext;
@Resource
public JavaSparkContext sc;
public Properties getConnectionProperties(){
Properties connectionProperties = new Properties();
connectionProperties.setProperty("dbtable",table);
connectionProperties.setProperty("user",username);//数据库用户
connectionProperties.setProperty("password",password); //数据库用户密码
return connectionProperties;
}
public String query() {
logger.info("=======================this url:"+this.url);
logger.info("=======================this table:"+this.table);
logger.info("=======================this master:"+this.master);
logger.info("=======================this username:"+this.username);
logger.info("=======================this password:"+this.password);
DataFrame df = null;
//以下数据库连接内容请使用实际配置地址代替
df = sqlContext.read().jdbc(url,table, getConnectionProperties());
df.registerTempTable(table);
String result = sqlContext.sql("select * from testtable").javaRDD().collect().toString();
logger.info("=====================spark mysql:"+result);
return result;
}
public String queryByCon(){
logger.info("=======================this url:"+this.url);
logger.info("=======================this table:"+this.table);
logger.info("=======================this master:"+this.master);
logger.info("=======================this username:"+this.username);
logger.info("=======================this password:"+this.password);
DataFrame df = sqlContext.read().jdbc(url, table, new String[]{"password=000000"}, getConnectionProperties());
String result = df.collectAsList().toString();
logger.info("=====================spark mysql:"+result);
return null;
}
public void add(){
List list = new ArrayList();
SparkUser us = new SparkUser();
us.setUsername("kevin");
us.setPassword("000000");
list.add(us);
SparkUser us2 = new SparkUser();
us2.setUsername("Lisa");
us2.setPassword("666666");
list.add(us2);
JavaRDD personsRDD = sc.parallelize(list);
DataFrame userDf = sqlContext.createDataFrame(personsRDD, SparkUser.class);
userDf.write().mode(SaveMode.Append).jdbc(url, table, getConnectionProperties());
}
}
第五步:junit调用
package com.harleycorp.testmybatis;
import javax.annotation.Resource;
import org.apache.log4j.Logger;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.harleycorp.service.ISparkUpperService;
@RunWith(SpringJUnit4ClassRunner.class) //表示继承了SpringJUnit4ClassRunner类
@ContextConfiguration(locations = {"classpath:spring-mybatis.xml"})
public class TestSpark {
private static Logger logger=Logger.getLogger(TestSpark.class);
@Resource
private ISparkUpperService sparkUpperService = null;
@Test
public void test1(){
sparkUpperService.query();
}
@Test
public void test2(){
sparkUpperService.add();
}
@Test
public void test3(){
sparkUpperService.queryByCon();
}
}
第六步:运行
权限呢
在哪里?配置
你应该说清楚 环境, 版本 ,问题 详细说清楚
springX