Redis 变慢解决常用办法

1. 获取 Redis 实例在当前环境下的基线性能。
./redis-cli --intrinsic-latency 120 该命令会打印 120 秒内监测到的最大延迟

2. 是否用了慢查询命令?如果是的话,就使用其他命令替代慢查询命令,或者把聚合计算命令放在客户端做。
a. 用其他高效命令代替。比如说,如果你需要返回一个 SET 中的所有成员时,不要使用 SMEMBERS 命令,而是要使用 SSCAN 多次迭代返回,避免一次返回大量数据,造成线程阻塞
b. 当你需要执行排序、交集、并集操作时,可以在客户端完成,而不要用 SORT、SUNION、SINTER 这些命令,以免拖慢 Redis 实例。

3. 是否对过期 key 设置了相同的过期时间?对于批量删除的 key,可以在每个 key 的过期时间上加一个随机数,避免同时删除。

4. 是否存在 bigkey? 对于 bigkey 的删除操作,如果你的 Redis 是 4.0 及以上的版本,可以直接利用异步线程机制减少主线程阻塞;如果是 Redis 4.0 以前的版本,可以使用 SCAN 命令迭代删除;对于 bigkey 的集合查询和聚合操作,可以使用 SCAN 命令在客户端完成。

5. Redis AOF 配置级别是什么?业务层面是否的确需要这一可靠性级别?如果我们需要高性能,同时也允许数据丢失,可以将配置项 no-appendfsync-on-rewrite 设置为 yes,避免 AOF 重写和 fsync 竞争磁盘 IO 资源,导致 Redis 延迟增加。当然, 如果既需要高性能又需要高可靠性,最好使用高速固态盘作为 AOF 日志的写入盘。

6. Redis 实例的内存使用是否过大?发生 swap 了吗?如果是的话,就增加机器内存,或者是使用 Redis 集群,分摊单机 Redis 的键值对数量和内存压力。同时,要避免出现 Redis 和其他内存需求大的应用共享机器的情况。

7. 在 Redis 实例的运行环境中,是否启用了透明大页机制?如果是的话,直接关闭内存大页机制就行了。

8. 是否运行了 Redis 主从集群?如果是的话,把主库实例的数据量大小控制在 2~4GB,以免主从复制时,从库因加载大的 RDB 文件而阻塞。

9. 是否使用了多核 CPU 或 NUMA 架构的机器运行 Redis 实例?使用多核 CPU 时,可以给 Redis 实例绑定物理核;使用 NUMA 架构时,注意把 Redis 实例和网络中断处理程序运行在同一个 CPU Socket 上。

10. 仔细检查下有没有恼人的“邻居”,具体点说,就是 Redis 所在的机器上有没有一些其他占内存、磁盘 IO 和网络 IO 的程序,比如说数据库程序或者数据采集程序。如果有的话,我建议你将这些程序迁移到其他机器上运行。

另外参考

发表在 redis | Redis 变慢解决常用办法已关闭评论

php 数组实现简单队列

<?php
class Que {
  //队列最大值
  protected $m = 10;
  //头指针
  protected $h = 0;
  //尾脂针
  protected $t = 0;
  //是否安全
  protected $isSave = false;

  function __construct($isSave = false)
  {
      $this->isSave = $isSave;
      $this->arr = array_fill(0, $this->m, null);
  }

  /**
   * $n >= -100
   * 满返回-999
   * 不满返回入队列后队列元素的数目
   * 
   */

  function in($n){
      if($this->isSave){
        static::lock('in');
      }

      if($n < -100){
        throw new \Exception("数据异常", 1);
      }
      
      if( !$this->canIn() ){
        return -999;
      }


      $this->t = $this->newInOffset();
      $this->arr[$this->t] = $n;

      return $this->getLength();
  }

  /**
   * 空返回-999
   * 非空返回出队列的值
   */
  function out(){
    if($this->isSave){
      static::lock('out');
    }

    if(!$this->canOut()){
        return -999;
    }
    
    $n = $this->arr[$this->h];
    unset($this->arr[$this->h]);
    $this->arr[$this->h] = null;
    $this->h = $this->newOutOffest();
    return $n;
  }

  /**
   *获取数组长度
   */
  function getLength(){
    if($this->isEmpty()) return 0;
    if($this->t < $this->h){
      $l = $this->m - $this->h + $this->t;
    }else{
      $l = $this->t - $this->h;
    }
    return $l + 1;
  }

  /**
   * 判断数组是否为空数组
   */
  function isEmpty(){
    return $this->h == $this->t && is_null($this->arr[$this->h]);
  }

  /**
   *获取入队后的尾指诊偏移量
   */
  function newInOffset(){
      if( $this->isEmpty() ){//是否是空队列
          return $this->t;
      }

      return ($this->t + 1) % $this->m;
  }

  /**
   *判断出队后头指诊的偏移量
   */
  function newOutOffest(){
    if(!$this->isEmpty() && $this->h == $this->t){
        return $this->h;
    }

    return ($this->h + 1) % $this->m;
  }

  /**
   *能否入队
   */
  function canIn(){
    return is_null($this->arr[$this->newInOffset()]);
  }

  /**
   *能否出队
   */
  function canOut(){
    return !is_null($this->arr[$this->h]);
  }

  /**
   *获取队列内容
   */
  function getQueList(){
    return $this->arr;
  }

  /**
   *加锁
   */
  public static function lock($name, $ex = 60)
  {
      if ($res = Redis::setnx($name, '1', 'NX')) {
          Redis::expire($name, $ex);

          register_shutdown_function(function () use ($name) {
              Que::unlock($name);
          });
      } else {
          throw new \Exception('执行中,请稍等');
      }
  }

  /**
   * 解锁
   */
  public static function unlock($name)
  {
      Redis::del($name);
  }
}

/**
$q = new Que;
for($i = 0; $i < 20; $i++){
    $r = $q->in($i);
    var_dump($r);
    if($i % 3 == 0){
      var_dump("out : ".$q->out());
    }
}

for($i = 0; $i < 20; $i++){
  var_dump("out : ".$q->out());
}
**/


发表在 php, php函数集, php小程序, 基础 | php 数组实现简单队列已关闭评论

dyld: Library not loaded: /usr/local/opt/readline/lib/libreadline.7.dylib 解决方案

dyld: Library not loaded: /usr/local/opt/readline/lib/libreadline.7.dylib
解决方案

ln -s /usr/local/opt/readline/lib/libreadline.dylib /usr/local/opt/readline/lib/libreadline.7.dylib

发表在 MAC | dyld: Library not loaded: /usr/local/opt/readline/lib/libreadline.7.dylib 解决方案已关闭评论

docker 设置时区

RUN apk add --no-cache tzdata
ENV TZ=Asia/Shanghai

发表在 docker | docker 设置时区已关闭评论

简单易用分布式锁

class PublicFunc{
    public static function lock($name, $ex = 60)
    {
        $val = uniqid();
        if ($redis->set($name, $val, 'NX', 'EX', $ex)) {
            register_shutdown_function(function () use ($name, $val) {
                PublicFunction::unlock($name, $val);
            });
        } else {
            throw new \Exception('执行中,请稍等');
        }
    }

    public static function unlock($name)
    {//释放锁需要先判断是否是自己持有的锁,如果是则释放,否则不释放,多不操作要求原子操作,所以需要lua脚本
        static $releaseLuaScript = <<<LUA
if redis.call("GET",KEYS[1])==ARGV[1] then
    return redis.call("DEL",KEYS[1])
else
    return 0
end
LUA;
        return $redis->eval($releaseLuaScript, 1, $name, $val);
    }
}
发表在 php, php 优化, php函数集 | 简单易用分布式锁已关闭评论

nginx 504 Gateway Time-out

方向代理增加超时时间
location ~ /api {
proxy_pass http://127.0.0.1:8081;
proxy_read_timeout 3600;//反向代理读取超时时间,默认60秒
}

发表在 nginx | nginx 504 Gateway Time-out已关闭评论

根据生日计算年龄



    public static function getAgeFromBirthDay($birthday)
    {
        $bday = new \DateTime($birthday); // 你的出生日

        $today = new \Datetime(date('Y-m-d'));

        $diff = $today->diff($bday);
        return $diff->y;
    }
发表在 php, php函数集, 基础 | 根据生日计算年龄已关闭评论

跨域访问设置方式

header('Access-Control-Allow-Origin: *');//允许跨域域名
header('Access-Control-Allow-Methods: GET, POST, PATCH, PUT, OPTIONS, DELETE');//允许跨域方式
header('Access-Control-Allow-Headers: *');//允许header
header('Access-Control-Expose-Headers: *');//某些header无法传递,须要指明header白名单(content-type 就在此列)
发表在 js, 前端, 设计模式, 运维 | 跨域访问设置方式已关闭评论

js ISO-8859-1 转中文

fetch 获取的 header 内容如果是中文(多子节),则会转译成 ISO-8859-1 编码格式,需要转编码转换

var str = “ç\u0088±æ\u0088\u0091ä¸\u00ADå\u009B½”;
var utfstring =decodeURI(escape(str))//先编码再解码
//或者 utfstring =decodeURIComponent(escape(str)) 

decodeURIComponent 和 decodeURIComponent 区别具体看这里。encodeURI 自身无法产生能适用于HTTP GET 或 POST 请求的URI,例如对于 XMLHTTPRequests, 因为 "&", "+", 和 "=" 不会被编码,然而在 GET 和 POST 请求中它们是特殊字符。然而encodeURIComponent这个方法会对这些字符编码
参考

发表在 js, 前端 | js ISO-8859-1 转中文已关闭评论

fetch 下载流文件

前后端分离的项目,前端大部分使用fetch调用接口,遇到下载的时候,服务器接口一般直接返回流文件(链接本身就是一个文件)

export const getExport = async (url, postData) => {
    let options = null
    if (postData) {
        options = _headerOptions('POST')
        options.headers['content-type'] = 'application/json'
        options.body = JSON.stringify(postData)
    } else {
        options = _headerOptions('GET')
    }

    let response = await fetch(config.apiHost + url, options)
    response.blob().then((blob) => {
        const a = window.document.createElement('a');
        const downUrl = window.URL.createObjectURL(blob);// 获取 blob 本地文件连接 (blob 为纯二进制对象,不能够直接保存到磁盘上)
        const m = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/.exec(response.headers.get('Content-Disposition'));
        const filename = m[1].replace(/['"]/g, '');
        // console.log(decodeURI(escape(filename[0])))
        a.href = downUrl;
        a.download = filename;
        a.click();
        window.URL.revokeObjectURL(downUrl);
    });

    return [];
}
发表在 js, 前端 | fetch 下载流文件已关闭评论