phpitgo

phpitgo 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 个人简介什么都没有

个人动态

phpitgo 收藏了文章 · 2020-12-02

ipad系统更新到13.2.x后,怎么判断 ipad?

事情起因

前端时间刚做完ipad兼容问题,本以为没问题了,然后又有客户反馈bug,

仔细想了一下原因,可能是ipad判断失效了,问了客户ipad系统是 13.2.2版本,长时间等待升级....

升级到13.2.3后,打印ipad navigator.userAgent字符串发现,里面熟悉的 /ipad字眼居然没了!!!

打印数据如下,

"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Safari/605.1.15"

omg,跟pc端的safari浏览器一模一样了。。

上个月ipad发布的新版本,也不通知一下!!估计会有大批前端开发人员遇到不知名bug,集体蒙圈 😂😂😂

解决方案

蒙圈了半天,然后仔细对比了一下pc和ipad中safari的 navigator 的区别。还是发现了有一点不同的地方

navigator.maxTouchPoints 返回当前设备能够支持的最大同时触摸的点数

ipad环境下打印 navigator.maxTouchPoints的数值是5

pc环境下打印 navigator.maxTouchPoints的数值是0

pc环境下,切换到移动端模式(Toggle device toolbar),打印 navigator.maxTouchPoints的数值是1

所以要判断ipad 的话,我是这么修改的

export function isIPad() {
  // 兼容 ipad 13.2.x版本
  return /iPad/.test(navigator.userAgent) || (/Mac/.test(navigator.userAgent) && navigator.maxTouchPoints > 0)
}

客户等着,只能先这么解决,各位还有啥比较正统正规的方法,不吝赐教。

查看原文

phpitgo 赞了文章 · 2020-12-02

ipad系统更新到13.2.x后,怎么判断 ipad?

事情起因

前端时间刚做完ipad兼容问题,本以为没问题了,然后又有客户反馈bug,

仔细想了一下原因,可能是ipad判断失效了,问了客户ipad系统是 13.2.2版本,长时间等待升级....

升级到13.2.3后,打印ipad navigator.userAgent字符串发现,里面熟悉的 /ipad字眼居然没了!!!

打印数据如下,

"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Safari/605.1.15"

omg,跟pc端的safari浏览器一模一样了。。

上个月ipad发布的新版本,也不通知一下!!估计会有大批前端开发人员遇到不知名bug,集体蒙圈 😂😂😂

解决方案

蒙圈了半天,然后仔细对比了一下pc和ipad中safari的 navigator 的区别。还是发现了有一点不同的地方

navigator.maxTouchPoints 返回当前设备能够支持的最大同时触摸的点数

ipad环境下打印 navigator.maxTouchPoints的数值是5

pc环境下打印 navigator.maxTouchPoints的数值是0

pc环境下,切换到移动端模式(Toggle device toolbar),打印 navigator.maxTouchPoints的数值是1

所以要判断ipad 的话,我是这么修改的

export function isIPad() {
  // 兼容 ipad 13.2.x版本
  return /iPad/.test(navigator.userAgent) || (/Mac/.test(navigator.userAgent) && navigator.maxTouchPoints > 0)
}

客户等着,只能先这么解决,各位还有啥比较正统正规的方法,不吝赐教。

查看原文

赞 2 收藏 1 评论 0

phpitgo 回答了问题 · 2020-11-11

解决一个汉字在UTF-8中几个字节?

学习了?,哈。

关注 8 回答 5

phpitgo 赞了回答 · 2020-09-16

解决swagger-php添加basePath后不生效

解决了:
新版本换了写法,应该使用@OA\Server注解:

/**
 * @OA\OpenApi(
 *     @OA\Info(
 *         title="API接口文档",
 *         version="1.0.0",
 *         description="接口文档"
 *     ),
 *     @OA\Server(
 *         url="http://127.0.0.1:8089/test/api"
 *     )
 * )
 */

关注 1 回答 1

phpitgo 赞了文章 · 2019-10-31

PHP计算二维数组的元素个数

PHP计算二维数组的元素个数

对于计算下面这种二位数组的个数,可以用count函数来计算

$arr = [
    [11,22],
    [
        'aa' => 33,
        'bb' => 44,
        'cc' => 55
    ]
];

一般对于count,相信每个人都很熟悉,但有一点可能是大家不清楚的,就是count的第二个参数,下面介绍一下

count ( mixed $array_or_countable [, int $mode = COUNT_NORMAL ] ) : int

参数解释

array_or_countable
    数组或者 Countable 对象。
mode
    如果可选的 mode 参数设为 COUNT_RECURSIVE(或 1),count() 将递归地对数组计数。
    计算多维数组的所有单元尤其有用。

关键是就是第二个参数的COUNT_RECURSIVE,它是递归的算出二位数组的个数。而不是二维数组的元素个数

$a = count($arr,COUNT_RECURSIVE)
//$a = 7
//因为 [11,22] 和 ['aa' => 33, 'bb' => 44,'cc' => 55]也算上了

所以最后要求元素个数$num = count($arr,COUNT_RECURSIVE) - count($arr)

查看原文

赞 1 收藏 0 评论 2

phpitgo 关注了问题 · 2018-12-27

解决如何把阿里云 code 的代码部署到服务器?

1.服务器已经安装了tomcat、git、MySQL。这是服务器地址:链接
2.代码已经放到了阿里云code代码平台 https://code.aliyun.com/t_149...
3.我要怎么把代码克隆到服务器?
4.百度了很多答案,越看疑问越多

  • 项目代码是 private 私有的,需要给服务器生成公钥私钥,并且导入公钥到代码平台吗 ?
  • 需要在服务器上建立一个 Git 仓库吗?这里的仓库和阿里云code代码平台是同一个概念吗 ?
  • 文章中经常出现的'钩子'是什么意思呢,怎么设置。
  • 可能大家觉得我的问题莫名其妙,但是我真的晕了。这里给一些图,希望大家容易可以理解我的想法。

阿里云code项目
目录结构,这是用inteli idea开发的Java Web,tomcat + MySQL
服务器环境是一键安装包,这是说明
网站根目录

关注 3 回答 1

phpitgo 赞了文章 · 2018-12-20

听说支付宝有一个“疯起来连自己都打”的项目

摘要: 红军 VS 蓝军,谁是更强者?

小蚂蚁说:

自古红蓝出CP,在蚂蚁金服就有这样两支“相爱相杀”的队伍——红军和蓝军。蓝军是进攻方,主要职责是挖掘系统的弱点并发起“真实”的攻击,俗称“找茬”;红军则是防守方,其防控体系建设中的实时核对平台能够做到稳定的分钟级核对异常发现能力,并提供业务快速接入的能力。

支付宝“疯起来连自己都打”的项目就是红蓝军技术攻防演练,他们不仅每周进行全栈级别的演练,每年还会举行规模极大的“期中考试”和“期末考试”。接下来就跟着小蚂蚁一起去看看这对红蓝cp的日常“互怼”生活吧!

如果一个技术团队不干别的,专门“搞破坏”,这是一种怎样的存在?这真的不是“天方夜谭”,在支付宝确实有这么一支队伍——技术蓝军。蓝军的任务就是不断地攻击和进攻,而防守方则是技术红军。在支付宝,蓝军从属于蚂蚁金服技术风险部(SRE),而红军则包括SRE及各业务部门的技术团队。

说到SRE,就需要科普一下了。SRE全拼为Site Reliability Engineer,是软件工程师和系统管理员的结合,是一种要求极高的技术工种。据说,目前全球只有少数几家顶级互联网公司拥有真正意义上的SRE团队,蚂蚁金服是其中之一。

由蓝军主导的技术攻防演练就是那个传说中的“疯起来连自己都打”的项目,今天,就来起底一下这个神秘的项目。

从“青铜”到强者

红蓝军技术攻防演练与蚂蚁金服技术风险部的发展息息相关,而蚂蚁技术风险的演进轨迹和游戏中的不断打怪升级非常相像。

早期是质量+运维+架构师三角协同,各司其职并自发性的开展一些技术风险相关的工作。2013年,蚂蚁金服技术团队提出了质量2.0战略,以统一的规章、统一的流程和统一的阵型,开始体系化地沉淀故障检测等方面的平台化能力。

大概一年后,也就是2014年,专门成立了技术质量部,从全域视角解决技术风险的问题。

2015年,技术质量部正式升级成为技术风险部,专注研发及架构的技术风险问题,并完成相应解决方案和落地的平台。

2016年,技术风险部再次升级为SRE团队。

SRE团队组建后,就开始全面开展故障自动定位、自适应容灾、防抖、精细化高可用等工作。其中防抖这块,要保证任何的网络或基础设施抖动,用户都无感知;而精细化高可用,又叫单笔高可用,其颗粒度可以精准到用户的每一笔交易,远远优于行业内的机房级高可用。

同时,那个热衷“找茬”的组织——技术蓝军也正式成立。这个专门的、拥有独立职能的团队不干别的,主要职责是挖掘系统的弱点并发起“真实”的攻击,红蓝军技术攻防演练也自此诞生。

牛X的是,技术蓝军并不对各业务方负责,只对应用架构及防御系统的稳定性和可靠性负责。在蓝军眼中,故障的发生是必然的,只是时间早晚而已。蓝军只有想尽办法去触发这些故障,这样,在故障真实发生的时候,才有足够的应付能力。

所以,蓝军发掘各类脆弱点,并通过红蓝军技术攻防演练,不断验证防御系统的可靠性。而故障防御系统及不断优化的高可用架构则是由SRE团队的红军与各业务深度合作,沉淀、构建出来的。

蓝军正在研究“突袭”计划

现在,全栈级别的技术攻防演练每周都在进行,蓝军似乎对“疯起来连自己都打”很上瘾。

利矛与坚盾不断升级

持续不断的攻防演练,让蓝军和红军的技术能力得到了极大地提升,同时双方“武器库”也在不断升级。

2017年秋天,蓝军团队在成立后的两个月内,自主研发了字节码级别的故障注入系统Awatch,这个武器的厉害之处在于可以实时地对运行中的业务系统进行任意链路的编织侵入。这对于对于技术蓝军以及整个红蓝攻防体系,具有里程碑式的意义。

蓝军研发出了厉害的武器,红军也没闲着。

与此同时,技术红军的防控体系建设也在如火如荼地进行着,实时核对平台横空而出。该平台能够做到稳定的分钟级核对异常发现能力,在某些场景下可以做到秒级发现,并且平台提供了业务快速接入的能力;红军还在实时核对平台的基础之上,升级演化出一套智能核对平台(内部代号四道防线),引入AI技术自动识别业务问题,目前这套防线已经覆盖蚂蚁80%以上的业务。另外,各个业务域针对自身业务的一些特殊性,也研发了相应的核对系统。

尽管蓝军制造故障的能力有很大的提高,但大部分的故障场景主要是各个业务方提供的,只有极少数是蓝军人工梳理业务或者分析代码产出。此时,蓝军团队认为,日常演练常态化,在故障场景发现方面不能再依赖业务,必须建立自主发现故障场景的能力。

用“可乐山”明志,是程序员常见的套路

2018年3月,蓝军推出故障场景挖掘平台,基于Awatch探针探测应用内数据流,以此进行“弱点挖掘”。这套弱点挖掘体系,能够自动发现故障场景,最高能够在5分钟内产生500+的故障场景,红蓝攻防的日常演练的最为重要一块拼图终于完成!

然而新的问题来了。

蓝军的故障挖掘平台能力毋庸置疑,但有攻击就需要应急,高频攻防实施亦会给红军带来大量的人力消耗。持续应急压力驱动,红军开展““故障自愈”架构体系升级及能力建设,以效能为目标,结合仿真,红蓝军一起研发了“无损”攻防体系,并且推出与之匹配的度量平台,自动度量攻防结果,数据可视化。

目前,常态红蓝技术对抗保持每周200+个故障场景的节奏在持续运作。

常态化的红蓝“互怼”

在线、实时、随地、无差别……这是支付宝技术蓝军实施攻击行为的几大标签。

2017年年底的红蓝技术攻防周,技术蓝军发起攻击,但由于故障组件一处隐藏bug导致故障命中数量远远大于预期,给红军增添了不少麻烦,业务线的技术同学投入大量的人力和资源进行善后。此情此景之下,红军方面不仅没有抱怨,反而给予蓝军鼓励,“这次预期外的故障攻击是最真实的应急锻炼!”

2018年年中的一次红蓝技术攻防中,蓝军在周末发起突袭,而刚好红军的相关同学正在举办婚礼。于是,一群程序员赶紧拿出吃饭的家伙,噼里啪啦敲着键盘进行应急,那画面简直不要太美了。

还是在2018年的一次对抗中,红军祭出了“尖端武器”——自适应防灾、防抖等,这让蓝军吃尽苦头,几乎每次攻击都无功而返。挫败感飙升的蓝军最终放出大招,让红军接受了非常猛烈的炮火洗礼。

有意思的是,似乎蓝军攻击得越欢,红军的同学越高兴……虽然看上去很受虐,但却没毛病,因为蓝军攻击得越狠越深入,被挖掘和发现出来的技术风险就会越确定,防御系统的能力也会因此而得到提升。

除了设计缜密的防御措施防止蓝军的袭击,拜关公求庇佑也是红军的“习俗”

令人震惊的是,为了防止蓝军的“袭击”,红军除了在防御系统方面下十足的功夫,每年期中和期末的红蓝技术攻防演练,红军都要举办一个仪式——那就是拜关公,除了叩拜,还得给驱邪镇恶的关公献礼,礼品包括旺仔牛奶、格子衬衫、键盘、香烟等。

风险防控技术全面开放

蚂蚁金服技术风险部门经过不断地升级,并将红蓝技术攻防演练形成常态化。除了每周进行全栈级别的演练,每年还会举行规模极大的“期中考试”和“期末考试”。这意味着,支付宝的风险防控体系持续地经受打磨与锤炼。

目前,支付宝的“红蓝对抗”演练已经沉淀出一整套成熟的风险防控体系,通过仿真环境模拟天灾人祸,去考验技术架构的健壮性及技术人员的应急能力,从而全面地提升系统稳定,实现系统的高可靠性和高可用性。

所谓的天灾和人祸。天灾指的是,当出现台风、断网、火情等极端异常情况的时候,系统如何快速应对。这有点类似于今年杭州云栖ATEC大会上,蚂蚁金服副CTO胡喜现场演练的异常断网情况下,“三地五中心”自动切换,保证支付服务不中断。人祸则是指因技术人员操作失误引发故障后,系统如何快速应。

在蚂蚁金融科技官网(https://tech.antfin.com/)上可以看到,这些技术风险相关的能力已经对外开放,目前共有3款产品,包括容灾应急平台、全链路压测和资金安全监控;另外,还有3款产品,变更管控、巡检平台和黑屏运维管控即将上线对外开放。



本文作者:平生栗子

阅读原文

本文为云栖社区原创内容,未经允许不得转载。

查看原文

赞 13 收藏 4 评论 3

phpitgo 关注了问题 · 2018-11-13

Laravel 关联表扁平化输出

问题描述

Laravel 有没有自带内部方法使得关联表和原表扁平化输出:如下
[

'a' => '1',
'b' => '2',
'c' => [
    'd' => 3,
    'e' => 4,
] 

]

[

'a' => '1',
'b' => '2',
'd' => 3,
'e' => 4,

]
将关联表 c 去掉,直接读出里面字段

谢谢😁

关注 4 回答 2

phpitgo 收藏了文章 · 2018-11-09

从零单排学Redis【白银】

前言

只有光头才能变强

今天继续来学习Redis,上一篇从零单排学Redis【青铜】已经将Redis常用的数据结构过了一遍了。如果还没看的同学可以先去看一遍再回来~

这篇主要讲的内容有:

  • Redis服务器的数据库
  • Redis对过期键的处理
  • Redis持久化策略(RDB和AOF)

本文力求简单讲清每个知识点,希望大家看完能有所收获

一、Redis服务器中的数据库

我们应该都用过MySQL,MySQL我们可以在里边创建好几个库:

MySQL创建几个数据库

同样地,Redis服务器中也有数据库这么一个概念。如果不指定具体的数量,默认会有16个数据库。

通过SELECT命令可以切换到0~15的数据库

上面的命令我们也可以发现:当切换到15号数据库,存进15号库的数据,再切换到0号数据库时,是获取不到的!

  • 这说明,数据库与数据库之间的数据是隔离的。

1.1Redis数据库的原理

Redis服务器用redisServer结构体来表示,其中redisDb是一个数组,用来保存所有的数据库,dbnum代表数据库的数量(这个可以配置,默认是16)


struct redisServer{  

    //redisDb数组,表示服务器中所有的数据库
    redisDb *db;  
    
    //服务器中数据库的数量
    int dbnum;  
  
}; 

我们知道Redis是C/S结构,Redis客户端通过redisClient结构体来表示:


typedef struct redisClient{  
   
    //客户端当前所选数据库
    redisDb *db;  
     
}redisClient;

Redis客户端连接Redis服务端时的示例图:

Redis客户端连接Redis服务端时的示例图

Redis中对每个数据库用redisDb结构体来表示:


typedef struct redisDb { 
    int id;         // 数据库ID标识
    dict *dict;     // 键空间,存放着所有的键值对              
    dict *expires;  // 过期哈希表,保存着键的过期时间                          
    dict *watched_keys; // 被watch命令监控的key和相应client    
    long long avg_ttl;  // 数据库内所有键的平均TTL(生存时间)     
} redisDb;

从代码上我们可以发现最重要的应该是dict *dict,它用来存放着所有的键值对。对于dict数据结构(哈希表)我们在上一篇也已经详细说了。一般我们将存储所有键值对的dict称为键空间

键空间示意图:

键空间示意图

Redis的数据库就是使用字典(哈希表)来作为底层实现的,对数据库的增删改查都是构建在字典(哈希表)的操作之上的

例如:


redis > GET message

"hello world"

查找键的示意图

1.2键的过期时间

Redis是基于内存,内存是比较昂贵的,容量肯定比不上硬盘的。就我们现在一台普通的机子,可能就8G内存,但硬盘随随便便都1T了。

因为我们的内存是有限的。所以我们会干掉不常用的数据,保留常用的数据。这就需要我们设置一下键的过期(生存)时间了。

  • 设置键的生存时间可以通过EXPIRE或者PEXPIRE命令。
  • 设置键的过期时间可以通过EXPIREAT或者PEXPIREAT命令。

其实EXPIREPEXPIREEXPIREAT这三个命令都是通过PEXPIREAT命令来实现的。

我们在redisDb结构体中还发现了dict *expires;属性,存放所有键过期的时间。

举个例子基本就可以理解了:


redis > PEXPIREAT message 1391234400000
(integer) 1

设置了message键的过期时间为1391234400000

新增一个过期时间的键

既然有设置过期(生存)时间的命令,那肯定也有移除过期时间,查看剩余生存时间的命令了:

  • PERSIST(移除过期时间)
  • TTL(Time To Live)返回剩余生存时间,以秒为单位
  • PTTL以毫秒为单位返回键的剩余生存时间

1.2.1过期策略

上面我们已经能够了解到:过期键是保存在哈希表中了。那这些过期键到了过期的时间,就会立马被删除掉吗??

要回答上面的问题,需要我们了解一下删除策略的知识,删除策略可分为三种

  • 定时删除(对内存友好,对CPU不友好)

    • 到时间点上就把所有过期的键删除了。
  • 惰性删除(对CPU极度友好,对内存极度不友好)

    • 每次从键空间取键的时候,判断一下该键是否过期了,如果过期了就删除。
  • 定期删除(折中)

    • 每隔一段时间去删除过期键,限制删除的执行时长和频率。

Redis采用的是惰性删除+定期删除两种策略,所以说,在Redis里边如果过期键到了过期的时间了,未必被立马删除的!

1.2.2内存淘汰机制

如果定期删除漏掉了很多过期key,也没及时去查(没走惰性删除),大量过期key堆积在内存里,导致redis内存块耗尽了,咋整?

我们可以设置内存最大使用量,当内存使用量超出时,会施行数据淘汰策略

Redis的内存淘汰机制有以下几种:

内存淘汰机制

一般场景:

使用 Redis 缓存数据时,为了提高缓存命中率,需要保证缓存数据都是热点数据。可以将内存最大使用量设置为热点数据占用的内存量,然后启用allkeys-lru淘汰策略,将最近最少使用的数据淘汰

二、Redis持久化

Redis是基于内存的,如果不想办法将数据保存在硬盘上,一旦Redis重启(退出/故障),内存的数据将会全部丢失。

  • 我们肯定不想Redis里头的数据由于某些故障全部丢失(导致所有请求都走MySQL),即便发生了故障也希望可以将Redis原有的数据恢复过来,这就是持久化的作用。

Redis提供了两种不同的持久化方法来讲数据存储到硬盘里边:

  • RDB(基于快照),将某一时刻的所有数据保存到一个RDB文件中。
  • AOF(append-only-file),当Redis服务器执行写命令的时候,将执行的写命令保存到AOF文件中。

2.1RDB(快照持久化)

RDB持久化可以手动执行,也可以根据服务器配置定期执行。RDB持久化所生成的RDB文件是一个经过压缩的二进制文件,Redis可以通过这个文件还原数据库的数据。

RDB文件还原数据

有两个命令可以生成RDB文件:

  • SAVE阻塞Redis服务器进程,服务器不能接收任何请求,直到RDB文件创建完毕为止。
  • BGSAVE创建出一个子进程,由子进程来负责创建RDB文件,服务器进程可以继续接收请求。

Redis服务器在启动的时候,如果发现有RDB文件,就会自动载入RDB文件(不需要人工干预)

  • 服务器在载入RDB文件期间,会处于阻塞状态,直到载入工作完成。

除了手动调用SAVE或者BGSAVE命令生成RDB文件之外,我们可以使用配置的方式来定期执行:

在默认的配置下,如果以下的条件被触发,就会执行BGSAVE命令

    save 900 1              #在900秒(15分钟)之后,至少有1个key发生变化,
    save 300 10            #在300秒(5分钟)之后,至少有10个key发生变化
    save 60 10000        #在60秒(1分钟)之后,至少有10000个key发生变化

原理大概就是这样子的(结合上面的配置来看):


struct redisServer{
    // 修改计数器
    long long dirty;

    // 上一次执行保存的时间
    time_t lastsave;

    // 参数的配置
    struct saveparam *saveparams;
};

遍历参数数组,判断修改次数和时间是否符合,如果符合则调用besave()来生成RDB文件

Redis服务器的状态

总结:通过手动调用SAVE或者BGSAVE命令或者配置条件触发,将数据库某一时刻的数据快照,生成RDB文件实现持久化。

2.2AOF(文件追加)

上面已经介绍了RDB持久化是通过将某一时刻数据库的数据“快照”来实现的,下面我们来看看AOF是怎么实现的。

  • AOF是通过保存Redis服务器所执行的写命令来记录数据库的数据的。

AOF原理图

比如说我们对空白的数据库执行以下写命令:


redis> SET meg "hello"
OK

redis> SADD fruits "apple" "banana" "cherry"
(integer) 3

redis> RPUSH numbers 128 256 512
(integer) 3 

Redis会产生以下内容的AOF文件:

AOF文件

这些都是以Redis的命令请求协议格式保存的。Redis协议规范(RESP)参考资料:

AOF持久化功能的实现可以分为3个步骤:

  • 命令追加:命令写入aof_buf缓冲区
  • 文件写入:调用flushAppendOnlyFile函数,考虑是否要将aof_buf缓冲区写入AOF文件中
  • 文件同步:考虑是否将内存缓冲区的数据真正写入到硬盘

AOF持久化步骤

flushAppendOnlyFile函数的行为由服务器配置的appendfsyn选项来决定的:


    appendfsync always     # 每次有数据修改发生时都会写入AOF文件。
    appendfsync everysec   # 每秒钟同步一次,该策略为AOF的默认策略。
    appendfsync no         # 从不同步。高效但是数据不会被持久化。

从字面上应该就更好理解了,这里我就不细说了...

下面来看一下AOF是如何载入与数据还原的:

  • 创建一个伪客户端(本地)来执行AOF的命令,直到AOF命令被全部执行完毕。

redis伪客户端载入AOF文件

2.2.1AOF重写

从前面的示例看出,我们写了三条命令,AOF文件就保存了三条命令。如果我们的命令是这样子的:


redis > RPUSH list "Java" "3y"
(integer)2

redis > RPUSH list "Java3y"
integer(3)

redis > RPUSH list "yyy"
integer(4)

同样地,AOF也会保存3条命令。我们会发现一个问题:上面的命令是可以合并起来成为1条命令的,并不需要3条。这样就可以让AOF文件的体积变得更小

AOF重写由Redis自行触发(参数配置),也可以用BGREWRITEAOF命令手动触发重写操作。

  • 要值得说明的是:AOF重写不需要对现有的AOF文件进行任何的读取、分析。AOF重写是通过读取服务器当前数据库的数据来实现的

比如说现在有一个Redis数据库的数据如下:

Redis数据库的数据

新的AOF文件的命令如下,没有一条是多余的

AOF重写后的命令

2.2.2AOF后台重写

Redis将AOF重写程序放到子进程里执行(BGREWRITEAOF命令),像BGSAVE命令一样fork出一个子进程来完成重写AOF的操作,从而不会影响到主进程。

AOF后台重写是不会阻塞主进程接收请求的,新的写命令请求可能会导致当前数据库和重写后的AOF文件的数据不一致

为了解决数据不一致的问题,Redis服务器设置了一个AOF重写缓冲区,这个缓存区会在服务器创建出子进程之后使用

AOF后台重写过程

2.3RDB和AOF对过期键的策略

RDB持久化对过期键的策略:

  • 执行SAVE或者BGSAVE命令创建出的RDB文件,程序会对数据库中的过期键检查,已过期的键不会保存在RDB文件中
  • 载入RDB文件时,程序同样会对RDB文件中的键进行检查,过期的键会被忽略

AOF持久化对过期键的策略:

  • 如果数据库的键已过期,但还没被惰性/定期删除,AOF文件不会因为这个过期键产生任何影响(也就说会保留),当过期的键被删除了以后,会追加一条DEL命令来显示记录该键被删除了
  • 重写AOF文件时,程序会对RDB文件中的键进行检查,过期的键会被忽略

复制模式:

  • 主服务器来控制从服务器统一删除过期键(保证主从服务器数据的一致性)

2.4RDB和AOF用哪个?

RDB和AOF并不互斥,它俩可以同时使用

  • RDB的优点:载入时恢复数据快、文件体积小。
  • RDB的缺点:会一定程度上丢失数据(因为系统一旦在定时持久化之前出现宕机现象,此前没有来得及写入磁盘的数据都将丢失。)
  • AOF的优点:丢失数据少(默认配置只丢失一秒的数据)。
  • AOF的缺点:恢复数据相对较慢,文件体积大

如果Redis服务器同时开启了RDB和AOF持久化,服务器会优先使用AOF文件来还原数据(因为AOF更新频率比RDB更新频率要高,还原的数据更完善)

可能涉及到RDB和AOF的配置:


redis持久化,两种方式
1、rdb快照方式
2、aof日志方式

----------rdb快照------------
save 900 1
save 300 10
save 60 10000

stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
dir /var/rdb/

-----------Aof的配置-----------
appendonly no # 是否打开 aof日志功能

appendfsync always #每一个命令都立即同步到aof,安全速度慢
appendfsync everysec
appendfsync no 写入工作交给操作系统,由操作系统判断缓冲区大小,统一写入到aof  同步频率低,速度快


no-appendfsync-on-rewrite yes 正在导出rdb快照的时候不要写aof
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb 


./bin/redis-benchmark -n 20000

官网文档:

三、最后

现在临近双十一买阿里云服务器就特别省钱!之前我买学生机也要9.8块钱一个月,现在最低价只需要8.3一个月!

无论是Nginx/Elasticsearch/Redis这些技术都是在Linux下完美运行的,如果还是程序员新手,买一个学习Linux基础命令,学习搭建环境也是不错的选择。

如果有要买服务器的同学可通过我的链接直接享受最低价https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.pfn5xpli


如果大家有更好的理解方式或者文章有错误的地方还请大家不吝在评论区留言,大家互相学习交流~~~

参考资料:

  • 《Redis设计与实现》
  • 《Redis实战》
一个坚持原创的Java技术公众号:Java3y,欢迎大家关注

3y所有的原创文章:

查看原文

phpitgo 收藏了文章 · 2018-11-09

使用postman调试jwt开发的接口

我的github博客:https://zgxxx.github.io/

上一篇博客文章https://segmentfault.com/a/11... 介绍了laravel使用dingo+jwt开发API的几个步骤,那么在实际操作中,我们需要测试API

$api = app('Dingo\Api\Routing\Router');
$api->version('v1', ['namespace' => 'App\Http\Controllers\V1'], function ($api) {
    $api->post('register', 'AuthController@register');
    $api->post('login', 'AuthController@login');
    $api->post('logout', 'AuthController@logout');
    $api->post('refresh', 'AuthController@refresh');
    $api->post('me', 'AuthController@me');
    $api->get('test', 'AuthController@test');
});

设置了这几个路由,对应的url类似这样:http://www.yourweb.com/api/me 使用postman来调试这些API。

请求API的大致流程

我们使用jwt代替session,首先是通过登录(jwt的attempt方法验证账号密码),成功后会返回一个JWT,我们把这个字符串统一叫做token,这个token需要我们客户端保存起来,然后后面需要认证的接口就在请求头里带上这个token,后台验证正确后就会进行下一操作,如果token错误,或者过期就返回401或500错误,拒绝后面的操作。

前端可以保存在localStorage,小程序可以 使用wx.setStorageSync保存

所以请求头信息Authorization:Bearer + token很重要,但是有个问题,这个token是有一个刷新时间和过期时间的:
'ttl' => env('JWT_TTL', 60),
'refresh_ttl' => env('JWT_REFRESH_TTL', 20160),

  • refresh_ttl是过期时间,默认14天,很好理解,就像一些网站一样,你好几个月没去登录,你的账号会自动退出登录,因为过期了,需要重新输入账号密码登录。
  • ttl刷新时间默认60分钟,也就是说你拿这个一小时前的token去请求是不行的,会报The token has been blacklisted的错误,意思是说这个旧的token已经被列入黑名单,无法使用
token是会被别人盗取的,所以token需要每隔一段时间就更新一次

这时候有个问题,每隔一小时就更新,那岂不是要每小时就重新登录一遍来获取新token?当然不需要,我们可以写个中间件来实现无痛刷新token,用户不会察觉我们已经更新了token。

<?php

namespace App\Http\Middleware;

use Closure;
use Tymon\JWTAuth\Exceptions\JWTException;
use Tymon\JWTAuth\Http\Middleware\BaseMiddleware;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;

class RefreshToken extends BaseMiddleware
{
    /**
     * @author: zhaogx
     * @param $request
     * @param Closure $next
     * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\Response|mixed
     * @throws JWTException
     */
    public function handle($request, Closure $next)
    {
        // 检查此次请求中是否带有 token,如果没有则抛出异常。
        $this->checkForToken($request);

        // 使用 try 包裹,以捕捉 token 过期所抛出的 TokenExpiredException  异常
        try {
            // 检测用户的登录状态,如果正常则通过
            if ($this->auth->parseToken()->authenticate()) {
                return $next($request);
            }
            throw new UnauthorizedHttpException('jwt-auth', '未登录');
        } catch (TokenExpiredException $exception) {
            // 此处捕获到了 token 过期所抛出的 TokenExpiredException 异常,我们在这里需要做的是刷新该用户的 token 并将它添加到响应头中
            try {
                // 刷新用户的 token
                $token = $this->auth->refresh();
                // 使用一次性登录以保证此次请求的成功
                \Auth::guard('api')->onceUsingId($this->auth->manager()->getPayloadFactory()->buildClaimsCollection()->toPlainArray()['sub']);
            } catch (JWTException $exception) {
                // 如果捕获到此异常,即代表 refresh 也过期了,用户无法刷新令牌,需要重新登录。
                throw new UnauthorizedHttpException('jwt-auth', $exception->getMessage());
            }
        }

        return $next($request)->withHeaders([
                'Authorization'=> 'Bearer '.$token,
            ]);
    }
}

一旦中间件检测到token过时了,就自动刷新token,然后在响应头把新的token返回来,我们客户端可以根据响应头是否有'Authorization'来决定是否要替换token
在使用postman调试这些API的时候就有个问题,postman又没有前端代码,我怎么及时更新这个token,难道每次请求都要去看响应头,发现Authorization后手动去复制黏贴吗,当然也不需要,postman有个强大的环境变量,其实也是前端js的东西。

postman自动刷新请求头token

登录后自动获取token

首先点击设置环境这个按钮,点击Add按钮添加一个变量,我们设置key值为access_token,
1
2

接着我们在登录接口的Tests中去赋值这个变量
3

var data = JSON.parse(responseBody);  
if (data.result.access_token) {  
      tests["Body has token"] = true;  
      var tokenArray = data.result.access_token.split(" ");
      postman.setEnvironmentVariable("access_token", tokenArray[1]);  
}  
else {  
  tests["Body has token"] = false;  
}  

这段js代码就是获取请求成功后返回的access_token值,将其赋值给postman的环境变量,我们看到请求成功后我们后台返回了一个json,其中就有我们需要的access_token,我们可以再去环境变量那里看看这时候的变量有什么变化
4

可以看到这里的变量access_token已经有值了,就是我们后台返回来的access_token字符串,说明赋值成功

接着我们到另一个需要认证的接口测试
我们在Authorization类型type选择Bearer Token,在后面Token表单那里打一个'{'就会自动提示我们设置过的变量{{access_token}}
5
发送请求测试下
6
已经成功了。

无痛刷新token

那如果token刷新了,经过后台中间件无痛刷新后,会在响应头返回一个新的token(这一次请求用的是旧的token,默认认证通过)
7

现在我们需要在这个接口上直接更新我们的变量access_token(如下图),而不需要去再请求一遍登录接口
10

var authHeader = postman.getResponseHeader('Authorization');
if (authHeader){
      var tokenArray = authHeader.split(" ");
      postman.setEnvironmentVariable("access_token", tokenArray[1]);  
      tests["Body has refreshtoken"] = true;  
} else {
       tests["Body has no refreshtoken"] = false;  
}

这段js代码就是将响应头中的Authorization赋值给我们的access_token
8

这是响应头的Authorization,截取了最后面的字符串
9

刷新时间过了后,我们试着再发一次请求,我们可以看到,还是可以访问的,而且请求头里的Authorization已经自动更新过来了

用一句话整理大概就是,你需要在哪个接口响应后更新变量,就去这个这个口的Test下写js赋值代码
postman.setEnvironmentVariable("access_token", token);,只要没错误你就可以在别的地方使用{{access_token}}更新替换了。

查看原文

认证与成就

  • 获得 0 次点赞
  • 获得 3 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 3 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2017-12-08
个人主页被 322 人浏览