string 字符串类型是 Redis 中最基础的数据类型,关于字符串类型需要注意以下几点:
a)SET:
SET 命令的作用是将 string 类型的 value 设置到 Redis 中。如果 key 在设置之前已经存在了,无论原来的数据类型是什么,都会覆盖原来的 value,并且 key 设置的 TTL 也会失效。
SET 的语法:
SET key value [expiration EX seconds|PX milliseconds] [NX|XX]
选项说明:
EX seconds
:以秒为单位设置 key 的过期时间,相当于 EXPIRE;PX milliseconds
:以毫秒为单位设置 key 的过期时间,相当于 PEXPIRE;NX
:只有在 key 不存在的时候才进行设置,如果 key 之前已经存在了,则设置不会执行;XX
:只有在 key 存在的时候才进行设置,通过 key 之间不存在,则设置不会执行。注意事项:
[]
之内的选项只能选择一个,[]
之间的选项则可以同时存在。即 EX
和 PX
选项不能同时存在,而 NX
和 XX
不能同时存在,因为它们之间是互斥关系。返回值:
nil
。使用示例:
b)GET
GET 命令的作用是获取 key 对应 的 value。如果 key 不存在,就返回 nil
,如果 value 的数据类型不是 string,就会报错。
MSET 的作用是一次设置多个键值对;而 MGET 的作用是一次性获取多个 key 的 value,如果对应的 key 不存在或者对应的数据类型不是 string,返回 nil
。
使用案例:
a)MSET:
注意事项:
在前面的文章提到过,Redis 是处理任务是单线程的,并且 Redis 的客户端和服务端之间是通过网络进行通信的,因此要设置或者获取多个key 的时候,建议同时进行操作,以减少网络请求的次数。
a)SETNX 命令的作用是在如果 key 不存在则设置,否则就不设置,例如:
b)SETEX 命令的作用是在设置 key 的时候指定秒级的过期时间
语法:
SETEX key seconds value
c)PSETEX 命令的作用是在设置 key 的时候指定毫秒级的过期时间
语法:
SETEX key milliseconds value
INCR
INCR
命令的作用是将 key 对应的 string 表示的数字加一。
INCR
执行成功,则返回加一后的值,否则返回相应错误信息。INCRBY
INCRBY
命令的作用是为 key 对应的 string 表示的整数加上一个指定的整数。
INCRBY
执行成功,则返回相加后的值,否则返回相应错误信息。使用示例:
DECR
DECR
命令的作用是将 key 对应的 string 表示的数字减一。
DECR
执行成功,则返回减一后的值,否则返回相应错误信息。使用示例:
DECRBY
DECRBY
命令的作用是为 key 对应的 string 表示的整数减去一个指定的整数。
DECRBY
执行成功,则返回相减后的值,否则返回相应错误信息;DECRBY
指定要减去的是一个负数,则表示加上这个数。INCRBYFLOAT
命令的作用是将 key 对应的 string 表示的浮点数加上指定的数。
语法:
INCRBYFLOAT key increment
INCRBYFLOAT
执行成功,则返回运算结果,否则返回错误信息;APPEND
命令的作用是中 key 对应的 string 后面追加字符串。
语法:
APPEND key value
APPEND
执行成功,则返回最终字符串的长度。GETRANGE
命令的作用是截取key 对应的 string 中的子串。
语法:
GETRANGE key start end
使用示例:
SETRANGE
命令的作用是覆盖从指定位置开始的 key 对应 string 中的一部分。
语法:
SETRANGE key offset value
STRLEN
命令的作用是获取 key 对应的 string 的长度。
语法:
STRLEN key
使用示例:
以下是 Redis 中与 string 类型相关的命令的总结,包括命令、作用和时间复杂度:
命令 | 作用 | 时间复杂度 |
---|---|---|
SET | 设置key的值为指定字符串 | O(1) |
GET | 获取key对应的字符串值 | O(1) |
MSET | 批量设置多个键值对 | O(N)(N为键值对数量) |
MGET | 批量获取多个key的值 | O(N)(N为键的数量) |
SETNX | 仅当key不存在时设置值 | O(1) |
SETEX | 设置key的值和过期时间(秒) | O(1) |
PSETEX | 设置key的值和过期时间(毫秒) | O(1) |
INCR | 将key对应的数字值加一 | O(1) |
INCRBY | 将key对应的数字值加上指定整数 | O(1) |
DECR | 将key对应的数字值减一 | O(1) |
DECRBY | 将key对应的数字值减去指定整数 | O(1) |
INCRBYFLOAT | 将key对应的浮点数值加上指定浮点数 | O(1) |
APPEND | 在key对应的字符串值后追加字符串 | O(1) |
GETRANGE | 获取key对应的字符串的子串 | O(N)(N为子串长度) |
SETRANGE | 覆盖key对应字符串的部分内容 | O(N)(N为替换字符串长度) |
STRLEN | 获取key对应的字符串的长度 | O(1) |
在Redis中,字符串(string)类型的值可以使用多种不同的编码方式存储,具体的编码方式是根据数据的内容和大小来动态选择的,以最大程度地节省内存和提高性能。以下是Redis中字符串类型的常见编码方式:
RAW(简单动态字符串):这是最常见的字符串编码方式。它用于存储较短的字符串,长度不超过字符串编码结构的限制。这种编码方式不会对字符串进行压缩,因此在存储较小的字符串时效率高。
INT(整数编码):当一个字符串可以被解释为整数时,Redis会将其编码为整数,以节省内存。整数编码分为以下几种子编码方式:
EMBSTR(嵌套字符串编码):用于存储较短的字符串,但与RAW不同的是,EMBSTR的编码方式将字符串长度也一并存储在编码结构中,以节省内存。
RAW和EMBSTR共享编码:在某些情况下,Redis会使用一种特殊的编码方式,该方式可以共享RAW和EMBSTR编码方式的优点。这意味着它既可以存储较短的字符串,又可以高效地存储较大的字符串。
SDS(简单动态字符串):SDS是一种用于表示字符串的数据结构,它具有动态大小,可以在不需要重新分配内存的情况下进行扩展。这种编码方式用于存储较大的字符串,以节省内存和提高性能。
需要注意的是,Redis会根据字符串的内容和大小动态选择适当的编码方式,因此开发者无需手动指定编码方式。这种动态编码方式使得Redis能够在不同情况下充分利用内存,提高效率。
可以使用Redis的OBJECT ENCODING
命令来查看特定键的编码方式,例如:
OBJECT ENCODING mykey
这将返回键mykey
的编码方式。
例如:
由于 Redis 速度快的特点,因此常用于缓存功能。比较典型的缓存使用场景就是,Redis 作为缓冲层,MySQL 作为存储层,绝大部分请
求的数据都是从 Redis 中获取。由于 Redis 具有支撑高并发的特性,所以缓存通常能起到加速读写和降低后端压力的作用。
Redis + MySQL 组成的缓存存储架构:
下面通过伪代码模拟了上图的业务数据的访问过程:
1)假设业务是根据用户的 uid 获取用户信息
UserInfo getUserInfo(long uid) {
...
}
2)首先从 Redis 获取用户信息,我们假设用户信息保存在 “user:info:” 对应的键中
// 根据 uid 得到 Redis 的键
String key = "user:info:" + uid;
// 尝试从 Redis 中获取对应的值
String value = Redis 执⾏命令:get key;
// 如果缓存命中(hit)
if (value != null) {
// 假设用户信息按照 JSON 格式存储
UserInfo userInfo = JSON 反序列化(value);
return userInfo;
}
3)如果没有从 Redis 中得到用户信息或者缓存未命中(miss),则进一步从 MySQL 中获取对应的信息,随后写入缓存并返回
// 如果缓存未命中(miss)
if (value == null) {
// 从数据库中,根据 uid 获取⽤⼾信息
UserInfo userInfo = MySQL 执⾏ SQL:select * from user_info where uid = <uid>
// 如果表中没有 uid 对应的⽤⼾信息
if (userInfo == null) {
// 响应 404
return null;
}
// 将用户信息序列化成 JSON 格式
String value = JSON 序列化(userInfo);
// 写⼊缓存,为了防⽌数据腐烂(rot),设置过期时间为 1 ⼩时(3600 秒)
Redis 执⾏命令:set key value ex 3600
// 返回用户信息
return userInfo;
}
通过增加缓存功能,在理想情况下,每个用户信息,一个小时期间只会有一次 MySQL 查询存在,极大地提升了查询效率,同时也降低了MySQL 的访问数。
PS: Redis 键名的设计原则和示例
- 业务名前缀:将键名以业务名开头,以便将不同业务或项目的键隔离开来。这有助于确保不同的应用程序或团队之间的键不会发生冲突。
示例:
user_info:6379
order_info:1234- 对象名:指定键名中的对象名,以描述存储的内容是什么。这有助于清晰地了解键存储的数据类型或业务对象。
示例:
user_profile:6379
product_catalog:5678- 唯一标识:如果需要,可以在键名中包含唯一标识符,以便更具体地标识存储的数据。这有助于将数据细分为不同的实体。
示例:
user:12345:profile
product:7890:details- 属性:如果数据具有多个属性,可以在键名中包含属性名称,以进一步细化键的用途。
示例:
user:12345:profile:name
product:7890:details:price- 键名缩写:如果键名变得过长,可以使用缩写来减少键名的长度,但要确保缩写对团队内部是清晰可理解的。
示例:
u:12345:pr:n(缩写版)通过使用这种键名命名空间约定,可以使Redis键更有组织,易于维护和管理。它还可以帮助避免键名冲突,特别是在多个应用程序或团队使用同一个Redis实例的情况下。但请注意,键名过长可能会导致性能下降,所以需要权衡键名的可读性和性能需求。
计数(Counter)功能是Redis中常见且有用的功能之一,它可以用来快速记录和查询某个对象的计数值。这种功能在许多应用中都非常有用,例如网站的访问计数、点赞数、评论数、播放次数等。
伪代码:
// 在 Redis 中统计某视频的播放次数
long incrVideoCounter(long vid) {
key = "video:" + vid;
long count = Redis 执⾏命令:incr key
return counter;
}
然而,在实际开发一个成熟、稳定的计数系统时,会面临许多挑战和复杂性。以下是一些可能需要应对的挑战和考虑因素:
- 防作弊:确保计数系统不容易被恶意操纵是至关重要的。常见的防作弊措施包括限制每个用户或IP地址的计数速率,使用验证码或令牌来验证用户行为等。
- 按不同维度计数:有时需要按照不同的维度进行计数,例如按时间、地理位置、用户类型等。为了实现这种灵活性,需要设计适应性强的计数系统架构。
- 避免单点问题:单点故障可能会导致计数系统的不可用性。为了确保高可用性,可以考虑使用Redis的主从复制或集群模式,或者使用其他分布式计数系统。
- 数据持久化:Redis默认将数据存储在内存中,但为了持久化数据,可以将数据定期快照到磁盘或使用持久化选项,如AOF(Append-Only File)。
- 性能优化:处理大量计数请求可能会对性能造成压力。需要优化Redis配置、考虑使用缓存层、分布式计数系统或负载均衡策略,以应对高负载情况。
- 并发控制:并发操作可能导致计数不一致。要确保计数的原子性,可以使用Redis的事务或乐观锁等技术。
- 监控和日志:建立监控和日志系统,以实时追踪计数系统的性能和运行状况,以及检测潜在的问题。
- 容量规划:考虑计数系统的容量规划,包括数据存储需求、内存和硬盘空间等,以支持未来的增长。
- 数据清理:定期清理不再需要的计数数据,以防止数据膨胀和内存占用过多。
总之,开发一个真实的计数系统是一个复杂的任务,需要考虑众多因素。选择合适的技术栈、设计良好的架构、实施安全性和防作弊措施、确保高可用性以及建立监控和维护策略都是成功实现计数系统的重要步骤。这些挑战需要仔细的规划和实施,以满足特定项目的需求。
在一个分布式 Web 服务中,用户的会话信息通常存储在各自的服务器上,这包括用户的登录状态和其他会话相关数据。然而,由于负载均衡的需要,用户的请求会被分发到不同的服务器上,而不同服务器上的会话数据并不共享。
这就导致了一个问题:如果用户的请求被均衡到不同的服务器上,用户在刷新页面或发送下一个请求时可能会发现自己需要重新登录,这种体验对用户来说是不可接受的。
例如下图所示的 Session 分散储存:
为了解决这个问题,可以使用Redis将用户的 Session 信息进行集中管理。在这种模式下,只要确保 Redis 是高可用和可扩展的,不论用户被均衡到哪台 Web 服务器上,都可以集中从 Redis 中查询、更新Session信息。
为了增强用户登录的安全性,许多应用会采取以下步骤:
这种流程可以有效地降低恶意登录和滥发验证码的风险,同时保障用户的账户安全。
String 发送验证码(phoneNumber) {
key = "shortMsg:limit:" + phoneNumber;
// 设置过期时间为 1 分钟(60 秒)
// 使用 NX,只在不存在 key 时才能设置成功
bool r = Redis 执行命令:set key 1 ex 60 nx
if (r == false) {
// 说明之前设置过该手机号的验证码了
long c = Redis 执行命令:incr key
if (c > 5) {
// 说明超过了一分钟 5 次的限制了
// 限制发送
return null;
}
}
// 说明要么之前没有设置过手机号的验证码;要么次数没有超过 5 次
String validationCode = 生成随机的 6 位数的验证码();
validationKey = "validation:" + phoneNumber;
// 验证码 5 分钟(300 秒)内有效
Redis 执行命令:set validationKey validationCode ex 300;
// 返回验证码,随后通过手机短信发送给用户
return validationCode;
}
// 验证用户输入的验证码是否正确
bool 验证验证码(phoneNumber, validationCode) {
validationKey = "validation:" + phoneNumber;
String value = Redis 执行命令:get validationKey;
if (value == null) {
// 说明没有这个手机号的验证码记录,验证失败
return false;
}
if (value == validationCode) {
return true;
} else {
return false;
}
}
以上介绍了使用 Redis 的字符串数据类型在缓存、计数、会话管理和手机验证码等场景中的应用。然而,Redis 的字符串类型的适用场景远不止这些,开发人员可以根据字符串类型的特点以及提供的命令,充分发挥自己的创造力和想象力,将其应用到各种业务场景中。