热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Redis数据结构之string应用场景解析

本文介绍了Redis的基础数据结构string的应用场景,并以面试的形式进行问答讲解,帮助读者更好地理解和应用Redis。同时,描述了一位面试者的心理状态和面试官的行为。


英国弗兰明曾说过一句话:“不要等待运气降临,应该去努力掌握知识。”


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:



  1. 相关描述:string是数据结构中比较简单的一种,通常是以key和value的形式存储,它内部存储是一个字符数组形式。Redis字符串的最大长度是512M,并且的存储是动态的(意味着可以随时修改它本身的值),每次分配内存时会高出实际字符串的length,这样采用 预分配冗余空间方式来减少内存的频繁分配


  2. 常使用命令:



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存储这一方案,我们还可以考虑权衡一下方案:



  1. session会话保持- 意思是保证用户每次请求都在同一台机器上


  2. 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岁就被淘汰的打工人 :fuelpump:️ :fuelpump:️ :fuelpump:️ 。




推荐阅读
  • 本文介绍了在mac环境下使用nginx配置nodejs代理服务器的步骤,包括安装nginx、创建目录和文件、配置代理的域名和日志记录等。 ... [详细]
  • Redis API
    安装启动最简启动命令行输入验证动态参数启动配置文件启动常用配置通用命令keysbdsize计算key的总数exists判断是否存在delkeyvalue删除指定的keyvalue成 ... [详细]
  • 搭建Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境的详细步骤
    本文详细介绍了搭建Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境的步骤,包括环境说明、相关软件下载的地址以及所需的插件下载地址。 ... [详细]
  • 如何使用Java获取服务器硬件信息和磁盘负载率
    本文介绍了使用Java编程语言获取服务器硬件信息和磁盘负载率的方法。首先在远程服务器上搭建一个支持服务端语言的HTTP服务,并获取服务器的磁盘信息,并将结果输出。然后在本地使用JS编写一个AJAX脚本,远程请求服务端的程序,得到结果并展示给用户。其中还介绍了如何提取硬盘序列号的方法。 ... [详细]
  • t-io 2.0.0发布-法网天眼第一版的回顾和更新说明
    本文回顾了t-io 1.x版本的工程结构和性能数据,并介绍了t-io在码云上的成绩和用户反馈。同时,还提到了@openSeLi同学发布的t-io 30W长连接并发压力测试报告。最后,详细介绍了t-io 2.0.0版本的更新内容,包括更简洁的使用方式和内置的httpsession功能。 ... [详细]
  • 本文介绍了RPC框架Thrift的安装环境变量配置与第一个实例,讲解了RPC的概念以及如何解决跨语言、c++客户端、web服务端、远程调用等需求。Thrift开发方便上手快,性能和稳定性也不错,适合初学者学习和使用。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 解决nginx启动报错epoll_wait() reported that client prematurely closed connection的方法
    本文介绍了解决nginx启动报错epoll_wait() reported that client prematurely closed connection的方法,包括检查location配置是否正确、pass_proxy是否需要加“/”等。同时,还介绍了修改nginx的error.log日志级别为debug,以便查看详细日志信息。 ... [详细]
  • 一句话解决高并发的核心原则
    本文介绍了解决高并发的核心原则,即将用户访问请求尽量往前推,避免访问CDN、静态服务器、动态服务器、数据库和存储,从而实现高性能、高并发、高可扩展的网站架构。同时提到了Google的成功案例,以及适用于千万级别PV站和亿级PV网站的架构层次。 ... [详细]
  • 本文介绍了操作系统的定义和功能,包括操作系统的本质、用户界面以及系统调用的分类。同时还介绍了进程和线程的区别,包括进程和线程的定义和作用。 ... [详细]
  • 本文介绍了一个React Native新手在尝试将数据发布到服务器时遇到的问题,以及他的React Native代码和服务器端代码。他使用fetch方法将数据发送到服务器,但无法在服务器端读取/获取发布的数据。 ... [详细]
  • Java和JavaScript是什么关系?java跟javaScript都是编程语言,只是java跟javaScript没有什么太大关系,一个是脚本语言(前端语言),一个是面向对象 ... [详细]
  • 本文介绍了OkHttp3的基本使用和特性,包括支持HTTP/2、连接池、GZIP压缩、缓存等功能。同时还提到了OkHttp3的适用平台和源码阅读计划。文章还介绍了OkHttp3的请求/响应API的设计和使用方式,包括阻塞式的同步请求和带回调的异步请求。 ... [详细]
  • 解决Sharepoint 2013运行状况分析出现的“一个或多个服务器未响应”问题的方法
    本文介绍了解决Sharepoint 2013运行状况分析中出现的“一个或多个服务器未响应”问题的方法。对于有高要求的客户来说,系统检测问题的存在是不可接受的。文章详细描述了解决该问题的步骤,包括删除服务器、处理分布式缓存留下的记录以及使用代码等方法。同时还提供了相关关键词和错误提示信息,以帮助读者更好地理解和解决该问题。 ... [详细]
  • gitlab重置password
    ruby没怎么学,自己搭建的gitlab的rootpassword又忘了。幸好看见此帖子,试验okhttp:roland.kierkels.netgitreset-your-git ... [详细]
author-avatar
落了个小妞农_602
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有