Joyber 发布的文章

扩展文件名: CDbBICommand.php ,将文件放于 \protected\components 目录下,确定项目会自动引入这个目录的文件,然后用法:
StatVisitor 是 CActiveRecord 类的数据表模型

$bi = new CDbBICommand(new StatVisitor());
$bi->batchInsert([['name'=>123],['name'=>234]]);

扩展文件代码如下:

<?php

/**
 * class for sql batch insert
 * eg:
 * $bi = new CDbBICommand(new StatVisitor());
 * $bi->batchInsert([['name'=>123],['name'=>234]]);
 */
class CDbBICommand {

    /**
     * CDbBICommand constructor.
     * @param CActiveRecord $acModel
     */
    public function __construct($acModel) {
        $this->ac = $acModel;
        $this->tablename = $this->ac->tableName();
        $this->db = $this->ac->getDbConnection();
        $this->command = $this->db->createCommand();
    }

    public function batchInsert($array_columns) {
        $sql    = '';
        $params = array();
        $i      = 0;
        foreach ($array_columns as $columns) {
            $names        = array();
            $placeholders = array();
            foreach ($columns as $name => $value) {
                if (!$i) {
                    $names[] = $this->db->quoteColumnName($name);
                }
                if ($value instanceof CDbExpression) {
                    $placeholders[] = $value->expression;
                    foreach ($value->params as $n => $v) {
                        $params[$n] = $v;
                    }
                } else {
                    $placeholders[]           = ':' . $name . $i;
                    $params[':' . $name . $i] = $value;
                }
            }
            if (!$i) {
                $sql = 'INSERT INTO ' . $this->db->quoteTableName($this->tablename)
                       . ' (' . implode(', ', $names) . ') VALUES ('
                       . implode(', ', $placeholders) . ')';
            } else {
                $sql .= ',(' . implode(', ', $placeholders) . ')';
            }
            $i++;
        }
        return $this->command->setText($sql)->execute($params);
    }
}

框架自带的批量插入方法,很少有网络资料:

$builder = Yii::app()->db->schema->commandBuilder; // 创建builder对象
$command = $builder->createMultipleInsertCommand('{{umeng_message}}', array( // umeng_message为数据库表
    array(// 格式为:'字段' => '值', 不包括主键ID,一个array为一条记录
        'msg_id' => $android['data']['task_id'],
        'detail' => $android['data']['json'],
        'msg_detail_id_fk' => $detailId
    ),
    array(
        'msg_id' => $android['data']['task_id'],
        'detail' => $android['data']['json'],
        'msg_detail_id_fk' => $detailId
    ),
));
$command->execute(); // 执行成功返回true

转的别人的文章:https://blog.csdn.net/weixin_40735752/article/details/88077362
在一主一从或一主多从的mysql架构中,当主库不可用时,需要及时切换到从库,那么,如何判断主库是否可用?

通过select 1来判断
方案
在sql中执行"select 1",如果失败,则认为sql服务不可用。

优点
简单,速度快

缺点
只能检测sql服务器进程是否存在,并不能真正识别服务的可用性。
比如,当innodb_thread_concurrency设置过小时(比如=1),大部分查询可能因为需要排队等待而无法实时响应时,select 1反而可以实时响应。

通过实际的查表语句来检测
方案
在系统库mysql库中创建一个 health_check表,并且里面只放一行数据,然后定期执行"select * from mysql.health_check"

优点
简单,可以检测出因为并发线程过多而导致的数据库不可用的情况。

缺点
由于只采用读来检测,所以类似磁盘满而导致的服务不可用问题,是无法检测出来的。

通过更新表来检测
方案
简单地改进上一种方法,通过更新表来实现可用性检测。
update mysql.health_check set t_modified = now();
上述语句执行时,会写binlog文件,如果磁盘满时,执行会失败,因此,可以检测出磁盘不可用等io问题。

缺点
在主从的mysql结构里面,如果主备关系是双M结构,这时如果在备库也执行这个命令,就会出现主备冲突,导致主备同步停止。

改进
在health_check表中创建两列,一列是id,一列是t_modified_time,每个服务器只update id=自己的serverid的行,这样就可以 保证主备库各自的检测命令不冲突。

改进后的缺点
改进后的更新表方案已经相对比较完善了,但是还是有些问题,主要的问题是可能出现“判定慢”。当服务器由于资源紧张时,大部分复杂的查询更新语句可能实质上已经超时,但是由于检测语句相对比较简单,可能不会超时(或者有时候超时,有时候会成功),因此出现判定慢或者判定不准确的问题。

通过sql内部的性能数据来检测可用性
方案
通过统计mysql的每一次io请求的时间,来判定服务是否可用。
mysql 5.6版本以后提供了performance_schema库,在file_summary_by_event_name表里面统计了每次io请求的时间。
performance_schema是可选项,全部打开性能统计会影响mysql的性能,大概下降10%左右。因此只需要enable少数需要的项进行统计。
比如打开 redo log的时间监控,可以执行:
update setup_instruments set ENABLED=‘YES’, Timed = ‘YES’ where name like ‘%wait/io/file/innodb/innodb_log_file%’;
假设已经打开了redo log和binlog这两个统计信息,接下来就是检测是否存在每次IO请求超过200ms的事件:
select event_name, MAX_TIMER_WAIT from performance_schema.file_summary_by_event_name where event_name in (‘wait/io/file/innodb/innodb_log_file’, ‘wait/io/file/sql/binlog’) and MAX_TIMER_WAIT > 200 * 100010001000;
发现异常以后,可以读取需要的信息,然后通过以下语句清空之前的统计信息,以便监控后续可能出现的异常:
truncate table performance_shema.file_summary_by_event_name;

优点
比较可靠

缺点
太复杂
————————————————
版权声明:本文为CSDN博主「greensea669」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_40735752/java/article/details/88077362

脚本摘自:https://www.runoob.com/python/python-email.html

#!/usr/bin/python
# -*- coding: UTF-8 -*-
 
import smtplib
from email.mime.text import MIMEText
from email.utils import formataddr
 
my_sender='xxx@qqvbc.com'    # 发件人邮箱账号
my_pass = 'xxx@'              # 发件人邮箱密码
my_user='xxx@qq.com'      # 收件人邮箱账号,我这边发送给自己
def mail():
    ret=True
    try:
        msg=MIMEText('填写邮件内容','plain','utf-8')
        msg['From']=formataddr(["FromRunoob",my_sender])  # 括号里的对应发件人邮箱昵称、发件人邮箱账号
        msg['To']=formataddr(["FK",my_user])              # 括号里的对应收件人邮箱昵称、收件人邮箱账号
        msg['Subject']="菜鸟教程发送邮件测试"                # 邮件的主题,也可以说是标题
 
        server=smtplib.SMTP_SSL("smtp.exmail.qq.com", 465)  # 发件人邮箱中的SMTP服务器,端口是25
        server.login(my_sender, my_pass)  # 括号中对应的是发件人邮箱账号、邮箱密码
        server.sendmail(my_sender,[my_user,],msg.as_string())  # 括号中对应的是发件人邮箱账号、收件人邮箱账号、发送邮件
        server.quit()  # 关闭连接
    except Exception:  # 如果 try 中的语句没有执行,则会执行下面的 ret=False
        ret=False
    return ret
 
ret=mail()
if ret:
    print("邮件发送成功")
else:
    print("邮件发送失败")

方法 1 – 使用反斜杠:\command
输入以下命令暂时绕过名为 mount 的 bash 别名:\mount

方法 2 – 使用引号: “command” 或 ‘command’
如下引用 mount 命令调用实际的 /bin/mount:”mount”
这里单引号和双引号都可以。

方法 3 – 使用命令的完全路径
使用完整的二进制路径,如 /bin/mount:

方法 4 – 使用内部命令 command
就是在执行的命令前加上 command 命令,样例如下:

command cmd
command cmd arg1 arg2
要绕过 .bash_aliases 中设置的别名,例如 mount:

command mount
command mount /dev/sdc /mnt/pendrive/
“command” 直接运行命令或显示关于命令的信息。它带参数运行命令,会阻止 shell 函数查询或者别名,或者显示有关给定命令的信息。

方法 5 – 使用 unalias 命令的说明
要从当前会话的已定义别名列表中移除别名,请使用 unalias 命令:

unalias mount
要从当前 bash 会话中删除所有别名定义:

unalias -a
如果要永久删除定义的别名,则必须删除别名的定义语句,确保你更新你的 ~/.bashrc 或 $HOME/.bash_aliases :

vi ~/.bashrc #或者执行 vi $HOME/.bash_aliases
想了解更多信息,参考这里的在线手册,或者输入下面的命令查看:

man bash
help command
help unalias
help alias

keepalived+nginx,实现nginx高可用,注意事项:1、VIP不需要在服务器网络配置文件中配置。2、nginx主不可用时,需要kill掉nginx主的keepalived服务,这样才可以实现VIP切换,因为主的keepalived优先级高。3、故障切换时发送邮件通知由nginx备的keepalived服务来实现。

其中nginx主上keepalived.conf配置为:

/etc/keepalived/keepalived.conf

! Configuration File for keepalived

global_defs {
#标识本节点的名称
   router_id master
}

vrrp_script chk_nginx {  
    script "/etc/keepalived/nginx_check.sh"
    #每2秒检测一次nginx的运行状态
    interval 2
    #失败一次,将自己的优先级调整为-20
    weight    -20
} 

vrrp_instance VI_1 {
    #状态,主节点为MASTER
    state MASTER
    #绑定VIP的网络接口
    interface ens33
    #虚拟路由的ID号,两个节点设置必须一样
    virtual_router_id 51
    #节点优先级,值范围0~254,MASTER>BACKUP
    priority 100
    #组播信息发送时间间隔,两个节点必须设置一样,默认为1秒
    advert_int 1
    #设置验证信息,两个节点必须一致
    authentication {
            auth_type PASS
            auth_pass 1111
        }

    #虚拟IP,两个节点设置必须一致,可以设置多个
    virtual_ipaddress {
            192.168.1.106
        }
        #nginx存活状态检测脚本
    track_script {
        chk_nginx
    }
}

其中调用的/etc/keepalived/nginx_check.sh脚本内容为:

#!/bin/bash  
A=`ps -C nginx -no-header |wc -l`
if [ $A -eq 1 ];then
        pkill keepalived
fi

nginx备上keepalived.conf配置为:

! Configuration File for keepalived

global_defs {
        #标识本节点的名称
        router_id backup
}

vrrp_instance VI_1 {
        #状态,备节点为BACKUP
        state BACKUP
        #绑定VIP的网络接口
        interface ens33
        #虚拟路由的ID号,两个节点设置必须一样
        virtual_router_id 51
        #节点优先级,值范围0~254,MASTER>BACKUP
        priority 99
        #组播信息发送时间间隔,两个节点必须设置一样,默认为1秒
        advert_int 1
        #设置验证信息,两个节点必须一致
        authentication {
                auth_type PASS
                auth_pass 1111
        }

        #节点变为master时执行
        notify_master "/usr/bin/python /root/send_mail_ssl.py backup 192.168.1.232 192.168.1.235"

        #虚拟IP,两个节点设置必须一致,可以设置多个
        virtual_ipaddress {
                192.168.1.106
        }

}

里边调用的send_mail.sh脚本为使用Perl编写的,需要安装环境:

yum -y install perl-CPAN
cpan Net::SMTP_auth

send_mail.sh脚本内容为:

#!/usr/bin/python
# -*- coding: UTF-8 -*-
 
import smtplib, sys, time, subprocess
from email.mime.text import MIMEText
from email.utils import formataddr
 
my_sender='xxx@qqvbc.com'    # 发件人邮箱账号
my_pass = 'xxx@'              # 发件人邮箱密码
my_user='xxx@qq.com'      # 收件人邮箱账号,我这边发送给自己

def print_help():
    note = '''python script.py role ip vip
    '''
    print(note)
    exit(1)

time_stamp = time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time()))

p = subprocess.Popen('hostname', shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
hostname = p.stdout.readline().split('\n')[0]

if len(sys.argv) != 4:
    print_help()
elif sys.argv[1] == 'master':
    message_content = '%s server: %s(%s) change to Master, vIP: %s' %(time_stamp, sys.argv[2], hostname, sys.argv[3])
    subject = '%s change to Master -- keepalived notify' %(sys.argv[2])
elif sys.argv[1] == 'backup':
    message_content = '%s server: %s(%s) change to Backup, vIP: %s' %(time_stamp, sys.argv[2], hostname, sys.argv[3])
    subject = '%s change to Backup -- keepalived notify' %(sys.argv[2])
else:
    print_help()

def mail():
    ret=True
    try:
        msg=MIMEText(message_content,'plain','utf-8')
        msg['From']=formataddr(["Server Auto Notify",my_sender])  # 括号里的对应发件人邮箱昵称、发件人邮箱账号
        msg['To']=formataddr(["SA",my_user])              # 括号里的对应收件人邮箱昵称、收件人邮箱账号
        msg['Subject']="服务器状态切换通知"                # 邮件的主题,也可以说是标题
 
        server=smtplib.SMTP_SSL("smtp.exmail.qq.com", 465)  # 发件人邮箱中的SMTP服务器,端口是25
        server.login(my_sender, my_pass)  # 括号中对应的是发件人邮箱账号、邮箱密码
        server.sendmail(my_sender,[my_user,],msg.as_string())  # 括号中对应的是发件人邮箱账号、收件人邮箱账号、发送邮件
        server.quit()  # 关闭连接
    except Exception:  # 如果 try 中的语句没有执行,则会执行下面的 ret=False
        ret=False
    return ret
 
ret=mail()
if ret:
    #print("邮件发送成功")
    sys.exit(0)
else:
    #print("邮件发送失败")
    sys.exit(1)