英国弗兰明曾说过一句话:“不要等待运气降临,应该去努力掌握知识。”
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的大小,减少网络额外开销。
// 实例化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的每天调用总次数等等场景。迅速在纸上手写代码:
// 实例化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结合处理。手撕伪代码如下:
// 实例化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保持,有三种方式:
会话保持的缺点:
1、负载均衡无用武之地:本身面对 大流量、高并发、我们使用nginx做负载均衡,降低服务压力 ;那么会话保持显然摒弃了这一点优势,每个用户规定请求某台机器,可能会出现多台机器资源空缺浪费,而部分机器压力负载过大,倾向于单机时代。
2、无法根据解决session问题:若某台服务器突然宕机或者超时链接不上,那么nginx的高可用则会踢出这台机器,请求会被重新分配到其他机器,导致用户要重新登录,这体验度简直不要不要的,差评。
4.4-1 session会话复制
会话复制是一种服务机制,用于复制存储在不同实例的会话中的数据。主要是指在集群环境下, 多台服务器之间同步session文件数据 ,保持所有机器上的session一致,对外透明。如果用户登录之后,请求被随机分配到任何一台机器都能通过session会话拿到登录信息,或者某台机器服务故障宕机, nginx负载均衡调度器会遍历可以使用的节点 ,分发其他机器请求。但是session已完成同步,不会影响用户再次登录。
不推荐使用原因:session的会话复制会带来额外的性能损失,一旦session中存在比较大的对象,会导致同步缓存,性能消耗上升。
最后总结
面试者:面试官你好,上面说的这些就是我在项目中经常使用的字符串类型,并且还有它的使用场景以及场景分析和出现的问题以及解决方案。
面试官:嗯嗯,可以看出来redis的基础知识还是很扎实的,常使用命令的一些分析的很好,也很仔细。看出来你在日常工作中善于积累," 来自面试官的一个肯定的眼神 "。那么我们来继续下一个问题(等待下一章节)...
从上面的面试情况来分析,基本上常使用的一些指令都涵盖到以及使用的场景介绍;并非一定是这样使用,只是我们在实际使用string类型指令要考量我们的场景,根据用户量或者其他方面来决定使用的类型,考量设置的数据量,太大影响性能消耗;那么我们下一章节继续。
宫崎骏曾说过一句话:“遇见的都是天意,拥有的都是幸运,不完美又何妨,万物皆有裂痕,那是光照进来的地方”;我们学习亦是如此,唯有面试官虐我千百遍,我才能待面试官如初恋;不完美才有提升的机会。
好了,我是阿沐,一个不想30岁就被淘汰的打工人 :fuelpump:️ :fuelpump:️ :fuelpump:️ 。