Redis开发与运维. 2.2 字符串

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介:

2.2 字符串

字符串类型是Redis最基础的数据结构。首先键都是字符串类型,而且其他几种数据结构都是在字符串类型基础上构建的,所以字符串类型能为其他四种数据结构的学习奠定基础。如图2-7所示,字符串类型的值实际可以是字符串(简单的字符串、复杂的字符串(例如JSON、XML))、数字(整数、浮点数),甚至是二进制(图片、音频、视频),但是值最大不能超过512MB。

 

图2-7 字符串数据结构

2.2.1 命令

字符串类型的命令比较多,本小节将按照常用和不常用两个维度进行说明,但是这里常用和不常用是相对的,希望读者尽可能都去了解和掌握。

1.?常用命令

(1)设置值

set key value [ex seconds] [px milliseconds] [nx|xx]

下面操作设置键为hello,值为world的键值对,返回结果为OK代表设置成功:

127.0.0.1:6379> set hello world

OK

set命令有几个选项:

ex seconds:为键设置秒级过期时间。

px milliseconds:为键设置毫秒级过期时间。

nx:键必须不存在,才可以设置成功,用于添加。

xx:与nx相反,键必须存在,才可以设置成功,用于更新。

除了set选项,Redis还提供了setex和setnx两个命令:

setex key seconds value

setnx key value

它们的作用和ex和nx选项是一样的。下面的例子说明了set、setnx、set xx的

区别。

当前键hello不存在:

127.0.0.1:6379> exists hello

(integer) 0

设置键为hello,值为world的键值对:

127.0.0.1:6379> set hello world

OK

因为键hello已存在,所以setnx失败,返回结果为0:

127.0.0.1:6379> setnx hello redis

(integer) 0

因为键hello已存在,所以set xx成功,返回结果为OK:

127.0.0.1:6379> set hello jedis xx

OK

setnx和setxx在实际使用中有什么应用场景吗 以setnx命令为例子,由于Redis的单线程命令处理机制,如果有多个客户端同时执行setnx key value,根据setnx的特性只有一个客户端能设置成功,setnx可以作为分布式锁的一种实现方案,Redis官方给出了使用setnx实现分布式锁的方法:http://redis.io/topics/distlock。

(2)获取值

get key

下面操作获取键hello的值:

127.0.0.1:6379> get hello

"world"

如果要获取的键不存在,则返回nil(空):

127.0.0.1:6379> get not_exist_key

(nil)

(3)批量设置值

mset key value [key value ...]

下面操作通过mset命令一次性设置4个键值对:

127.0.0.1:6379> mset a 1 b 2 c 3 d 4

OK

(4)批量获取值

mget key [key ...]

下面操作批量获取了键a、b、c、d的值:

127.0.0.1:6379> mget a b c d

1) "1"

2) "2"

3) "3"

4) "4"

如果有些键不存在,那么它的值为nil(空),结果是按照传入键的顺序返回:

127.0.0.1:6379> mget a b c f

1) "1"

2) "2"

3) "3"

4) (nil)

批量操作命令可以有效提高开发效率,假如没有mget这样的命令,要执行n次get命令需要按照图2-8的方式来执行,具体耗时如下:

n次get时间 = n次网络时间 + n次命令时间

 

图2-8 n次get命令执行模型

使用mget命令后,要执行n次get命令操作只需要按照图2-9的方式来完成,具体耗时如下:

n次get时间 = 1次网络时间 + n次命令时间

 

图2-9 一次mget命令执行模型

Redis可以支撑每秒数万的读写操作,但是这指的是Redis服务端的处理能力,对于客户端来说,一次命令除了命令时间还是有网络时间,假设网络时间为1毫秒,命令时间为0.1毫秒(按照每秒处理1万条命令算),那么执行1000次get命令和1次mget命令的区别如表2-1,因为Redis的处理能力已经足够高,对于开发人员来说,网络可能会成为性能的瓶颈。

表2-1 1000次get和1次get对比表

操  作         时  间

1?000次get   1?000 × 1 + 1?000 × 0.1 = 1?100毫秒 = 1.1秒

1次met(组装了1?000个键值对)  1 × 1 + 1?000 × 0.1 = 101毫秒 = 0.101秒

 

学会使用批量操作,有助于提高业务处理效率,但是要注意的是每次批量操作所发送的命令数不是无节制的,如果数量过多可能造成Redis阻塞或者网络拥塞。

(5)计数

incr key

incr命令用于对值做自增操作,返回结果分为三种情况:

值不是整数,返回错误。

值是整数,返回自增后的结果。

键不存在,按照值为0自增,返回结果为1。

例如对一个不存在的键执行incr操作后,返回结果是1:

127.0.0.1:6379> exists key

(integer) 0

127.0.0.1:6379> incr key

(integer) 1

再次对键执行incr命令,返回结果是2:

127.0.0.1:6379> incr key

(integer) 2

如果值不是整数,那么会返回错误:

127.0.0.1:6379> set hello world

OK

127.0.0.1:6379> incr hello

(error) ERR value is not an integer or out of range

除了incr命令,Redis提供了decr(自减)、incrby(自增指定数字)、decrby(自减指定数字)、incrbyfloat(自增浮点数):

decr key

incrby key increment

decrby key decrement

incrbyfloat key increment

很多存储系统和编程语言内部使用CAS机制实现计数功能,会有一定的CPU开销,但在Redis中完全不存在这个问题,因为Redis是单线程架构,任何命令到了Redis服务端都要顺序执行。

2.?不常用命令

(1)追加值

append key value

append可以向字符串尾部追加值,例如:

127.0.0.1:6379> get key

"redis"

127.0.0.1:6379> append key world

(integer) 10

127.0.0.1:6379> get key

"redisworld"

(2)字符串长度

strlen key

例如,当前值为redisworld,所以返回值为10:

127.0.0.1:6379> get key

"redisworld"

127.0.0.1:6379> strlen key

(integer) 10

下面操作返回结果为6,因为每个中文占用3个字节:

127.0.0.1:6379> set hello "世界"

OK

127.0.0.1:6379> strlen hello

(integer) 6

(3)设置并返回原值

getset key value

getset和set一样会设置值,但是不同的是,它同时会返回键原来的值,例如:

127.0.0.1:6379> getset hello world

(nil)

127.0.0.1:6379> getset hello redis

"world"

(4)设置指定位置的字符

setrange key offeset value

下面操作将值由pest变为了best:

127.0.0.1:6379> set redis pest

OK

127.0.0.1:6379> setrange redis 0 b

(integer) 4

127.0.0.1:6379> get redis

"best"

(5)获取部分字符串

getrange key start end

start和end分别是开始和结束的偏移量,偏移量从0开始计算,例如下面操作获取了值best的前两个字符。

127.0.0.1:6379> getrange redis 0 1

"be"

表2-2是字符串类型命令的时间复杂度,开发人员可以参考此表,结合自身业务需求和数据大小选择适合的命令。

表2-2 字符串类型命令时间复杂度

命  令         时间复杂度

set key value    O(1)

get key     O(1)?

del key [key ...]         O(k),k是键的个数

mset key value [key value ...]  O(k),k是键的个数

mget key [key ...]     O(k),k是键的个数

incr key    O(1)

decr key   O(1)

incrby key increment        O(1)

decrby key decrement     O(1)

incrbyfloat key increment        O(1)

append key value     O(1)

strlen key          O(1)

setrange key offset value         O(1)

getrange key start end    O(n),n是字符串长度,由于获取字符串非常快,所以如果字符串不是很长,可以视同为O(1)

 

2.2.2 内部编码

字符串类型的内部编码有3种:

int:8个字节的长整型。

embstr:小于等于39个字节的字符串。

raw:大于39个字节的字符串。

Redis会根据当前值的类型和长度决定使用哪种内部编码实现。

整数类型示例如下:

127.0.0.1:6379> set key 8653

OK

127.0.0.1:6379> object encoding key

"int"

短字符串示例如下:

#小于等于39个字节的字符串:embstr

127.0.0.1:6379> set key "hello,world"

OK

127.0.0.1:6379> object encoding key

"embstr"

长字符串示例如下:

#大于39个字节的字符串:raw

127.0.0.1:6379> set key "one string greater than 39 byte........."

OK

127.0.0.1:6379> object encoding key

"raw"

127.0.0.1:6379> strlen key

(integer) 40

有关字符串类型的内存优化技巧将在8.3节详细介绍。

2.2.3 典型使用场景

1.?缓存功能

图2-10是比较典型的缓存使用场景,其中Redis作为缓存层,MySQL作为存储层,绝大部分请求的数据都是从Redis中获取。由于Redis具有支撑高并发的特性,所以缓存通常能起到加速读写和降低后端压力的作用。

下面伪代码模拟了图2-10的访问过程:

1)该函数用于获取用户的基础信息:

UserInfo getUserInfo(long id){

...

}

2)首先从Redis获取用户信息:

// 定义键

userRedisKey = "user:info:" + id;

// 从Redis获取值

value = redis.get(userRedisKey);

if (value != null) {

    // 将值进行反序列化为UserInfo并返回结果

    userInfo = deserialize(value);

    return userInfo;

}

与MySQL等关系型数据库不同的是,Redis没有命令空间,而且也没有对键名有强制要求(除了不能使用一些特殊字符)。但设计合理的键名,有利于防止键冲突和项目的可维护性,比较推荐的方式是使用“业务名:对象名: id : [属性]”作为键名(也可以不是分号)。例如MySQL的数据库名为vs,用户表名为user,那么对应的键可以用"vs:user:1","vs:user:1:name"来表示,如果当前Redis只被一个业务使用,甚至可以去掉“vs:”。如果键名比较长,例如“user:{uid}:friends:messages:{mid}”,可以在能描述键含义的前提下适当减少键的长度,例如变为“u:{uid}:fr:m:{mid}”,从而减少由于键过长的内存浪费。

3)如果没有从Redis获取到用户信息,需要从MySQL中进行获取,并将结果回写到Redis,添加1小时(3600秒)过期时间:

// 从MySQL获取用户信息

userInfo = mysql.get(id);

// 将userInfo序列化,并存入Redis

redis.setex(userRedisKey, 3600, serialize(userInfo));

// 返回结果

return userInfo

整个功能的伪代码如下:

UserInfo getUserInfo(long id){

    userRedisKey = "user:info:" + id

    value = redis.get(userRedisKey);

    UserInfo userInfo;?

    if (value != null) {

        userInfo = deserialize(value);

    } else {

        userInfo = mysql.get(id);

        if (userInfo != null)

            redis.setex(userRedisKey, 3600, serialize(userInfo));

    }

    return userInfo;

}

2.?计数

许多应用都会使用Redis作为计数的基础工具,它可以实现快速计数、查询缓存的功能,同时数据可以异步落地到其他数据源。例如笔者所在团队的视频播放数系统就是使用Redis作为视频播放数计数的基础组件,用户每播放一次视频,相应的视频播放数就会自增1:

long incrVideoCounter(long id) {

    key = "video:playCount:" + id;

    return redis.incr(key);

}

实际上一个真实的计数系统要考虑的问题会很多:防作弊、按照不同维度计数,数据持久化到底层数据源等。

3.?共享Session

如图2-11所示,一个分布式Web服务将用户的Session信息(例如用户登录信息)保存在各自服务器中,这样会造成一个问题,出于负载均衡的考虑,分布式服务会将用户的访问均衡到不同服务器上,用户刷新一次访问可能会发现需要重新登录,这个问题是用户无法容忍的。

 

图2-11 Session分散管理

为了解决这个问题,可以使用Redis将用户的Session进行集中管理,如图2-12所示,在这种模式下只要保证Redis是高可用和扩展性的,每次用户更新或者查询登录信息都直接从Redis中集中获取。

4.?限速

很多应用出于安全的考虑,会在每次进行登录时,让用户输入手机验证码,从而确定是否是用户本人。但是为了短信接口不被频繁访问,会限制用户每分钟获取验证码的频率,例如一分钟不能超过5次,如图2-13所示。

 

图2-13 短信验证码限速

此功能可以使用Redis来实现,下面的伪代码给出了基本实现思路:

phoneNum = "138xxxxxxxx";

key = "shortMsg:limit:" + phoneNum;

// SET key value EX 60 NX

isExists = redis.set(key,1,"EX 60","NX");

if(isExists != null || redis.incr(key) <=5){

    // 通过

}else{

    // 限速

}

上述就是利用Redis实现了限速功能,例如一些网站限制一个IP地址不能在一秒钟之内访问超过n次也可以采用类似的思路。

除了上面介绍的几种使用场景,字符串还有非常多的适用场景,开发人员可以结合字符串提供的相应命令充分发挥自己的想象力。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
15天前
|
XML JSON NoSQL
Redis的常用数据结构之字符串类型
Redis的常用数据结构之字符串类型
19 0
|
1月前
|
NoSQL 安全 Linux
Redis 字符串:SDS
Redis 字符串:SDS
37 0
|
2月前
|
存储 NoSQL 5G
redis优化编码之字符串
Redis数据结构之字符串
41 2
redis优化编码之字符串
|
1月前
|
人工智能 运维 监控
构建高性能微服务架构:现代后端开发的挑战与策略构建高效自动化运维系统的关键策略
【2月更文挑战第30天】 随着企业应用的复杂性增加,传统的单体应用架构已经难以满足快速迭代和高可用性的需求。微服务架构作为解决方案,以其服务的细粒度、独立性和弹性而受到青睐。本文将深入探讨如何构建一个高性能的微服务系统,包括关键的设计原则、常用的技术栈选择以及性能优化的最佳实践。我们将分析微服务在处理分布式事务、数据一致性以及服务发现等方面的挑战,并提出相应的解决策略。通过实例分析和案例研究,我们的目标是为后端开发人员提供一套实用的指南,帮助他们构建出既能快速响应市场变化,又能保持高效率和稳定性的微服务系统。 【2月更文挑战第30天】随着信息技术的飞速发展,企业对于信息系统的稳定性和效率要求
|
7天前
|
人工智能 前端开发 Java
Java语言开发的AI智慧导诊系统源码springboot+redis 3D互联网智导诊系统源码
智慧导诊解决盲目就诊问题,减轻分诊工作压力。降低挂错号比例,优化就诊流程,有效提高线上线下医疗机构接诊效率。可通过人体画像选择症状部位,了解对应病症信息和推荐就医科室。
146 10
|
13天前
|
NoSQL 关系型数据库 MySQL
开发者福音:用IDEA和Iedis2加速Redis开发与调试
开发者福音:用IDEA和Iedis2加速Redis开发与调试
31 0
开发者福音:用IDEA和Iedis2加速Redis开发与调试
|
14天前
|
运维 NoSQL 算法
Java开发-深入理解Redis Cluster的工作原理
综上所述,Redis Cluster通过数据分片、节点发现、主从复制、数据迁移、故障检测和客户端路由等机制,实现了一个分布式的、高可用的Redis解决方案。它允许数据分布在多个节点上,提供了自动故障转移和读写分离的功能,适用于需要大规模、高性能、高可用性的应用场景。
16 0
|
22天前
|
存储 消息中间件 缓存
Redis 字符串:用一串数据解决多种问题
Redis 字符串:用一串数据解决多种问题
|
1月前
|
人工智能 JSON 运维
AI大模型运维开发探索第三篇:深入浅出运维智能体
大模型出现伊始,我们就在SREWorks开源社区征集相关的实验案例。玦离同学提供了面向大数据HDFS集群的智能体案例,非常好地完成了运维诊断的目标。于是基于这一系列的实验和探索。本文详细介绍智能体在运维诊断中的应用探索。
|
2月前
|
Kubernetes Linux 开发工具
容器开发运维人员的 Linux 操作机配置优化建议
容器开发运维人员的 Linux 操作机配置优化建议

热门文章

最新文章