yii2 实现灵活的限流方式

yii2自带的限流方式使用做在某路径下的,但是想要实现在某种规则下才限流,该怎么做呢?
(如果对限流的逻辑不明白,请搜索“漏斗算法”)
1. 对自带的限流工具做一下简单的修改,个人觉得,自带的限流算法有个小小的问题,在超限调用接口的时候,不应当记录次数
(library是我个人创建的放公共包的目录)

<?php
namespace app\library;

use yii\web\TooManyRequestsHttpException;

class RateLimiter extends \yii\filters\RateLimiter
{
    public function checkRateLimit($user, $request, $response, $action)
    {
        list($limit, $window) = $user->getRateLimit($request, $action);
        list($allowance, $timestamp) = $user->loadAllowance($request, $action);

        $current = time();

        $allowance += (int) (($current - $timestamp) * $limit / $window);
        if ($allowance > $limit) {
            $allowance = $limit;
        }

        if ($allowance < 1) {
            //只注释了下面这一行代码,如果次数不足,则不更新时间
//            $user->saveAllowance($request, $action, 0, $current);
            $this->addRateLimitHeaders($response, $limit, 0, $window);
            throw new TooManyRequestsHttpException($this->errorMessage);
        }

        $user->saveAllowance($request, $action, $allowance - 1, $current);
        $this->addRateLimitHeaders($response, $limit, $allowance - 1, (int) (($limit - $allowance + 1) * $window / $limit));
    }


}

2. 创建限流的对象(代码仅仅做个抛砖引玉,可以根据个人需要优化哈,也可以搞一个独立的限流工具)

        $this->rateLimiter = Yii::createObject([
            'class' => \app\library\RateLimiter::class,
            'errorMessage' => '您已经提交过,请勿重复提交',//超限提示
            'enableRateLimitHeaders' => false,//是否把限流的详情展示到header中
            'user' => new class implements RateLimitInterface {//实现限流对象
                public function __construct()
                {//限流使用session,这里可以改成你想要的,也可以封装成store工厂
                 //提示,使用session实现的限流算法可能是不安全的(达不到限流的目的),请自行调整
                    $session = Yii::$app->getSession();
                    $session->open();
                    $this->store = $session;
                }

                public function getRateLimit($request, $action)
                {
                    if ($action->id == 'xxxx') {//这里可以针对不同的action做到不同的限流方式
                        return [1, 300];//300秒之内只能执行一次
                    }
                    return [2, 1];//1秒之内只能执行2次
                }

                public function loadAllowance($request, $action)
                {
                    return $this->get($action->id);//获取上一次的限流情况
                }

                public function saveAllowance($request, $action, $allowance, $timestamp)
                {
                    $this->save($action->id, $allowance, $timestamp);//保存本次的限流情况
                }

                public function get($id)//返回的是[上一次的使用后剩余次数,上一次的更新时间]
                {
                    return [$this->store[$id . '-allowance'], $this->store[$id . '-timestamp']];
                }

                public function save($id, $allowance, $timestamp)
                {//保存上本次的限流详情
                    $this->store[$id . '-allowance'] = $allowance;//剩余次数
                    $this->store[$id . '-timestamp'] = $timestamp;//调用时间
                }
            }
        ]);

3. 使用

//在你想要限流的地方加入下面的代码
$this->rateLimiter->checkRateLimit($this->rateLimiter->user, Yii::$app->request, Yii::$app->response, $this->action);
发表在 php, yii2 | 标签为 , , | yii2 实现灵活的限流方式已关闭评论

yii2 验证码

yii2的验证码很简单,使用yii2写接口的时候,它自带的验证码需要简单修改一下。
1. 在 根目录的library下创建一个Captcha.php(library是我自己创建的一个公共的包目录)

<?php
namespace app\library;

use \Yii;
use yii\captcha\CaptchaAction;
use yii\helpers\Url;
use yii\web\Response;

class Captcha extends CaptchaAction
{
    public function run()
    {
        //修改刷新方式,只要参数中包含刷新的key,则刷新验证码
        if (Yii::$app->request->getQueryParam(self::REFRESH_GET_VAR) !== null) {
            $code = $this->getVerifyCode(true);
        }else{
            $code = $this->getVerifyCode();
        }

        $this->setHttpHeaders();
        Yii::$app->response->format = Response::FORMAT_RAW;

        return $this->renderImage($code);
    }
}

2. 调用(比如在IndexController.php)中

    public function actionCaptcha()
    {
        $params = [
            'class' => 'app\library\Captcha',
            'fixedVerifyCode' => null,
            'backColor' => 0x000300,//背景颜色
            'maxLength' => 6, //最大显示个数
            'minLength' => 5,//最少显示个数
            'padding' => 6,//间距
            'height' => 40,//高度
            'width' => 130,  //宽度
            'foreColor' => 0xffffff,     //字体颜色
            'offset' => 5,        //设置字符偏移量 有效果
            'controller' => $this,
        ];
        echo (\Yii::createObject($params))->run();
    }

3. 验证方式

       //。。。
       $params = [
            'class' => 'app\library\Captcha',
            'testLimit' => 1,//超过这个错误次数,就刷新验证码
            'controller' => $this,
        ];

        $res = (\Yii::createObject($params))->validate($data['code'], true);//第一个参数是前端传过来的验证码,第二个参数是否验证大小写
        if(!$res) throw new \Exception("验证码错误");
       //。。。
发表在 php, yii2 | 标签为 , | yii2 验证码已关闭评论

es自定义时间字段索引

curl -X PUT -H 'Content-Type: application/json' "localhost:9200/indexName" -d '
{
"mappings": {
"dynamic_date_formats": ["yyyy-MM-dd HH:mm:ss"]
}
}'

https://www.elastic.co/guide/en/elasticsearch/reference/7.16/dynamic-field-mapping.html

发表在 es | es自定义时间字段索引已关闭评论

elasticsearch 快照迁移

1. 启用es
并且配置:- path.repo=/usr/share/elasticsearch/backup

2. 创建快照仓库
curl -X PUT "localhost:9200/_snapshot/baolin?pretty" -H 'Content-Type: application/json' -d'
{
"type": "fs",
"settings": {
"location": "baolin",
"compress": true
}
}
'

3. 查看仓库状态
curl -X POST "localhost:9600/_snapshot/baolin/_verify?pretty"

4. 创建快照策略(每天一个快照),只能是UTC,不支持其他时间地区
curl -X PUT "localhost:9200/_slm/policy/baolin-day?pretty" -H 'Content-Type: application/json' -d'
{
"schedule": "0 30 16 * * ?",
"name": "",
"repository": "baolin",
"config": {
"indices": "baolin*",
"include_global_state": true
},
"retention": {
"expire_after": "30d",
"min_count": 5,
"max_count": 50
}
}
'
保存30天,快照数量5 ~ 50个

5. 查看快照策略
curl -X GET "localhost:9200/_slm/policy/baolin-day?pretty"

# 查看所有的仓库
curl -H "Content-Type: application/json" -X GET http://localhost:9200/_snapshot/_all
# 查看某一个具体的仓库的快照情况
curl -H "Content-Type: application/json" -X GET 'http://localhost:9200/_snapshot/baolin/_all?pretty‘
# 列出所有当前正在运行的快照以及显示他们的详细状态信息
GET /_snapshot/_status?pretty
# 列出所有当前正在运行的快照以及显示他们的详细状态信息
GET /_snapshot/baolin/_status?pretty
# 查看指定快照的详细状态信息即使不是正在运行
GET /_snapshot/baolin/snapshot_2/_status?pretty
#删除某一个快照
DELETE /_snapshot/baolin/snapshot_2

#手动备份快照,不实用slm的情况下
curl -H "Content-Type: application/json" -XPUT 'http://localhost:9200/_snapshot/baolin/baolin_20220107?pretty&wait_for_completion=true'

6. 执行策略
curl -X POST "localhost:9200/_slm/policy/baolin-day/_execute?pretty"

7. 快照恢复(顺序不能错)

a. 启用新集群
b. 创建仓库
curl -X PUT "localhost:9200/_snapshot/baolin?pretty" -H 'Content-Type: application/json' -d'
{
"type": "fs",
"settings": {
"location": "baolin",
"compress": true
}
}
'
c. 把快照移动到当前集群仓库位置
cp -rf ../es/data/elasticsearch/backup/baolin/* data/elasticsearch/backup/baolin/

d. 在新集群中查看快照
curl -X GET "localhost:9600/_snapshot/baolin/_all?pretty" -H 'Content-Type: application/json'

f. 恢复快照
curl -X POST "localhost:9600/_snapshot/baolin/[快照名字]/_restore?pretty" -H 'Content-Type: application/json' -d'
{
"indices": "baolin*"
}
'

curl -X GET "localhost:9600/_snapshot/baolin/baolin-day-2022.01.07-o0zryaxtq1mzlvxeqnwm4q/_restore?pretty" -H 'Content-Type: application/json' -d'
{
"indices": "baolin*"
}
'

发表在 es | elasticsearch 快照迁移已关闭评论

Class ‘GuzzleHttp\Psr7\Utils’ not found

composer require guzzlehttp/psr7:^1.6.1 guzzlehttp/promises:^1.3.1

发表在 php, 框架 | Class ‘GuzzleHttp\Psr7\Utils’ not found已关闭评论

go fmt.Printf 格式化占位符详解

%v 输出结构体 {10 30}

%+v 输出结构体显示字段名 {one:10 tow:30}

%#v 输出结构体源代码片段 main.Point{one:10, tow:30}

%T 输出值的类型 main.Point

%t 输出格式化布尔值 true

%d`输出标准的十进制格式化 100

%b`输出标准的二进制格式化 99 对应 1100011

%c`输出定整数的对应字符 99 对应 c

%x`输出十六进制编码 99 对应 63

%f`输出十进制格式化 99 对应 63

%e`输出科学技科学记数法表示形式 123400000.0 对应 1.234000e+08

%E`输出科学技科学记数法表示形式 123400000.0 对应 1.234000e+08

%s 进行基本的字符串输出 "\"string\"" 对应 "string"

%q 源代码中那样带有双引号的输出 "\"string\"" 对应 "\"string\""

%p 输出一个指针的值 &jgt 对应 0xc00004a090

% 后面使用数字来控制输出宽度 默认结果使用右对齐并且通过空格来填充空白部分

%2.2f 指定浮点型的输出宽度 1.2 对应 1.20

%*2.2f 指定浮点型的输出宽度对齐,使用 `-` 标志 1.2 对应 *1.20

发表在 go | go fmt.Printf 格式化占位符详解已关闭评论

抽奖算法之1

抽奖逻辑:有10个奖品,每个奖品的概率不一样,设计一种算法至少抽中其中的一个
算法讲解:10个奖品,每个奖品的概率是一个范围,把所有的范围都加到一起作为整体x,然后随机1 ~ x数字n,n落到哪个区间内,就中了哪个奖品

$奖品概率 = [10,35,50,....];
$x = array_sum($奖品概率);
$n = mt_rand();
foreach($奖品概率  as $k => $item){
   if($n < $item){
      return $k;
   }

   $n -= $item;
}
发表在 php, php函数集, php小程序 | 抽奖算法之1已关闭评论

yii2 db buildQuery 时 禁止转译 字符串

https://www.yiichina.com/doc/api/2.0/yii-db-expression

$expression = new Expression('NOW()');
$now = (new \yii\db\Query)->select($expression)->scalar();  // SELECT NOW();
echo $now;
发表在 php, yii2, 框架 | yii2 db buildQuery 时 禁止转译 字符串已关闭评论

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 数组实现简单队列已关闭评论