英国弗兰明曾说过一句话:“不要等待运气降临,应该去努力掌握知识。”
1 前言
大家好,我是阿沐!对于redis大家是最熟悉不过了,作为缓存界的使用率一直遥遥领先。基本上整个互联网无论大小公司使用redis占绝大部分,那么很多人使用它,那就是只是使用它,对于它的使用场景并没有去理会太多(能用就行),这篇文章来讲讲redis的基础数据结构string。
Redis有五种基础数据结构如下:string(字符串)、hash(哈希又叫字典)、set(集合又可叫无序集合)、zset(有序集合)、list(列表)。这五种数据结构涵盖了整个redis的知识点,它是redis最重要却也是最基础的知识点。当然,你也会在面试中经常遇到redis这些基础知识,三连问:它的使用场景?它的坑在哪里?如何解决?那么下面阿沐将会使用面试官与面试者的角色一一的进行问答讲解!
2 面试开场心里状态
面试官:一个带着眼镜的年轻面试官(26,27岁)缓慢从原来走过来,手里拿着我的简历,边走边看,然后时不时抬头望向我这边。
面试者:“哎呦,,,别再看我了,我就是一个一年经验的小菜鸟,看的我心发慌,腿直抖。他会问我啥呢,我该怎么流畅的回答呢?心里嘀嘀咕咕,真的我还是只是一个孩子啊,面试官放过我吧,简单点来。”
面试官:“来到面试会议室坐在我对面”。简单的介绍下自己吧?
面试者:你好面试官,我叫阿沐,河南人,毕业于郑州大学。目前工作半年,上家公司是广州xxx公司,担任后端开发工程师,主要负责搜素,购物车,产品模块等。业余时间喜欢xxxx(省略200字)......
面试官:嗯呢,看了你的简历,那么我们简单聊聊redis,看你上面写的项目大部分都是跟redis相关连。就说一说redis的string类型的一些常用的基础指令吧!
3 面试开始直进主题了
这不是小菜一碟嘛,难不住我,easy:
相关描述:string是数据结构中比较简单的一种,通常是以key和value的形式存储,它内部存储是一个字符数组形式。Redis字符串的最大长度是512M,并且的存储是动态的(意味着可以随时修改它本身的值),每次分配内存时会高出实际字符串的length,这样采用预分配冗余空间方式来减少内存的频繁分配。
常使用命令:
set - 设置key的值;若key存在,覆盖旧值无视类型,成功返回OK(注意大写OK,很多框架封装返回的值改变了,不要被误导了)
get - 获取指key值;若key不存在,返回nil(不是null,不要搞错)
mset - 同set一致,批量设置键值对,减少网络开销
mget - 同get一致,批量获取键值对,减少网络开销
incr - key值+1,不存在则先set后incr,返回integer切记值必须能标识为数字
incrby - 同incr一致,多出一个指定数字增量值
decr - 同incr原理一致,操作方式变为了减法
decrby - 同incrby原理一致,操作方式变为了减法
strlen - 获取长度len,若不存在返回0,类型为integer
setnx - 设置key值,若key不存在,则返回1,否则返回0。(若场景需要设置过期时间,不推荐使用这个命令,网络波动情况下,有可能setnx成功,expire却失败了,不是原子操作)
setex - 设置key值及过期时间,若key已存在,则替换旧值覆盖。(若需要set值且需设置过期时间且要求较高必须要有过期时间,推荐使用这个命令,设置key值+过期时间是原子操作,要么成功要么失败)
append - 对key值进行末尾追加数据,返回值是字符串长度
面试官:微微点点头,心里说道:“哎呦,功课做得挺足的哦,对使用和注意点了解的这么清楚,来自面试官的肯定,加分”。
4 string的使用场景
面试者:就知道你会问我使用场景,还好我准备的比较充足,是时候表现真正的技术啦!面带表情开始一本经常的介绍到:计数、缓存基础数据、限制请求次数、分布式下共享session、签到等等,那么我按照这些场景介绍下用法。
4.1 缓存基础数据
例如缓存登录用户的基本的缓存数据(建议hash存储,这里只是举例),用户登录成功之后,可以将用户基础数据组装json字符串设置到缓存,用户请求查询缓存层来加速读写性能降低mysql查询的压力。若用户更新则对应的刷新同步缓存,如果用户表数据字段较多,可以分成冷热数据分别存储,降低key的大小,减少网络额外开销。
<?php
// 实例化redis
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
//echo "Server is running: " . $redis->ping();
$member_info = [
'member_id' => 1001,
'member_nickname' => '我是阿沐',
'member_email' => 'lw1772363381@163.com',
'member_phone' => '12345678998',
'member_qq' => '2511221051@qq.com',
'member_level' => 120,
];
$key = sprintf('member:info:id:%u', $member_info['member_id']);
$expire = 60;
// 设置缓存并且60s过期
$result = $redis->set($key, json_encode($member_info), $expire);
//$redis->set($key, json_encode($member_info), ['nx', 'ex' => 60]);
if (!$result) {
exit( "额,设置失败了");
}
echo "哇喔,设置成功了";
-- 终端 get
127.0.0.1:6379> get member:info:id:1001
"{\"member_id\":1001,\"member_nickname\":\"\\u6211\\u662f\\u963f\\u6c90\",\"member_email\":\"lw1772363381@163.com\",\"member_phone\":\"12345678998\",\"member_qq\":\"2511221051@qq.com\",\"member_level\":120}"
127.0.0.1:6379> get member:info:id:1001
(nil) -- 记住无数据返回nil
4.2 计数器
计数功能常用于统计某一个页面的访问数据量,例如:我之前是在电商公司工作,产品会经常让我们做活动页面的浏览量以及活动内的产品详情打开量,通过pv数据分析活动中哪些产品受众。当然也会统计产品详情页视频的点击量,以及开放api的每天调用总次数等等场景。迅速在纸上手写代码:
<?php
// 实例化redis
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
//echo "Server is running: " . $redis->ping();
$key = sprintf('product:detail:pid:%u', 12345);
$result = $redis->incr($key);
// $result php中返回的是布尔类型 true or false
//redis终端
127.0.0.1:6379> incr product:detail:pid:12345
(integer) 2
127.0.0.1:6379> get product:detail:pid:12345
"2"
4.3 限制请求次数
限制访问次数,这个一般用来控制某一图谋不轨的人利用非正常手动刷接口或者恶意破坏我们的系统服务,对于一些比较敏感,比较重要的接口做上限流措施。例如:网站使用手机号注册,一般公司短信发送是使用第三方,是要给钱的,心疼啊!点一次就是1毛钱。心在滴血。尽管客户端做了校验限制,但是避免不了抓包模拟请求,这个时候我们需求对这些用户或者IP地址进行限流请求,使用incr+expire结合处理。手撕伪代码如下:
<?php
// 实例化redis
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
//echo "Server is running: " . $redis->ping();
$key = sprintf('member:login:phone:count:%u', 12345678998);
$maxCount = 10;
// 获取当前key是否有生存时间 -2未设置key -1 key 存在未设置过期时间
$ttl = $redis->ttl($key);
if ($ttl > 0) {
$times = $redis->get($key);
if ($times >= $maxCount) return true;
$result = $redis->incr($key);
if ($result) return true;
} else {
$result = $redis->set($key, 1, 60);
if ($result) return true;
}
return false;
4.4 分布式共享session
面试者:“心里暗暗庆幸着,还好我在上家公司有实习了解过,不然真的要死翘翘了“。开始吹牛皮了:系统最开始时由于用户量小一般都是单机器支撑,用户登录之后存储在session(服务器文件存储)中,那么当随着我们的业务越来越大,用户量激增到上百万,这个时候我们有单机变成了集群,使用了ngxin做负载均衡,将各个用户的请求都会被负载到各个服务器上,用来分摊机器压力,保证服务稳定。
这个时候会发现用户登录时请求随机到了某一台机器,这个时候生成了session文件,但是在用户访问其他页面又被随机到其他服务器这个时候拿不到session,就会被拦截用户登录请求,就导致用户可能要登录很多次。那这样肯定不行呀,非常不友好的用户体验会被的狗血淋头。
那么这个时候分布式缓存就可以解决这个问题啦。用Redis将用户的Session信息进行集中的管理,每次用户登录信息都从Redis中集中获取,这样完美的解决了这一问题。我们可以设置php.ini:
session.save_handler = redis -- 保存方式
session.save_path ="tcp://127.0.0.1:6379" -- 保存路径
面试官:”这家伙准备的这么充分,头头是道的,条理清晰。看来我得给他加点调料了,戳戳锐气“。面试官说道:请问负载均衡集群中的session解决方案除了使用redis管理session,还有哪些方案可以使用呢?
面试者:”心里暗香:吆喝,这是跟我杠上了,问的差不多不就行了,还非要深入去问;我这一年多经验的人,就对我这么狠,求求你饶过小弟吧!不过我初生不怕牛犊,我就是刚“。恩,既然您问到这一块了,那我也就对号入座了,说下我自己了解到的吧:
其实除了session使用redis存储这一方案,我们还可以考虑权衡一下方案:
session会话保持 - 意思是保证用户每次请求都在同一台机器上
session会话复制 - 意思是把每个应用服务器中的session信息复制同步到其它服务器节点
4.4-1 session会话保持
-1、一般使用nginx负载均衡中的upstream中的一种分配方式ip_hash或者url_hash(需要额外install) 原理就是:将用户的每一个请求ip地址hash结果集进行分配到固定一台服务器请求,这样就可以保证用户一直处于登录状态。例如下面配置:
upstream bakend { -- bakend是upstream的名称,也是proxy_pass反向代理的地址
ip_hash;
server 192.168.0.1:80;
server 192.168.0.2:80;
server 192.168.0.3:80;
}
2、使用haproxy做负载均衡的Session保持,有三种方式:
① 类似nginx的ip_hash识别算法
② cookie 识别,haproxy在用户第一次访问的后在浏览器插入一个cookie,用户下一次访问的时候浏览器就会带上这个cookie给Haproxy,进行识别分析
③ haproxy将后端服务器产生的session和后端服务器标识存在haproxy中的一张表里,请求时先查询这张表进行识别(感兴趣的可以自行谷歌查阅haproxy)
会话保持的缺点:
1、负载均衡无用武之地:本身面对大流量、高并发、我们使用nginx做负载均衡,降低服务压力;那么会话保持显然摒弃了这一点优势,每个用户规定请求某台机器,可能会出现多台机器资源空缺浪费,而部分机器压力负载过大,倾向于单机时代。
2、无法根据解决session问题:若某台服务器突然宕机或者超时链接不上,那么nginx的高可用则会踢出这台机器,请求会被重新分配到其他机器,导致用户要重新登录,这体验度简直不要不要的,差评。
4.4-1 session会话复制
会话复制是一种服务机制,用于复制存储在不同实例的会话中的数据。主要是指在集群环境下,多台服务器之间同步session文件数据,保持所有机器上的session一致,对外透明。如果用户登录之后,请求被随机分配到任何一台机器都能通过session会话拿到登录信息,或者某台机器服务故障宕机,nginx负载均衡调度器会遍历可以使用的节点,分发其他机器请求。但是session已完成同步,不会影响用户再次登录。
不推荐使用原因:session的会话复制会带来额外的性能损失,一旦session中存在比较大的对象,会导致同步缓存,性能消耗上升。
最后总结
面试者:面试官你好,上面说的这些就是我在项目中经常使用的字符串类型,并且还有它的使用场景以及场景分析和出现的问题以及解决方案。
面试官:嗯嗯,可以看出来redis的基础知识还是很扎实的,常使用命令的一些分析的很好,也很仔细。看出来你在日常工作中善于积累,"来自面试官的一个肯定的眼神"。那么我们来继续下一个问题(等待下一章节)...
从上面的面试情况来分析,基本上常使用的一些指令都涵盖到以及使用的场景介绍;并非一定是这样使用,只是我们在实际使用string类型指令要考量我们的场景,根据用户量或者其他方面来决定使用的类型,考量设置的数据量,太大影响性能消耗;那么我们下一章节继续。
宫崎骏曾说过一句话:“遇见的都是天意,拥有的都是幸运,不完美又何妨,万物皆有裂痕,那是光照进来的地方”;我们学习亦是如此,唯有面试官虐我千百遍,我才能待面试官如初恋;不完美才有提升的机会。
好了,我是阿沐,一个不想30岁就被淘汰的打工人 ⛽️ ⛽️ ⛽️ 。