转载:Redis 的 KEYS 命令引起 RDS 数据库雪崩

最近的互联网线上事故发生比较频繁,9月19日网上爆料出顺丰近期发生了一起线上删库事件,在这里就不介绍了。

在这里讲述一下最近发生在我公司的事故,以及如何避免,并且如何处理优化。 该宕机的直接原因是使用 Redis 的 keys * 命令引起的,一共造成了某个服务化项目的两次宕机。

间接原因还有很多,技术跟不上业务的发展,由每日百万量到千万级是一个大的跨进,公司对于系统优化的处理优先级不高,技术开发人手的短缺。
第一次宕机

2018年9月13日的某个点,公司某服务化项目的 RDS 实例连接飙升,CPU 升到 100%,拒绝了其他应用的所有请求服务。

整个过程如下:

阅读更多

详解用 MiniFramework 计算程序运行时间的方法

我们在项目调优过程中,通常会对代码的运行时间进行统计,以便了解程序运行的性能和效率,这些统计结果将作为代码优化时的重要指标,帮助开发者有针对性的进行调优工作。

MiniFramework 在 1.3.0 版本中,新增了 Debug 类,其中包含有时间统计功能的若干方法,可以非常便捷地帮助开发者实现上述统计需求,下面我们来通过示例代码介绍具体实现方法。

首先,假设我们有一个名为 Index 的 Controller,并且其中包含有一个名为 index 的 Action(MiniFramework下载包中已经包含),我们将代码写在这个 Action 中,如下:

阅读更多

Golang 开发 Socket 通信时常用的 TCP 封包和解包协议

在开发 Socket 通信时,由于 TCP 协议的特性,在网络状况不佳的情况下,数据传输过程中经常会出现半包或粘包。为解决这一问题,通常我们需要自定义一个通信协议,增加一个 HEADER 部分,并在其中对数据包的长度进行声明,下面分享一段封包和解包的示例代码,可用于 Golang 开发 Socket 时处理数据传输,具体代码如下:

package protocol
 
import (
    "bytes"
    "encoding/binary"
)
 
const (
    TCP_HEADER     = "TCPHEADER"
    TCP_HEADER_LEN = 9
    TCP_DATA_LEN   = 4
)
 
// 封包
func Enpack(msg []byte) []byte {
    return append(append([]byte(TCP_HEADER), IntToBytes(len(msg))...), msg...)
}
 
// 解包
func Depack(buffer []byte, readerChannel chan []byte) []byte {
    length := len(buffer)
 
    var i int
    for i = 0; i < length; i++ {
        if length < i + TCP_HEADER_LEN + TCP_DATA_LEN {
            break
        }
        if string(buffer[i:i + TCP_HEADER_LEN]) == TCP_HEADER {
            msgLen := BytesToInt(buffer[i + TCP_HEADER_LEN : i + TCP_HEADER_LEN + TCP_DATA_LEN])
            if length < i + TCP_HEADER_LEN + TCP_DATA_LEN + msgLen {
                break
            }
            data := buffer[i + TCP_HEADER_LEN + TCP_DATA_LEN : i + TCP_HEADER_LEN + TCP_DATA_LEN + msgLen]
            readerChannel <- data
 
            i += TCP_HEADER_LEN + TCP_DATA_LEN + msgLen - 1
        }
    }
 
    if i == length {
        return make([]byte, 0)
    }
    
    return buffer[i:]
}
 
// 整形转换成字节
func IntToBytes(n int) []byte {
	x := int32(n)
    bytesBuffer := bytes.NewBuffer([]byte{})
    binary.Write(bytesBuffer, binary.BigEndian, x)
    
    return bytesBuffer.Bytes()
}
 
// 字节转换成整形
func BytesToInt(b []byte) int {
    bytesBuffer := bytes.NewBuffer(b)
    var x int32
    binary.Read(bytesBuffer, binary.BigEndian, &x)
    
    return int(x)
}

 

深入研究 PHP 的 SESSION 阻塞问题

最近在一个基于 Web 的 IM 项目中,我采用异步向服务器发起请求拉取最新的聊天内容,服务器端通过 PHP 处理拉取请求,拉取过程是用 10 次循环查询数据库是否有最新的聊天内容。如发现新内容,则立即向浏览器输出,并结束掉本次请求的进程。在这 10 次的循环中,每次查询数据库后,均通过 Sleep 函数让进程暂停 1 秒,那么这个 PHP 进程可能会在服务器端保持 10 秒。

在测试过程中,我发现当这个拉取请求运行期间,其他向服务器端 PHP 发起的请求,均受到影响,响应变的非常慢。

经过一系列的排查,问题始终得不到解决,但当把代码中涉及到 SESSION 的部分全部跳过时,情况发生了变化,所有 PHP 进程都恢复正常的响应速度了。由此,联想到问题可能出在了 SESSION 阻塞机制上了。

阅读更多

部署 Keepalived 实现 MySQL 双主高可用架构

1.部署 MySQL 双主(Master – Master)集群

参考我的博文:《MySQL 双主 Master to Master 架构部署方法》

地址:http://www.sunbloger.com/2018/08/16/604.html

假设已经在 IP 为 192.168.0.1 和 192.168.0.2 的两台主机上部署好了 MySQL 双主集群,接下来我们进行 Keepalived 的部署。

阅读更多

冒泡排序法 for Golang 写法

代码如下:

// main
package main

import (
	"fmt"
)

func main() {
	val := []int{7, 3, 8, 1, 0, 2, 5, 9, 6, 4}
	fmt.Println(val)

	// 冒泡排序
	for i := 0; i < len(val)-1; i++ {
		for j := i + 1; j < len(val); j++ {
			if val[i] > val[j] {
				val[i], val[j] = val[j], val[i]
			}
		}
	}

	fmt.Println(val)
}

阳光部落原创,更多内容请访问http://www.sunbloger.com/

MySQL 双主 Master to Master 架构部署方法

为了演示部署过程,我通过 VMware 创建了两台主机,分别为 TestServer1(192.168.0.1) 和 TestServer2(192.168.0.2)。两台主机均安装了 CentOS 6.9。

MySQL 通过源码进行编译,我选择的是 mysql-5.6.41.tar.gz 这个版本的源码包。

编译安装过程如下:

创建 MySQL 的用户组和用户

# groupadd mysql
# useradd -g mysql mysql

阅读更多

用 MiniFramework 实现文件上传功能

MiniFramework 是一款遵循 Apache2 开源协议发布的,支持 MVC 和 RESTful 的超轻量级 PHP 开发框架。

在 MiniFramework 最新的版本中,提供了上传文件的特性,下面我们来演示一下实现过程。

首先,新建一个名为 Upload 的 Controller,并在其中新建一个名为 index 的 Action,代码如下:

<?php
namespace App\Controller;

use Mini\Upload;

class Upload extends Action
{
    function indexAction()
    {
        if (! empty($_FILES)) {
            
            // 实例化 Upload 类
            $upload = new Upload();
            
            $res = $upload->save($_FILES['f']);
            if (! $res) {
                $errmsg = $upload->getErrorMsg();
                echo $errmsg;
            } else {
                dump($res);
            }
        }
        
        $this->view->display();
    }
}

上边的代码中,通过 use Mini\Upload; 引入了 MiniFramework 框架所提供的一个用于文件上传的类库。

在实例化 Upload 类后,通过调用 save 方法,并将 PHP 的 $_FILES 传入,来实现上传文件的保存。

在调用 save 方法后,若文件保存失败,可以通过 getErrorMsg 方法来获取错误信息。

如果文件保存成功,那么 save 方法会返回一个数组,其中包含有文件保存的路径和文件名,上边的示例代码中使用了 MiniFramework 内置的全局函数 dump() 来输出 save 所返回的数组。

另外,在实例化 Upload 类时,可传入一个数组类型的参数,对文件保存路径、大小和类型进行设定,例如:

// 配置数组
$config = array(

    // 文件保存的根目录
    'rootPath'  => PUBLIC_PATH . '/uploads',

    // 文件的大小限制(单位:Byte)
    'maxPath'   => 512000,

    // 允许的类型
    'allowType' => 'bmp,gif,jpg,jpeg,png'

);

// 实例化 Upload 类时,将配置数组作为参数传入
$upload = new Upload($config);

上边的代码中,常量 PUBLIC_PATH 是 MiniFramework 内置用于定义站点根目录所对应的路径,默认对应的是 Public 目录所在的路径

接下来创建视图,在 View 目录中创建一个名为 upload 的目录,将视图文件保存到 View/upload/index.php,代码如下:

<h1>Upload</h1>
<form method="post" enctype="multipart/form-data">
  <input type="file" name="f" >
  <input type="submit" value="upload" /></p>
</form>

完成上述步骤后,便可以访问类似下面的地址来测试了
http://你的域名/index.php?c=upload&a=index

如果你的站点开启了伪静态,那么访问地址可以是下面这样:
http://你的域名/upload/index

上边的 URL 中,最后的 index 可以省略,例如:
http://你的域名/upload

阳光部落原创,更多内容请访问http://www.sunbloger.com/

解决 PHP 设置 open_basedir 与 eAccelerator 冲突问题

为 PHP 设置了 open_basedir 后,发现使用 require 和 include 始终会报出下面这样的 Warning 信息:

Warning: require(): open_basedir restriction in effect. File() is not within the allowed path(s)

这个 Warning 信息中,require 和 File 的括号中都是空白没有内容的,经过反复检查代码,确认所引用的路径在 open_basedir 允许的范围内。

通过去国外网站搜索资料,发现是因为 open_basedir 与 eAccelerator 冲突造成的。

需要重新编译一次 eAccelerator,编译时需要带上 –without-eaccelerator-use-inode 参数。

编译完重新启动 php-fpm 并删除之前的 eAccelerator 缓存文件即可。

阳光部落原创,更多内容请访问http://www.sunbloger.com/