【Redis】Redis初阶

 🔥个人主页: 中草药


 一、认识Redis

Redis(Remote Dictionary Server)是一个开源的、基于内存的键值对存储数据库,支持持久化、网络化访问,并提供多种数据结构操作,用作数据缓存。它由Salvatore Sanfilippo开发,凭借其单线程模型高效数据结构,成为高并发场景下的首选解决方案。

核心特性:

  1. 内存存储:数据主要存储在内存中(内存为主,硬盘为辅),读写速度远超传统磁盘数据库,如MySQL(最大特点快)。

  2. 丰富的数据结构:支持String、List、Hash、Set、Zset五种基础类型,以及BitMap、HyperLogLog等扩展类型。

  3. 持久化机制:提供RDB(快照)和AOF(追加日志)两种方式,确保数据安全。

  4. 高可用与扩展:支持主从复制、哨兵监控、集群分片,满足分布式需求。


二、Redis的安装与基础使用

Linux安装步骤

Ubuntu

apt search redis
apt install redis

这里的127.0.0.1的ip意味着只能由当前主机上的客户端访问,无法跨主机访问,我们要手动修改

修改127.0.0.1 成 0.0.0.0

修改protected-mode yes 成 no

修改完成后,重新启动服务器

service redis-server restart

用客户端连接服务器 redis-cli

至此Ubuntu上连接完成

Centos

如果是Centos8,yum仓库中默认的redis版本就是5,直接yum install即可

如果是Centos7,yum仓库中默认的redis版本就是3,版本较旧,此时需要我们安装额外的软件源

yum install centos-release-scl-rh
yum install rh-redis5-redis

建立符号链接

cd /usr/bin
ln -s /opt/rh/rh-redis5/root/usr/bin/redis-cli ./redis-cli
ln -s /opt/rh/rh-redis5/root/usr/bin/redis-sentinel ./redis-sentinel
ln -s /opt/rh/rh-redis5/root/usr/bin/redis-server ./redis-server

cd /etc/
ln -s /etc/opt/rh/rh-redis5/ ./redis

设置工作目录 

mkdir -p /var/lib/redis

设置日志目录

mkdir -p /var/log/redis/

将二进制字节对应到汉字 redis-cli raw

通用命令使用

redis命令很多可以参考-Redis官网 不区分大小写

1、set 设置key和value (set key value [expiration EX seconds | PX milliseconds] [NX|XX]

        NX 如果key不存在才设置,存在则返回nil

        XX 如果key存在才设置,不存在返回nil 

2、get 根据key获取value

        get仅支持字符串类型的value


3、keys 用来查询服务器上匹配的key,通过一些通配符来描述key的模样,匹配上述摸样的key

通配符

  • ? 匹配任意一个字符
  • *   匹配0个或多个字符
  • [ae] 只能匹配ae字符
  • [^a} 排除a字符
  • [a-b] a~b范围区间的字符,包含边界

举例

h?llo 匹配 hello , hallo hxllo

h*llo 匹配 hllo heeeello

h[ae]llo 匹配 hello hallo 但不匹配 hillo

h[^e]llo 匹配 hallo , hbllo , ... 但不匹配 hello

h[a-b]llo 匹配 hallo hbllo

keys命令的时间复杂度是O(n),因此在生成环境上,一般是禁止keys命令,尤其是keys *-查询Redis中所有的key,Redis是一个单线程服务器,防止造成阻塞,无法对其他客户端提供服务


4、exists 判断key是否存在 ( 时间复杂度0(1) ) 

5、del 删除key  ( 时间复杂度0(1) )

6、expire key 设置过期时间 时间单位 秒s  (pexpire ms)

7、ttl 查询过期时间还剩多少 (time to live) -1 表示没有关联过期时间  -2 表示key不存在

8、type 返回key对应的数据类型

数据库管理

在redis之中也有database这样的概念,只不过不像mysql那样,redis中的数据库是现成的,用户不能创建新的数据库,也不能删除已有的数据库,默认的Redis提供了16个数据库,0-15,这16个数据库中的数据是隔离的,相互之间不会产生影响,默认使用的数据库是0号

select dbIndex 切换数据库

flushdb 删除当前数据库中的所有key

flushall 删除所有数据库中的所有key


Redis的高性能秘密

1. 单线程模型

  • 优势:避免多线程竞争和锁开销,通过I/O多路复用(如epoll)处理并发连接10。

  • 瓶颈:CPU密集型操作可能阻塞线程,需避免长耗时命令(如KEYS *)8。

2. 持久化机制

  • RDB:定时生成快照,适合备份和恢复,但可能丢失最后一次快照后的数据4。

  • AOF:记录所有写操作日志,支持每秒同步(appendfsync everysec),数据安全性更高8。

Redis虽然是单线程模型,为啥效率这么高,速度这么快呢?

这个块是相对的,参照物是数据库

1. 基于内存操作,读写速度极快

        Redis 所有数据存储在内存中(内存读写速度比磁盘快几个数量级),避免了传统数据库频繁磁盘 I/O 导致的性能瓶颈。

2、redis的核心功能相较于数据库(MySQL)更简单 

        数据库对于数据的增删改查都有更复杂的功能支持,这些功能会花费更多的开销,redis提供的功能相较于数据库更少

3. 单线程避免了多线程的锁竞争和上下文切换

  • 无锁设计:单线程无需处理多线程间的锁竞争问题,减少了复杂锁机制带来的性能损耗。
  • 上下文切换成本低:单线程模型只需处理一个任务队列,避免了多线程频繁切换上下文(Context Switching)的开销。

4. I/O 多路复用与非阻塞模型

Redis 通过 I/O 多路复用(如 Linux 的 epoll)实现高效的网络事件监听机制:

  • 单线程监听多个连接:主线程通过一个事件循环(Event Loop)同时监听大量客户端的连接请求和命令操作。
  • 非阻塞 I/O:读写网络数据时采用非阻塞模式,避免因某个客户端响应慢而阻塞整个进程。

这种模型将 CPU 密集任务(命令处理)与 I/O 密集任务(网络通信)解耦,最大化主线程的执行效率。

5. 高效数据结构与优化

Redis 内置多种高性能数据结构(如简单动态字符串、跳跃表、压缩列表等),通过算法优化减少操作的时间复杂度:

  • 例如:哈希表使用渐进式 Rehash 避免迁移时的卡顿;有序集合结合哈希表与跳表实现 O(1) 查找和 O(logN) 范围查询。

//6. 多线程的辅助优化(Redis 6.0+)

虽然主线程仍是单线程,但 Redis 6.0 后引入了多线程处理网络 I/O(如接收请求、协议解析)等辅助任务,进一步提升高并发下的吞吐量。

三、redis中key的过期策略

Redis 中 Key 的过期策略是为了自动清理不再需要的键,避免内存浪费。其核心机制包括 惰性删除 和 定期删除,同时结合 内存淘汰策略 应对极端情况。

过期策略的核心机制

1. 惰性删除 (Lazy Expiration)

  • 原理:当客户端尝试访问一个 Key 时,Redis 会先检查该 Key 是否已过期。若过期则立即删除,返回 nil;否则正常返回数据。

  • 优点:对 CPU 友好,只在访问时触发删除操作,无额外资源消耗。

  • 缺点:若大量 Key 过期后未被访问,会长期占用内存(内存泄漏风险)。

2. 定期删除 (Periodic Expiration)

  • 原理:Redis 每隔一段时间(默认每秒 10 次)随机抽取部分设置了过期时间的 Key,删除其中已过期的。具体流程:

    1. 从过期字典中随机选择 20 个 Key。

    2. 删除其中已过期的 Key。

    3. 若超过 25% 的 Key 已过期,则重复该过程(最多持续 25ms,避免阻塞)。

  • 优点:通过抽样减少遍历开销,平衡内存和 CPU 使用。

  • 缺点:无法完全实时删除所有过期 Key,需依赖惰性删除兜底。

内存淘汰策略(补充机制)

当内存不足时(maxmemory 限制被触发),Redis 会根据配置的策略删除 Key,即使它们未过期。常见策略包括:

  • noeviction(默认):拒绝写入新数据,读请求正常。

  • volatile-ttl:优先删除剩余存活时间(TTL)最短的 Key。

  • volatile-lru:从过期 Key 中删除最近最少使用(LRU)的 Key。

  • volatile-lfu:从过期 Key 中删除最不频繁使用(LFU)的 Key。

  • allkeys-lru:从所有 Key 中删除 LRU 的 Key。

  • allkeys-lfu:从所有 Key 中删除 LFU 的 Key。

  • volatile-random / allkeys-random:随机删除过期或所有 Key。

四、数据结构

Redis 对外暴露的数据结构(API)包括:

  • String(字符串)

  • List(列表)

  • Hash(哈希表)

  • Set(集合)

  • Sorted Set(有序集合)

  • Bitmaps(位图)

  • HyperLogLog(基数统计)

  • Stream(流,用于消息队列)

  • Geospatial(地理空间索引)

但底层实现更为复杂,Redis 通过不同的编码(encoding)优化内存和性能。


String(字符串)

  • 用途:缓存、计数器、分布式锁、二进制存储(如图片)。

  • 底层实现

    • int:(8个字节)整数时直接存储为长整型,redis有时可以用来实现“计数”等功能

    • embstr:短字符串(≤39字节)使用连续内存的紧凑结构。

    • raw:(大于39)长字符串使用动态字符串(SDS,Simple Dynamic String)。

  • 特点

    • 最大支持 512MB

    • SDS 支持高效追加操作(O(1) 时间复杂度),预分配内存减少碎片。

命令

命令执行效果时间复杂度
set key value设置 key 的值是 valueO(k), k 是键个数
get key获取 key 的值O(1)
del key [key ...]删除指定的 keyO(k), k 是键个数
mset key value [key value ...]批量设置指定的 key 和 valueO(k), k 是键个数
mget key [key ...]批量获取 key 的值O(k), k 是键个数
incr key指定的 key 的值 +1O(1)
decr key指定的 key 的值 -1O(1)
incrby key n指定的 key 的值 +nO(1)
decrby key n指定的 key 的值 -nO(1)
incrbyfloat key n指定的 key 的值 +nO(1)
append key value指定的 key 的值追加 valueO(1)
strlen key获取指定 key 的值的长度O(1)
setrange key offset value覆盖指定 key 的从 offset 开始的部分值,如果是不存在的字节则会填充为0x00O(n), n 是字符串长度, 通常视为 O(1)
getrange key start end获取指定 key 的从 start 到 end 的部分值,O(n), n 是字符串长度, 通常视为 O(1)

典型应用场景

1、缓存功能

        减少数据库压力,加速数据访问。以下是Redis + MySQL 组成的缓存存储架构

        应用服务器在访问数据的时候,先查询Redis,如果Redis上数据存在,直接从Redis上读取数据交给应用服务器,如果不存在再读取MySQL,并同时将数据也写入Redis,Redis这样的缓存,经常用来存储“热点”数据。

        此时,随着数据的范根,Redis上的key越来越多,这里主要有两方面策略,1、设置过期策略2、Redis在内存不足的时候,提供了淘汰策略(后面会有介绍)

        与 MySQL 等关系型数据库不同的是,Redis 没有表、字段这种命名空间,而且也没有对键名有强制要求(除了不能使用一些特殊字符)。但设计合理的键名,有利于防止键冲突和项目的可维护性,比较推荐的方式是使用 "业务名:对象名:唯⼀标识:属性" 作为键名。
        例如 MySQL 的数据库名为 vs,用户表名为 user_info,那么对应的键可以使
"vs:user_info:6379"、"vs:user_info:6379:name" 来标识,如果当前 Redis 只会被⼀个业务
使用,可以省略业务名 "vs:"。如果键名过程,则可以使用团队内部都认同的缩写替代,例如
"user:6379:friends:messages:5217" 可以被 "u:6379:fr:m:5217" 代替。毕竟键名过长,还
是会导致 Redis 的性能明显下降的。

2、计数功能

        实时统计高频操作(如点赞、浏览、转发),替代数据库的直接计数。

 Redis并不擅长数据统计,比如统计播放量前100的视频有那些,基于Redis就会很麻烦。并且实际上要开发一个成熟,稳定的真实计数系统,要面临的挑战远不止这么简单:防作弊、按照不同维度计数,避免单点问题,数据持久化到底层数据源等

3、共享会话

存储用户登录态,实现分布式系统的会话共享。

        如下图所示,⼀个分布式 Web 服务将用户的 Session 信息(例如用户登录信息)保存在各自的服务器中,但这样会造成⼀个问题:出于负载均衡的考虑,分布式服务会将用户的访问请求均衡到不同的服务器上,并且通常无法保证用户每次请求都会被均衡到同⼀台服务器上,这样当用户刷新一次访问是可能会发现需要重新登录,这个问题是用户无法容忍的。

 我们可以用Redis集中管理session

4、手机验证码功能

临时存储验证码(短信、邮箱)、限制验证频率。

        很多应用出于安全考虑,会在每次进行登录时,让用户输入手机号并且配合给手机发送验证码,然后让用户再次输入收到的验证码并进行验证,从而确定是否是用户本人。为了短信接口不会频繁访问,会限制用户每分钟获取验证码的频率,例如一分钟不能超过5次。

像发送短信这样的操作,都有专门的SDK来实现(第三方提供的短信平台服务)

Hash(哈希表)

  • 用途:存储对象属性(如用户信息)、字段级更新。

  • 底层实现

    • ziplist(小哈希):当哈希中的元素个数小于hash-max-ziplist-entries配置(默认512个),且同时所有value的值都小于hash-max-ziplist-value配置(默认64字节)时,才会使用zipList作为内部实现,ziplist使用更加紧凑的结构实现 字段和值交替存储为连续内存,压缩列表,节省空间。

    • hashtable(大哈希):使用字典(数组 + 链表)实现,支持 O(1) 查找。

  • 特点

    • 单个 Hash 可存储 2^32 -1 个键值对。

    • 支持原子性字段操作(如 HINCRBY)。

哈希类型中的映射关系通常称为field-value,用于区分Redis整体的键值对(key-value),注意这里的value在不同上下文的作用

命令 

命令执行效果时间复杂度
hset key field value设置值(可批量)O(1)
hget key field获取值O(1)
hdel key field [field ...]删除 field (可批量)O(k), k 是 field 个数
hlen key计算 field 个数O(1)
hgetall key获取所有的 field-valueO(k), k 是 field 个数
hmget field [field ...]批量获取 field-valueO(k), k 是 field 个数
hmset field value [field value ...]批量获取 field-valueO(k), k 是 field 个数
hexists key field判断 field 是否存在O(1)
hkeys key获取所有的 fieldO(k), k 是 field 个数
hvals key获取所有的 valueO(k), k 是 field 个数
hsetnx key field value设置值,但必须在 field 不存在时才能设置成功O(1)
hincrby key field n对应 field-value +nO(1)
hincrbyfloat key field n对应 field-value +nO(1)
hstrlen key field计算 value 的字符串长度

O(1)

基本上和String类型的操作差不多,只是多了个h 

典型应用场景 

1、作为缓存

存储结构化的数据,使用hash类型更合适一点

相比于使用Json格式的字符串去缓存用户信息。哈希类型变得更加直观,并且在更新操作上变得更加灵活。

关系型数据库

uidnameagecitygenderfavor
1James<null>Beijing<null>sports
2Johnathan30<null>male<null>

哈希型

user1--->uid1user:2--->uid2
namejamesnamejohnathan
cityBeijingage30
favorsportsgendermale

哈希类型是稀疏的,而关系型数据库是完全结构化的。例如,哈希类型每个键可以有不同的 field,而关系型数据库一旦添加新的列,所有行都要为其设置值,即使为 null。

关系数据库可以做复杂的关系查询,而 Redis 去模拟关系型复杂查询,例如联表查询、聚合查询等基本不可能,维护成本高。

List(列表)

  • 用途:消息队列、最新消息排行、阻塞操作。

  • 底层实现

    • QuickList(Redis 3.2+):由多个 listpack(紧凑列表)通过双向链表连接,平衡内存和访问效率。

    • 旧版本使用 ziplist(压缩列表)+ LInkedList双向链表。

  • 特点

    • 支持双向操作(LPUSH/RPUSHLPOP/RPOP)。

    • 可通过 BLPOP/BRPOP 实现阻塞式读取。

    • 列表中的元素是有序的,这意味着可以通过索引下标获取某个元素或者某个范围的元素列表。

    • 列表的元素是允许重复的

list的编码方式并非是一个简单数组,而是更接近于 双端队列(deque) 

列表的获取删除操作

命令

操作类型命令作用时间复杂度
添加rpush key value [value ...]右侧添加O (k),k 是元素个数
lpush key value [value ...]左侧添加O (k),k 是元素个数
linsert key before | after pivot value在某个元素的前/后添加O (n),n 是pivot距离头尾的距离,从左往右
查找lrange key start end0 -1实现全部查找O (s+n),s 是 start 偏移量,n 是 start 到 end 的范围
lindex key index根据下标查找O (n),n 是索引的偏移量
llen key返回长度O(1)
删除lpop key左侧popO(1)
rpop key右侧popO(1)
lrem key count value删count个元素valueO (k),k 是元素个数
ltrim key start end保留区间O (k),k 是元素个数
修改lset key index value必须key存在,下标不能越界O (n),n 是索引的偏移量
阻塞操作blpop brpop key timeout可以设置阻塞时间。返回的结果是一个二元组,包括那个key什么valueO(1)

blpop 和 brpop 是 lpOP 和 rpop 的阻塞版本,与对应非阻塞版本作用基本一致,区别如下:

 
  • 列表有元素时,阻塞和非阻塞表现一致。列表无元素时,非阻塞版本返回 nil,阻塞版本根据 timeout 阻塞一段时间,期间 Redis 可执行其他命令,但执行该命令的客户端会处于阻塞状态。
  • 命令设置多个键时,会从左向右遍历键,一旦某个键对应的列表能弹出元素,命令立即返回。
  • 多个客户端同时对一个键执行 pop,最先执行命令的客户端获得弹出元素 。

此命令主要是用“消息队列”,但功能比较局限

典型应用场景 

1、作为消息队列

生产者消费者模型

Redis 分频道 阻塞消息队列模型

不同的频道负责不同的数据,实现了数据的解耦合

2、微博 TimeLine

每个用户都有属于自己的 Timeline(微博列表),现需要分页展示文章列表。此时可以考虑使用列表,因为列表不但是有序的,同时支持按照索引范围获取元素。

此方案在实际中可能存在两个问题:

        1 + n 问题。即如果每次分页获取的微博个数较多,需要执行多次 hgetall 操作,此时可以考虑使用 pipeline(流水线)模式批量提交命令,或者微博不采用哈希类型,而是使用序列化的字符串类型,使用 mget 获取。

        分裂获取文章时,lrange 在列表两端表现较好,获取列表中间的元素表现较差,此时可以考虑将列表做拆分。

💡 选择列表类型时,请参考:
同侧存取(lpush + lpop 或者 rpush + rpop)为栈
异侧存取(lpush + rpop 或者 rpush + lpop)为队列

Set(集合)

  • 用途:标签系统、唯一性校验、集合运算(并集、交集)。

  • 底层实现

    • intset(整数集合):元素均为整数时,使用有序数组存储。

    • hashtable(哈希表):字符串等非整数或元素较多时,用字典存储(值为 NULL)。

  • 特点

    • 无序且元素唯一(可以和list对比记忆)。

    • 支持 SINTER(交集)、SUNION(并集)等集合运算。

命令

命令功能时间复杂度
sadd key element [element ...]添加元素O (k),k 是元素个数
srem key element [element ...]删除元素O (k),k 是元素个数
scard key获取元素个数O(1)
sismember key element判断元素是否在集合中O(1)
srandmember key [count]随机取出元素O (n),n 是 count
spop key [count]随机删除元素O (n), n 是 count
smembers key查找集合中所有元素O (k),k 是元素个数
sinter key [key ...] sinterstore求交集 到新的集合O (m * k),k 是几个集合中元素最小的个数,m 是键个数
sunion key [key ...] sunionstore求并集O (k),k 是多个集合的元素个数总和
sdiff key [key ...] sdiffstore求差集O (k),k 是多个集合的元素个数总和
smove source destination member将source集合中的元素member移动到destinationO(1)

典型应用场景 

 1、使用set来保存用户的“标签”

set方便计算交集

根据用户的历史行为,个人数据,分析出个人的特征,然后提炼为用户的标签,生成用户画像来实现个性化的内容推荐(例如 抖音)

用户画像(User Profile)是通过收集、分析用户的多维度数据,抽象出的一个虚拟的“用户模型”。它用于精准描述用户特征、行为习惯和潜在需求,是推荐系统、广告投放、精准营销等场景的核心基础。

2、用set求用户之间的共同好友 

基于  集合求交集  能实现得出不同用户之间的共同好友,因此拓展后续的推荐好友等功能

3、使用set求UV

UV(Unique Visitor,独立访客)是互联网领域的一个关键指标,用于衡量在一定时间内访问某个网站、应用或页面的唯一用户数量。它与PV(Page View,页面浏览量)共同构成流量分析的基础。

指标定义用途示例
UV独立用户数(去重)衡量用户规模与覆盖广度1天内1000人访问网站,UV=1000
PV页面被浏览的总次数(不去重)衡量内容热度或用户活跃度同一用户刷新5次页面,PV=5
会话用户单次访问的连续操作集合分析用户单次访问的深度与行为路径用户打开APP后浏览3页后退出,会话数=1

Zset (Sorted Set有序集合)

  • 用途:排行榜、带权重的队列、范围查询。

  • 底层实现

    • 跳表(SkipList) + 哈希表

      • 跳表:支持 O(logN) 范围查询和排序。

      • 哈希表:存储成员到分值的映射,实现 O(1) 分值查询。

    • 小规模数据可能使用 ziplist 存储(按分值排序)。

  • 特点

    • 元素唯一,给每一个元素(member)同时引入一个属性---分值(score),按分值排序。

    • 分数相同,根据元素自身字符串的字典序来排列

    • 支持 ZRANGEBYSCOREZRANK 等范围操作。

命令

命令功能时间复杂度
zadd key score member [score member ...]

默认返回之后为新增的个数

XX:存在时修改

NX:不存在时修改

LT:新分数比之前分数少更新

GT:新分数比之前分数高更新

CH:影响返回值,包括修改的分数

INCR:实现运算

O (k * log (n)),k 是添加成员的个数,n 是当前有序集合的元素个数
zcard key获取元素个数O(1)
zscore key member获取元素分值O(1)
zrank key member
zrevrank key member
获取指定元素的排名O (log (n)),n 是当前有序集合的元素个数
zrem key member [member ...]删除元素O (k * log (n)),k 是删除成员的个数,n 是当前有序集合的元素个数
zincrby key increment member为指定元素添加指定的分数值O (log (n)),n 是当前有序集合的元素个数

zrange key start end [withscores]

zrevrange key start end [withscores]

默认 升序排列O (k + log (n)),k 是获取成员的个数,n 是当前有序集合的元素个数
zrangebyscore key min max [withscores]
zrevrangebyscore key max min [withscores]
根据分数区间来排列 默认升序O (k + log (n)),k 是获取成员的个数,n 是当前有序集合的元素个数
zcount key min max

返回分数在min-max区间的元素个数

O (log (n)),n 是当前有序集合的元素个数

zremrangebyrank key start end根据下标描述的范围删除O (k + log (n)),k 是获取成员的个数,n 是当前有序集合的元素个数
zremrangebyscore key min max根据分数描述的范围删除O (k + log (n)),k 是获取成员的个数,n 是当前有序集合的元素个数
zinterstore destination numkeys key [key ...]求交集O (n * k)+O (m * log (m)),n 是输入的集合最小的元素个数,k 是集合个数,m 是目标集合元素个数
zunionstore destination numkeys key [key ...]求并集O (n)+O (m * log (m)),n 是输入集合总元素个数,m 是目标集合元素个数

zpopmax key [count]

zpopmin key [count]

删除最大值/最小值元素O(log(N)*M)其中N是排序集合中的元素个数,M是弹出的元素个数。

bzpopmax key [key…] timeout

bzpopmin key [key…] timeout

zpopmax的阻塞版本 可以读取多个keyO (log (n)),n 是当前有序集合的元素个数
  • zpopmax key [count] 删除最大值元素 针对这一操作,redis确实记录了尾部这样的特定位置,理论上直接操作这个特定位置,可以实现 O(1) 但是在实际删除中,并没有用上这个特性,而是直接调用了一个“通用的删除函数”----》给定一个member的值,进行查找,找到位置再进行删除
  • zcount key min max 返回分数在min-max区间的元素个数,可以用 ( 来将闭区间变为开区间,注意正确的表示方式为zcount  myzset (90 (100  且-inf 为负无穷大(无限趋近于0) inf为无限大。它的时间复杂度为O (log (n)),n 是当前有序集合的元素个数 (并不是通过遍历元素的方式去获取,而是在zset的内部,会记录每个元素的次序,然后相减得到个数)
  • zinter zunion zdiff 这几个命令从redis 6.2 开始支持的
  • ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight
      [weight ...]] [AGGREGATE <SUM | MIN | MAX>] 求交集保存到另一个集合中 numkeys 的含义是后续参与交集运算的key的个数 之所以在这个命令之中涉及到了numkeys,是因为后续的一些[WEIGHTS weight [weight ...]] [AGGREGATE <SUM | MIN | MAX>] 需要与其进行区分 声明那些是key(有点类似于Http协议中的请求头和正文的粘包问题)WEIGHTS 的含义是 权重,相当于一个系数,会乘以当前的分数 [AGGREGATE <SUM | MIN | MAX>] 的含义是合并方式

典型应用场景 

1、排行榜系统

排行的要求是实时更新,zset非常适合这种场景,分数的改变也可以用zincrby去修改分数,排行顺序也会随之调整。

thousand--1kb  million--1m 10亿--GB

对于游戏排行榜,前后顺序非常容易确定,但是类似于“微博热度”这种排行榜就是看的综合数值,不同纬度对应着不同的权重,根据不同的 weight权重 得到综合数值,这里就会用到zinterstore/zunionstore


特定场景的数据类型--粗略介绍 

Stream(流)

用途

  • 消息队列:类似于 Kafka 的轻量级实现,支持多消费者组、消息持久化和消息回溯。

  • 事件溯源:记录时序事件(如用户操作日志)。

  • 实时数据处理:处理实时数据流(如 IoT 设备数据)。

核心命令

  • XADD key * field1 value1 ...:添加消息(* 表示自动生成消息 ID)。

  • XREAD [BLOCK ms] STREAMS key start_id:读取消息(支持阻塞模式)。

  • XGROUP CREATE key groupname start_id:创建消费者组。

  • XACK key groupname message_id:确认消息消费。

示例

# 生产者发送消息
XADD orders * user_id 1001 product "Coffee" price 3.5

# 消费者读取消息(阻塞模式,最多等待 5000ms)
XREAD BLOCK 5000 STREAMS orders $

特点

  • 消息 ID:格式为 <时间戳>-<序列号>(如 1630454400000-0),支持按范围查询。

  • 消费者组:支持负载均衡(同一组的消费者共享消息)和消息确认机制。

  • 持久化:消息默认持久化到内存,可配置 RDB/AOF 保证可靠性。

Kafka 是一种分布式流处理平台,它被设计用于处理大规模的实时数据流,可作为消息队列或消息总线使用,在数据管道、消息传递、事件溯源和流处理等场景中广泛应用。

Geospatial(地理空间)

用途

  • LBS(基于位置的服务):如查找附近的餐厅、共享单车、打车服务。

  • 距离计算:计算两点间的距离(如配送费计算)。

  • 地理围栏:判断用户是否进入某个区域。

核心命令

  • GEOADD key longitude latitude member:添加地理位置(经度、纬度、名称)。

  • GEODIST key member1 member2 [unit]:计算两个位置的距离(单位:m/km/mi/ft)。

  • GEORADIUS key longitude latitude radius unit:查找半径内的位置。

  • GEOHASH key member:获取位置的 Geohash 编码。

示例

# 添加位置数据
GEOADD restaurants 116.404269 39.91582 "Pizza Hut"

# 查找距离用户 5km 内的餐厅
GEORADIUS restaurants 116.401245 39.913789 5 km WITHDIST

特点

  • 底层结构:基于 Sorted Set 实现,位置信息通过 Geohash 算法转换为浮点数存储为 score

  • 精度:经纬度支持 6 位小数(精度约 10cm)。

HyperLogLog(基数统计)

用途

  • 只有一个应用场景估算集合中的元素个数

  • 大数据去重统计:如统计每日 UV(set实现,需要存储每个元素,内存占用相较于HyperLogLog更大)、搜索关键词数量。

  • 低内存占用固定使用约 12KB 内存,误差率约 0.81%。

核心命令

  • PFADD key element1 element2 ...:添加元素。

  • PFCOUNT key:统计基数(不重复元素数量)。

  • PFMERGE destkey sourcekey1 sourcekey2 ...:合并多个 HyperLogLog。

示例

# 统计用户访问量
PFADD daily_uv "user1" "user2" "user3"
PFCOUNT daily_uv  # 输出 3

特点

  • 误差率:标准误差 0.81%,适合对精度要求不高的场景。

  • 不可逆:无法获取具体元素,仅统计数量。

  • 是一种思想,而非redis所独有的

Bitmaps(位图)

用途

  • 布尔标记:如用户签到、功能开关、在线状态。

  • 高效统计:统计活跃用户数(如 DAU)。

核心命令

  • SETBIT key offset value:设置位的值(0 或 1)。

  • GETBIT key offset:获取位的值。

  • BITCOUNT key [start end]:统计值为 1 的位数。

  • BITOP operation destkey key1 key2 ...:位运算(AND/OR/XOR/NOT)。

示例

# 记录用户 1001 第 7 天签到
SETBIT user:1001:sign 7 1

# 统计本月签到总天数
BITCOUNT user:1001:sign

特点

  • 空间高效:每个用户每日签到仅需 1 位,1 亿用户每月仅需约 12MB。

  • 稀疏优化:Redis 动态分配内存,稀疏位图实际占用空间更小。

  • 本质:基于 String 的位操作(如用户在线状态、签到统计),属于是Set类型针对整数的特化版本

Bitfield(位域)

用途

  • 紧凑存储数值:如存储多个小整数(如 RGB 颜色、状态标志)。

  • 原子操作:支持对指定位的自增、溢出控制。

核心命令

  • BITFIELD key [GET type offset] [SET type offset value] [INCRBY type offset increment]:读写或修改位域。

  • Type 格式u/i<位数>(如 u8 表示无符号 8 位整数)。

示例

# 存储用户状态:u4(权限等级) + u8(积分)
BITFIELD user:1001:flags SET u4 0 2 SET u8 4 150

# 自增用户积分(溢出时饱和)
BITFIELD user:1001:flags INCRBY u8 4 10 OVERFLOW SAT

特点

  • 灵活位操作:支持有符号/无符号整数,自定义偏移量。

  • 溢出策略:支持 WRAP(折返)、SAT(饱和)、FAIL(拒绝操作)。

  • 可以理解为一串二进制序列


选择数据结构的建议

  1. 高频更新场景

    • 计数器用 StringINCR)。

    • 对象属性更新用 HashHINCRBY)。

  2. 范围查询

    • 时间序列数据用 Sorted Set(按时间戳排序)。

    • 地理位置用 Geospatial

  3. 去重与集合运算

    • 标签系统用 Set

    • 唯一值统计用 HyperLogLog

  4. 消息队列

    • 简单队列用 List

    • 需持久化和消费者组管理用 Stream


 五、渐进式遍历 

Redis 的 渐进式遍历 是一种高效、安全的遍历大数据集的方法,旨在避免使用 KEYSSMEMBERS 等阻塞式命令导致的性能问题。它通过分批次、游标(cursor)的方式逐步遍历数据,确保在遍历过程中不会长时间阻塞 Redis 主线程,适合处理海量数据。


为什么需要渐进式遍历?

  1. 避免阻塞
    KEYS *SMEMBERS 等命令会一次性返回所有数据,如果数据量极大(如百万级),会导致 Redis 主线程长时间阻塞,影响其他请求。

  2. 动态数据集
    如果遍历过程中数据集发生变化(如新增或删除元素),传统命令可能返回不一致的结果。

  3. 内存安全
    一次性返回所有数据可能撑爆客户端内存,而渐进式遍历允许客户端分批处理。


核心命令:SCAN 系列

Redis 提供 SCAN 命令及其衍生命令,支持对键、集合、哈希、有序集合的渐进式遍历:

  • SCAN:遍历数据库中的键。

  • SSCAN:遍历集合(Set)中的元素。

  • HSCAN:遍历哈希(Hash)中的字段。

  • ZSCAN:遍历有序集合(ZSet)中的成员。


SCAN 命令详解

语法

SCAN cursor [MATCH pattern] [COUNT count] [TYPE type]
  • cursor:光标,游标,从 0 开始,遍历结束时返回 0,仅仅是一个字符串,不能和下标混为一谈。

  • MATCH pattern:按模式匹配键(如 user:*),和keys的pattern是一样的。

  • COUNT count建议每批返回的元素数量,默认是10(实际返回数量可能不同,不会差很多)。

  • TYPE type(Redis 6.0+):按数据类型过滤(如 stringhash)。

示例


关键特性

  1. 非阻塞
    每次 SCAN 只返回少量数据,避免长时间占用主线程,且遍历过程可以随时中断。

  2. 游标机制
    客户端需记录游标,直到返回 0 表示遍历完成。

  3. 弱一致性
    遍历过程中若数据发生变化(如新增或删除元素),可能会返回重复或遗漏部分数据。

  4. COUNT 参数
    COUNT 仅是一个建议值,实际返回数量可能不同(Redis 根据内部优化调整)。

  5. 时间复杂度
    每次 SCAN 的时间复杂度为 O(1),完整遍历的时间复杂度为 O(N)(与 KEYS 相同,但分摊了耗时)。


六、JavaClient

在日常开发之中命令行客户端并不是我们日常开发的主要形式,更多是用API去构建自己的客户端,客户端按照对应的应用层协议发送请求,服务器按照这个协议进行解析,再按照这个协议构造响应,客户端再进行解析

 RESPREdis Serialization Protocol

Redis 的 RESPREdis Serialization Protocol)是 Redis 客户端和服务端之间通信的核心协议。其设计目标是保持简单、高效,同时具备可读性。REST API | Docs


1. RESP 的核心设计

  • 基于 TCP:协议通过 TCP 连接传输,但是和TCP没有强耦合,支持请求-响应模型和 Pipeline 批量操作。
  • 二进制安全:允许传输任意二进制数据(比如图片、JSON 等),不依赖特殊字符终止。
  • 类型化数据结构:定义了多种简单但高效的数据类型,每个数据块以特定前缀标识类型。

2. RESP 支持的数据类型

类型前缀字符示例说明
Simple Strings++OK\r\n用于成功响应(如OK),不含换行符,只能传输文本。
Errors--ERR unknown command\r\n错误信息,客户端应优先处理错误。
Integers::123\r\n整数,如 INCR 操作的返回值。
Bulk Strings$$5\r\nhello\r\n用于传输二进制安全的字符串或数据块。
Arrays**3\r\n$3\r\nSET\r\n...客户端请求通常以数组形式发送命令。
Null$-1\r\n$-1\r\n表示空值(nil)。

3. 协议特点

  • 性能高效:仅通过前缀字符判断类型,解析速度快。
  • 可读性:人类可读的文本格式(非二进制协议),方便调试(如用 telnet 手动发送命令)。
  • 压缩传输:用长度字段减少冗余字符,优化大数据传输(如 Bulk Strings)。
  • 简单好实现:协议简单并不复杂

4. RESP3(Redis 6 引入的扩展协议)

  • 改进点:增加更多数据类型(如布尔值、浮点数、Map 类型等),支持客户端-服务端双向通信。
  • 兼容性:默认仍使用 RESP2,可通过 HELLO 命令切换版本。

通过理解 RESP,可以深入 Redis 的底层通信机制,优化客户端性能或定制工具。比如在 Java 中使用 Jedis 时,每次命令最终都会被编码为 RESP 格式发送给服务端。


Jedis

GitHub - redis/jedis: Redis Java client

指导手册 jedis 5.3.0-beta1 javadoc (redis.clients)

Jedis 是一个流行的 Redis 官方推荐的 Java 客户端库,用于在 Java 应用程序中与 Redis 服务器进行交互。它提供了简单且直观的 API,支持 Redis 的绝大部分命令和功能。

redis的6379端口默认是被云服务器的防火墙给保护起来的,无法外界进行连接访问,而Redis的端口一旦被公开到公网上,特别容易被入侵,为此,在不开放redis的端口且又能保证我们自己通过外网访问云服务器上的redis,我们有以下两种方式:

1、直接将相关的java程序打成jar包,然后把jar包拷贝到linux服务器上执行(可借助第三方的插件)

2、配置ssh端口转发,把云服务器的redis端口,映射到本地主机(推荐)

SSH 端口转发(Port Forwarding)是一种通过 SSH 加密隧道将网络流量从一个端口转发到另一个端口的技术,常用于突破网络限制、访问内网资源或加密通信。

先配置一下隧道

当ssh连接上了之后,端口的转发才生效

通过netstat命令检查一下

经过如此操作就可以通过访问127.0.0.1操作到云服务器的redis了(包括最开始在安装步骤中的配置绑定的ip和关闭保护模式)

测试代码

public class RedisDemo {
    public static void main(String[] args) {
        JedisPool jedisPool = new JedisPool("tcp://localhost:8888");
        try(Jedis jedis = jedisPool.getResource()) {
            //redis的各种命令就是jedis的各种方法
            System.out.println(jedis.ping());
        }
    }
}

在jedis中的方法与实际命令十分相同,这里不做多以赘述

部分操作Demo

public class RedisDemoSet {
    //sinter sunion
    public static void test2(Jedis jedis){
        jedis.flushAll();
        jedis.sadd("set1","111","222","333");
        jedis.sadd("set2","444","222","333");
        System.out.println(jedis.sinter("set1", "set2"));
        jedis.sinterstore("set3", "set1", "set2");
        System.out.println(jedis.smembers("set3"));
        System.out.println(jedis.sunion("set1", "set2"));
        jedis.sunionstore("set4", "set1", "set2");
        System.out.println(jedis.smembers("set4"));
    }

    // sadd smembers sismember spop
    public static void test1(Jedis jedis){
        jedis.flushAll();
        jedis.sadd("set","111","222","333","444","555","666");
        System.out.println(jedis.smembers("set"));
        System.out.println(jedis.sismember("set", "111"));
        System.out.println(jedis.sismember("set", "777"));
        System.out.println(jedis.spop("set"));
        System.out.println(jedis.spop("set"));
        System.out.println(jedis.spop("set"));
        System.out.println(jedis.smembers("set"));

    }

    public static void main(String[] args) {
        JedisPool jedisPool = new JedisPool("tcp://localhost:8888");
        try(Jedis jedis = jedisPool.getResource()) {
            //test1(jedis);
            test2(jedis);
        }
    }
}

RedisTemplate

Spring Data Redis

RedisTemplate 是 Spring Data Redis 提供的一个高级抽象工具,用于简化 Java 应用与 Redis 的交互。它封装了底层 Redis 客户端(如 Jedis、Lettuce)的复杂性,提供了更符合 Spring 生态的编程风格,支持自动序列化、连接管理和事务等功能。

RedisTemplate 的使用和 jedis 略微存在一些不同,它提供面向不同数据结构的 API,相当于进行了二次封装:

  • 字符串opsForValue()

  • 哈希opsForHash()

  • 列表opsForList()

  • 集合opsForSet()

  • 有序集合opsForZSet()

测试 Demo

@RestController
public class MyController {
    // StringRedisTemplate 是 RedisTemplate 的子类,专门用来处理文本类
    @Autowired
    private StringRedisTemplate redisTemplate;

    @GetMapping("/testString")
    @ResponseBody
    public String testString() {
        redisTemplate.opsForValue().set("hello", "world");
        System.out.println(redisTemplate.opsForValue().get("hello"));
        return "ok";
    }

    @GetMapping("/testList")
    @ResponseBody
    public String testList() {
        //execute的回调方法必须要有return语句
        redisTemplate.execute((RedisConnection connection) -> {
            //进行原生操作
           connection.flushAll();
           return null;
        });
        redisTemplate.opsForList().leftPush("list", "111");
        redisTemplate.opsForList().leftPush("list", "222");
        redisTemplate.opsForList().leftPush("list", "333");
        System.out.println(redisTemplate.opsForList().range("list", 0, -1));
        System.out.println(redisTemplate.opsForList().leftPop("list"));
        System.out.println(redisTemplate.opsForList().rightPop("list"));
        return "ok";
    }

    @GetMapping("/testSet")
    @ResponseBody
    public String testSet() {
        //execute的回调方法必须要有return语句
        redisTemplate.execute((RedisConnection connection) -> {
            //进行原生操作
            connection.flushAll();
            return null;
        });
        redisTemplate.opsForSet().add("set", "aaa");
        redisTemplate.opsForSet().add("set", "bbb");
        redisTemplate.opsForSet().add("set", "ccc");
        System.out.println("set中的元素有:"+redisTemplate.opsForSet().size("set")+"个");
        System.out.println(redisTemplate.opsForSet().members("set"));
        System.out.println("pop出:"+redisTemplate.opsForSet().pop("set"));
        System.out.println(redisTemplate.opsForSet().members("set"));
        return "ok";
    }

    @GetMapping("/testHash")
    @ResponseBody
    public String testHash() {
        //execute的回调方法必须要有return语句
        redisTemplate.execute((RedisConnection connection) -> {
            //进行原生操作
            connection.flushAll();
            return null;
        });
        redisTemplate.opsForHash().put("hash", "AAA", "aaa");
        redisTemplate.opsForHash().put("hash", "BBB", "bbb");
        redisTemplate.opsForHash().put("hash", "CCC", "ccc");
        redisTemplate.opsForHash().put("hash", "DDD", "ddd");
        System.out.println("DDD:"+redisTemplate.opsForHash().get("hash", "DDD"));
        System.out.println("hash里面存储的键值对数量为"+redisTemplate.opsForHash().size("hash"));
        System.out.println(redisTemplate.opsForHash().keys("hash"));
        System.out.println(redisTemplate.opsForHash().hasKey("hash", "AAA"));
        System.out.println(redisTemplate.opsForHash().hasKey("hash", "EEE"));
        return "ok";
    }

    @GetMapping("/testZset")
    @ResponseBody
    public String testZset() {
        //execute的回调方法必须要有return语句
        redisTemplate.execute((RedisConnection connection) -> {
            //进行原生操作
            connection.flushAll();
            return null;
        });
        redisTemplate.opsForZSet().add("zset","A",100);
        redisTemplate.opsForZSet().add("zset","B",30);
        redisTemplate.opsForZSet().add("zset","C",60);
        redisTemplate.opsForZSet().add("zset","D",80);
        System.out.println("zset的元素个数为"+redisTemplate.opsForZSet().size("zset"));
        System.out.println("member C 的位置在"+redisTemplate.opsForZSet().rank("zset", "C"));
        System.out.println(redisTemplate.opsForZSet().range("zset", 0, -1));
        System.out.println(redisTemplate.opsForZSet().rangeWithScores("zset", 0, -1));
        System.out.println("C的分数为"+redisTemplate.opsForZSet().score("zset", "C"));
        redisTemplate.opsForZSet().popMax("zset");
        System.out.println(redisTemplate.opsForZSet().rangeWithScores("zset", 0, -1));
        return "ok";
    }
}

但愿每次回忆,对生活都不感到负疚         —— 郭小川

🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀

以上,就是本期的全部内容啦,若有错误疏忽希望各位大佬及时指出💐

  制作不易,希望能对各位提供微小的帮助,可否留下你免费的赞呢🌸 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/979172.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

帧中继+静态路由实验(大规模网络路由器技术)

一、帧中继实验 &#xff08;1&#xff09;实验拓扑图如下图所示: 帧中继交换机1接口两侧的DLCI值&#xff1a; 数据链路连接标识符&#xff08;DLCI&#xff0c;Data Link Connection Identifier&#xff09;&#xff0c;DLCI值用于标识 永久虚电路 &#xff08;PVC&#xf…

Azure Speech

1、文字转语音(Text-To-Speech, TTS) 2、语音转文字(Speech-To-Text): Azure Speech to Text 1- 环境配置&#xff1a;Microsoft Azure 注册使用免费服务&#xff1a; 需要信用卡&#xff0c;本人没有&#xff0c;所以没有完成注册

海洋cmsv9报错注入,order by 和limit注入

海洋cmsv9 1&#xff0c;我们拿到海洋cmsv9源码分析发现注入点&#xff0c;$rlist 2&#xff0c;seacms开源&#xff0c;可以知道seacmsv9系统数据库&#xff08;mysql&#xff09;为seacms&#xff0c;存放管理员账号的表为 sea_admin&#xff0c;表中存放管理员姓名的字段为…

Linux系统下基于mplayer媒体播放器

1、项目背景 随着多媒体技术的发展&#xff0c;各种音视频格式的流行&#xff0c;用户对媒体播放器的功能和性能要求 日益增加。MPlayer是一个强大的开源媒体播放器&#xff0c;支持多种音视频格式。本项目旨在 基于MPlayer构建一个轻量级的Linux媒体播放器&#xff0c;提供简洁…

牛客NC288803 和+和

​import java.util.Comparator;import java.util.PriorityQueue;import java.util.Scanner;​public class Main {public static void main(String[] args) {// 创建Scanner对象用于读取输入Scanner sc new Scanner(System.in);// 读取两个整数n和m&#xff0c;分别表示数组的…

2025 软件供应链安全情报预警平台建设与实践

何为数字安全供应链情报&#xff1f; 所谓的数字供应链开源安全情报主要针对目标是开源数字应用资产。包括开源组件&#xff0c;中间件和操作系统。开源安全情报类型可以分为三大类&#xff1a; 1 第一类是传统的安全漏洞风险情报&#xff0c;开源漏洞情报数据获取主要有2种渠…

红蓝对抗之常见网络安全事件研判、了解网络安全设备、Webshell入侵检测

文章目录 ​​研判&#xff08;入侵检测&#xff09;​​ ​​设备​​ ​​经典网络​​​​云网络​​ ​​异常HTTP请求​​​​Webshell分析​​ ​​Webshell 的分类​​​​Webshell 的检测​​ ​​主机层面​​​​流量层面​​ ​​附录​​ ​​常见端口漏洞…

【Python系列】Python 连接 PostgreSQL 数据库并查询数据

???欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学习,不断总结,共同进步,活到老学到老…

DeepSeek赋能智慧社区:提升社区治理,优化资源配置,带来全新变革

在数字化浪潮的推动下&#xff0c;智慧社区正逐渐成为城市发展的重要方向。作为一款先进的人工智能大模型&#xff0c;DeepSeek凭借其强大的多模态数据分析和智能决策能力&#xff0c;正在为智慧社区的建设注入新的活力。 标准规范及顶层设计指南、供应商整体解决方案合集、供应…

代理服务器与内网穿透/打洞

内网穿透 简单来说内网穿透就是让一个在私人IP的设备&#xff0c;能在公网上被别的主机访问到资源。 中间经过服务器将获取的数据转发给主机。 内网打洞 内网打洞&#xff0c;也叫 P2P 穿透或 NAT 穿越&#xff0c;是一种用于实现位于不同内网中的设备之间直接建立连接的技…

本地大模型编程实战(26)用langgraph实现基于SQL数据构建的问答系统(5)

本文将将扩展上一篇文章完成的 langgraph 链&#xff0c;继续使用基于 langgraph 链 &#xff0c;对结构化数据库 SQlite 进行查询的方法。该系统建立以后&#xff0c;我们不需要掌握专业的 SQL 技能&#xff0c;可以用自然语言询问有关数据库中数据的问题并返回答案。主要完善…

Geek卸载软件安装使用教程

文章目录 一、Geek下载二、使用步骤 一、Geek下载 Geek Uninstallers最新版是一款高效、快速、小巧、免费的软件卸载与清理工具&#xff0c;旨在帮助用户删除系统上安装的程序。不同于其他的卸载程序&#xff0c;Geek Uninstaller执行深入扫描进程&#xff0c;并清除软件卸载后…

BIO、NIO、AIO解析

一、基础概念 1、IO的含义 IO&#xff0c;Input/Output&#xff0c;即输入/输出。从计算机结构来看&#xff0c;IO描述了计算机系统和外部设备之间通讯的过程。从应用程序角度来看&#xff0c;一个进程的地址空间划分为 用户空间&#xff08;User space&#xff09; 和 内核空…

抖音生活服务加强探店内容治理,2024年达人违规率下降30%

发布 | 大力财经 2月27日&#xff0c;抖音生活服务发布《2024抖音生活服务消费者权益保护年度报告》&#xff08;以下简称“报告”&#xff09;。报告显示&#xff0c;过去一年&#xff0c;抖音生活服务针对消费者反感的虚假、夸张探店内容&#xff0c;开展了专项治理。通过一…

Apollo Cyber 学习笔记

目录 0 Introduction What Why Advantage 1 Example 2 Concept 3 Flow Chart 4 Module 4.1 Transport 4.1.1 Share Memory 4.1.1.1 Segment 4.1.1.1.1 State 4.1.1.1.2 Block 4.1.1.1.3 Common 4.1.1.2 Notifier 4.1.1.2.1 ConditionNotifier 4.1.1.2.2 Multi…

企业如何挖掘数据资产价值?

本期推荐&#xff1a;挖掘数据资产价值&#xff0c;赋能企业发展&#xff0c;共28页ppt。 关注WeChat Subscription Account【智慧城市指北】&#xff0c;回复关键字“20250228数据资产”&#xff0c;获取获得本文电子版材料的方式(非无偿&#xff09;~ 篇幅限制&#xff0c;…

FastExcel vs EasyExcel vs Apache POI:三者的全面对比分析

一、核心定位与历史沿革 Apache POI&#xff08;1990s-&#xff09; 作为Java生态中最古老的Excel处理库&#xff0c;提供对.xls/.xlsx文件的全功能支持。其核心价值在于对Excel规范的完整实现&#xff0c;包括单元格样式、公式计算、图表操作等深度功能。但存在内存消耗大&…

千峰React:Hooks(下)

useLayoutEffect useLayoutEffect在useEffect之前触发 这样会闪屏&#xff0c;因为是异步的&#xff0c;两次都渲染了 import {useEffect,useState } from react;function App() {const [msg,setMsg] useState(hello App)useEffect(() > {setMsg(hello useEffect)});retu…

盛京开源社区加入 GitCode,书写东北开源生态新篇章

在数字化转型与开源技术蓬勃发展的浪潮下&#xff0c;开源社区已成为推动技术创新的核心力量。盛京开源社区&#xff08;SJOSC&#xff09;作为沈阳地区的开源交流平台&#xff0c;始终致力于连接开发者、企业及高校&#xff0c;构建区域技术生态圈。 现在&#xff0c;盛京开源…

Unity:实时查看和调试日志信息(In-game Debug Console插件)

在Unity中使用In-game Debug Console插件可以方便地在应用内实时查看和调试日志信息。 1、导入插件 从Packages:My Assets导入In-game Debug Console插件&#xff0c;导入后&#xff0c;插件会自动添加到项目的Packages文件夹中。&#xff08;需要先下载该插件&#xff09; 2、…