【八股】Redis篇

使用场景

🙂缓存:缓存穿透、击穿、雪崩、双写一致、持久化、数据过期、数据淘汰策略
🙂分布式锁:setnx、redisson
🙂消息队列、延迟队列、保存token:何种数据类型
🙂计数器

数据类型和它们底层的数据结构

🙂String(字符串):最基本的类型,可以存储任何数据,例如文本、图片、二进制数据等。用于缓存计数器、缓存数据、存储配置
【简单字符串SDS】
🙂List(列表):可以存储多个有序的元素,用于消息队列
【双向链表(短小)、压缩列表(长小)、快速列表(长大)】
🙂Set(集合):可以存储多个无序的元素,元素不能重复,用于去重、标签
【哈希表、整数集合】
🙂Hash(哈希):键值对存储,可以存储多个键值对,存储用户信息
【哈希表】
🙂Zset(有序集合):可以存储多个有序的键值对,排行榜、时间线和统计
【跳表或压缩列表】

  • Zset在 set 的基础上增加了一个权重参数 score,使得集合中的元素能够按 score 进行有序排列
  • 排行榜:将用户的 ID 作为元素,用户的分数作为分数
  • 时间线:将发布的消息作为元素,消息的发布时间作为分数
  • 延时队列:将需要延时处理的任务作为元素,任务的执行时间作为分数
  • 压缩列表和跳表的区别:
    👉当 Zset 存储的元素数量小于zset-max-ziplist-entries 的值,且所有元素的最大长度小于 zset-max-ziplist-value 的值时,会选择使用压缩列表。占用的内存较少,但是在需要修改数据时,可能需要对整个压缩列表进行重写,性能较低。
    👉 当 Zset 存储的元素数量超过 zset-max-ziplist-entries 的值,或者任何元素的长度超过 zset-max-ziplist-value 的值时,会将底层结构从压缩列表转换为跳跃表。跳跃表的查找和修改数据的性能较高,但是占用的内存也较多。

缓存穿透现象是什么,怎么解决

🎶是指查询数据的时候,数据既不存在于redis中,也不存在于数据库中,导致查不到数据也写不到redis中,每次请求都会去请求数据库,导致数据库挂掉,这种情况大概率是遭受到了攻击

👉有两种解决方式,一个是返回空值,当我们查询不到数据的时候,也将这个null值写到redis中,还有一个方法是布隆过滤器,在查询数据的时候先查布隆过滤器中是否存在该数据,不存在则直接返回,存在再进入下一步查询redis

布隆过滤器

基于redisson实现的底层:布隆过滤器它是一个只存放二进制的数组, 通过对id值进行三次不同的哈希运算,得到三个哈希值,修改哈希值索引的数组元素为1。这样在每次查询id的时候,只需要查它对应的三个数组值是否为1,就能知道他是否存在了。但存在一个数据误判的情况,这时候我们可以扩大数组大小或者选择多个哈希函数来减少误判率,但这也是牺牲了空间换来的,一般我们设置误判率在5%左右即可

👉为什么不能用哈希表要用布隆过滤器?
哈希表考虑到负载因子的存在,对空间的利用率不高;而且哈希表有链表查询,在哈希冲突严重的情况下,会比纯数组查询的布隆慢

👉优点
存储空间和插入/查询时间都是常数;散列函数相互之间没有关系,方便并行实现;可以表示全集,不需要存储元素本身,在某些对保密要求非常严格的场合有优势

👉缺点
存在误算率,数据越多,误算率越高;一般情况下无法从过滤器中删除数据;二进制数组长度和 hash 函数个数确定过程复杂

缓存击穿

🎶是指key过期时刚好有大量的请求访问key,导致所有的请求都访问到数据库,增大了数据库的压力

👉解决方式:看我们的业务场景是需要数据强一致还是无需强一致。如果需要的话就使用互斥锁,不需要就为key设置逻辑过期时间

  • 互斥锁是一个线程在访问redis中的key发现过期的时候,用setnx设置一个互斥锁,当同步redis和数据库中的操作完成后,再释放锁资源,这样就算有另外的线程访问这个key,也会因为没有拿到锁资源而被阻塞。这样能保证数据的强一致性,但是性能不高。
  • 逻辑过期时间是指一个线程访问key发现过期时,开启一个新的线程进行数据同步,当前线程直接返回redis中的过期数据。而当新的线程同步完成后也会重新设置key的过期时间。这样会导致当前线程拿到的是一个过期的数据,无法保证数据强一致性,但是性能高。

缓存雪崩

🎶是指很多的key同时到期了,导致访问这些key的请求都到达了数据库端。
👉解决方式:尽量不要设置相同的key过期时间,而是采取随机值。

双写一致性

🎶是指数据库中的数据应该与Redis中的数据保持一致。如何保证双写一致性也分为强一致业务和允许延时一致的业务

👉允许延时一致的业务场景:使用延迟双删,即缓存中删除数据后,再到数据库中修改数据,然后延迟一段时间再到缓存中删除数据。【why延迟?】因为数据库是有两章主从分离的表,从表更新主表的数据也是需要时间的

👉需要强一致性的场景:使用读写锁排他锁,读的时候上读写锁,这样其他线程来只能读不能写。写的时候上排他锁,其他线程都被阻塞

【场景题】有多个 Redis 节点,当 MySQL 发生更新时,需要确保更新各个节点的缓存,其中一个节点下线的情况下如何保持系统的正常运行。

  • 事务:在 MySQL 更新操作之前,开启 Redis 事务。在事务中执行更新各个 Redis 节点的缓存操作,包括写入新的数据、删除旧的数据等。提交事务以确保所有操作原子性
  • 管道:使用 Redis 管道可以将多个命令一次性发送到 Redis 服务器,并在一次通信中获取所有命令的执行结果,从而减少通信开销和延迟。在 MySQL 更新之前,创建一个 Redis 管道。将更新各个节点的缓存操作添加到管道中。执行管道以一次性提交所有操作

持久化

RDB(Redis DataBase)

🎶数据快照,把内存中的所有数据记录到磁盘,当Redis宕机恢复数据的时候,从RDB的快照文件中恢复数据

👉怎么做
有两个命令,savebgsavesave会阻塞Redis服务器进程,直到RDB文件创建完成;bgsavefork一个子进程来负责创建RDB文件,只有fork的时候会阻塞,创建RDB的时候父进程可以继续处理命令请求,所以一般用bgsave。在redis.config文件中配置Redis内部触发RDB的机制,比如save 900 1 表示900s内,如果至少一个key被修改,执行bgsave命令

👉执行原理
bgsave开始时会fork主进程得到子进程,子进程共享主进程中的内存数据。fork采取的是copy-on-write技术:当主进程执行读操作时,访问共享内存,当主进程执行写操作时,则会拷贝一份内存数据的副本,在副本中执行写操作,这样就不会出现脏读现象
【扩展:怎么共享】Redis读写数据的时候不能直接处理物理内存,而是处理虚拟内存,通过页表来找到虚拟地址和物理地址之间的映射关系实现的。因此子进程只需要fork一份主进程的页表,即可完成内存共享。

👉优缺点:RDB是二进制压缩文件,占用空间小,便于传输,恢复数据速度较快。两次RDB期间有空档期,此期间若Redis宕机了可能会造成数据的丢失。

AOF(Append Only File)

🎶当redis操作写命令的时候,都会将命令存储在追加文件AOF中,当redis实例宕机恢复数据的时候,会从AOF中再次执行一遍命令来恢复数据。

👉怎么做

  • AOF默认是关闭的,在redis.config文件中配置appendonly yes开启,记录的频率通过appendfsync always/everysec/no修改,这三种指令分别代表了 同步刷盘/每秒刷盘/操作系统控制刷盘,数据完整性由好到查,速度由慢到快,一般选用everysec
  • 使用bgrerwriteaof命令,让AOF文件执行重写功能,比如说一个key执行了多次写操作,但只有最后一次写操作有用,开启这个命令就可以只记录最后一次写操作。
  • 自动重写AOF:auto-aof-rewrite-percentage 100/ auto-aof-rewrit-min-size 64mb
    AOF文件比上次文件增长超过多少100%则触发重写/AOF文件体积到达64mb触发重写

👉优缺点:数据的完整性较高,文件较大,恢复速度较慢。

RDB-AOF混合持久化

该模式会将生成相应的RDB数据,写入AOF文件中,重写后的新 AOF 文件前半段是 RDB 格式的全量数据,后半段是 AOF 格式的增量数据。这样用户可以同时获得RDB持久化和AOF持久化的优点。

数据过期策略

🎶Redis对数据设置数据的有效时间,数据过期以后,就需要将数据从内存中删除掉,Redis的删除策略是惰性删除 + 定期删除两种策略进行配合使用

👉惰性删除:访问key的时候判断是否过期,如果过期则删除。对CPU友好但是对内存不友好

👉定期删除:每隔一段时间,我们就对一些key进行检查,删除里面过期的key。可以通过限制删除操作的执行频率和时长来减少对CPU的影响。但是难以确定合适的频率和时长

  • SLOW:定时任务,执行频率10hz,每次不超过25ms
  • FAST:执行频率不固定,但两次间隔不低于2ms,每次耗时不超过1ms

👉定时过期:定时过期是指在设置键值对的时候,同时指定一个过期时间。一旦超过这个时间,键值对就会自动被删除。这种策略可以确保数据的实时性,但是它的效率并不是很高,因为Redis需要为每一个设置了过期时间的键维护一个定时器。

数据淘汰策略

🎶针对Redis内存不足时,仍然需要向Redis中添加策略的场景,此时需要按照特定规则来淘汰内存中的数据,将其删除掉

LRU和LFU

🎶LRU(Least Recently Used):最近最少使用。用当前时间减去最后一次访问时间,这个值越大则淘汰优先级越高;
🎶LFU(Least Frequently Used):最少频率使用。会统计每个key的访问频率,值越小淘汰优先级越高。

👉八种数据淘汰策略和使用建议

  1. noeviction【默认】
    • 不淘汰任何key,但是内存满时不允许写入新数据
    • 内存用完了再添加新数据时会直接报错
  2. volatile-ttl
    • 对设置了TTL的key,比较key的剩余TTL值,TTL越小越先被淘汰。
  3. allkeys-random
    • 对全体key ,随机进行淘汰。
    • 访问频率差别不大,没有明显冷热数据区分
  4. volatile-random
    • 对设置了TTL的key ,随机进行淘汰。
  5. allkeys-lru
    • 对全体key,基于LRU算法进行淘汰。
    • 优先使用,特别是如果业务有明显的冷热数据区分
    • 场景:数据库有1000万数据 ,Redis只能缓存20w数据, 保证Redis中数据都是热点数据
  6. volatile-lru
    • 对设置了TTL的key,基于LRU算法进行淘汰。
    • 有置顶需求,置顶数据不设置过期时间
  7. allkeys-lfu
    • 对全体key,基于LFU算法进行淘汰。
    • 短时高频访问
  8. volatile-lfu
    • 对设置了TTL的key,基于LFU算法进行淘汰。
    • 短时高频访问

分布式锁

集群架构下,用分布式锁解决线程之间的互斥性,有两种实现:setnx和Redisson

setnx

SET lock val NX EX 10 NX表示互斥:set if not exists,EX表示设置超时时间
DEL key 释放锁:

Redisson(基于setnx和lua)

RLock lock = redissonClient.getlock("锁名");
boolean isLock = lock.tryLock(10, TimeUnit.SECONDS);
if (islock){
  try{ 线程要执行的具体业务 } finally{ lock.unlock(); } 
}

👉控制锁时间的合理性:提供了一个watch dog机制来合理控制锁的有效时长,一个线程获得锁成功后,watch dog会给持有锁的线程续期【默认10s】

👉流程:一个线程来尝试加锁,成功后可以操作Redis,同时另开了一个线程进行监控,也就是watch dog,它会不断监听持有锁的线程,每隔(releaseTime / 3)的时间做一次续期,增加锁的使用时间,手动释放锁后,还需要通知watch dog不再监听。如果此时又有另外一个线程来尝试加锁,它会循环等待持有锁的线程释放锁,在高并发情况下增加了性能,但是等待时间超过阈值了以后也会停止

👉可重入吗?
可以,用hash结构记录线程id和重入次数【key是锁名,值是线程id和重入次数】

👉能解决主从一致性吗?
不能,但是可以用redisson提供的红锁,但不推荐,如果非要保证强一致性可以用zookeeper实现的分布式锁。

👉 执行了SETNX命令加锁后的风险和解决思路

  • 假如某个客户端在执行了SETNX命令加锁之后,在后面操作业务逻辑时发生了异常,没有执行 DEL 命令释放锁。该锁就会一直被这个客户端持有,其它客户端无法拿到锁,导致其它客户端无法执行后续操作。
    • 解决:给锁变量设置一个过期时间,到期自动释放锁 SET key value [EX seconds | PX milliseconds] [NX]
  • 如果客户端 A 执行了 SETNX 命令加锁后,客户端 B 执行 DEL 命令释放锁,此时,客户端 A 的锁就被误释放了。如果客户端 C 正好也在申请加锁,则可以成功获得锁。
    • 解决:加锁操作时给每个客户端设置一个唯一值(比如UUID),唯一值可以用来标识当前操作的客户端。在释放锁操作时,客户端判断当前锁变量的值是否和唯一标识相等,只有在相等的情况下,才能释放锁。(同一客户端线程中加锁、释放锁)SET lock_key unique_value NX PX 10000

Redis如何保证操作的原子性

  • 使用原子操作命令:如SET、HSET、SADD等。 Redis 是使用单线程串行处理客户端的请求来操作命令,这些命令在执行过程中不会被其他操作打断(相当于互斥)。
  • 使用事务:Redis支持事务操作,即一系列原子操作被封装为一个事务。当事务开始时,Redis会锁住数据,防止其他进程或线程对其进行修改。当事务执行完毕,锁才会被释放。这就保证了在事务执行期间,其他进程无法修改数据。
  • 锁机制:在多进程或多线程环境中,Redis通过使用锁机制来保证原子性。当一个进程或线程需要访问或修改数据时,它会先获取锁。只有当锁被成功获取,且没有其他进程或线程拥有锁时,该进程或线程才能执行数据操作。一旦操作完成,它就会释放锁,让其他进程或线程有机会获取。
  • Lua脚本
    【场景】两个客户端同时对[key1]执行自增操作,如何保证不会相互影响
    👉使用单命令操作:比如用Redis的INCRDECRSETNX 命令,把RMW三个操作转变为一个原子操作
    👉加锁: 调用SETNX命令对某个键进行加锁(如果获取锁则执行后续RMW操作,否则直接返回未获取锁提示)-> 执行RMW业务操作 -> 调用DEL命令删除锁
    👉Lua脚本:多个操作写到一个 Lua 脚本中(Redis 会把整个 Lua 脚本作为一个整体执行,在执行的过程中不会被其他命令打断,从而保证了 Lua 脚本中操作的原子性),限制所有客户端在一定时间范围内对某个方法(键)的访问次数。客户端 IP 作为 key,某个方法(键)的访问次数作为 value
local current current = redis.call("incr",KEYS[1])  //从Redis中获取名为 KEYS[1] 的键的当前值,并将其递增。
if tonumber(current) == 1  // 如果递增后的值为1,则设置该键的过期时间为60秒。
then redis.call("expire",KEYS[1],60) 
end

然后调用执行:redis-cli --eval lua.script keys , args

主从复制

单个Redis节点的并发能力是有上限的,可以搭建主从集群,实现读写分离来提高并发能力,一般是一主多从,主节点负责写数据,从节点负责读数据

全量同步

从节点第一次与主节点建立连接的时候使用全量同步

  1. 从节点请求主节点同步数据:从节点会携带自己的replication idoffset偏移量。
  2. 主节点判断是否是第一次请求,主要判断依据就是,主节点与从节点是否是同一个replication id,如果不是,就说明是第一次同步,那主节点就会把自己replication idoffset发送给从节点,让从节点与主节点的信息保持一致。
  3. 主节点执行bgsave指令生成rdb文件,发送给从节点去执行,从节点先把自己的数据清空,然后执行主节点发送过来的rdb文件。
  4. rdb生成的期间,主节点会以命令的方式记录到缓冲区(一个日志文件repl_baklog),会把这个日志文件也发送到主节点进行同步。

增量同步

slave重启或后期数据变化使用增量同步

  • 从节点请求主节点同步数据,主节点还是判断是不是第一次请求,不是第一次就获取从节点的offset值,然后主节点从命令日志repl_baklog中获取offset值之后的数据,发送给从节点进行数据同步。

哨兵sentinel

实现主从集群的自动故障恢复

作用:监测/选主/通知

  • 监测:Sentinel 会基于心跳机制不断检查master和slave是否按预期工作。【主观下线】如果某sentinel节点发现某实例未在规定时间响应,则认为该实例主观下线。【客观下线】若超过一半的sentinel都认为该实例主观下线,则该实例客观下线。
  • 选主:如果master故障,Sentinel会将一个slave提升为master。当故障实例恢复后也以新的master为主。首先判断主与从节点断开时间长短,如断开时间太长则不选举该从节点。然后判断从节点的slave-priority值,越小优先级越高。如果优先值相等,则判断从节点的offset值,越大优先级越高
  • 通知:Sentinel充当Redis客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给Redis的客户端。

脑裂问题

🎶由于网络等原因,使得哨兵无法心跳感知到主节点,于是通过选举的方式产生了一个新的主节点,于是就有了两个主节点,这样会导致客户端在老主节点那更新数据,新的主节点无法同步更新数据,产生数据丢失。
👉解决方案,配置参数:一个主节点至少需要有一个从节点,才允许写入。或者缩短主从数据同步的延迟时间。

分片集群

用来解决高并发写和海量存储问题

👉原理

  1. 集群中有多个master,每个master保存不同数据
  2. 每个master可以有多个slave节点
  3. master之间通过ping检测彼此健康状态,就无需哨兵了
  4. 客户端可以访问任意节点,最终都会经过路由转发到正确节点

👉存储和读取数据的原理:16384个哈希槽分配到不同的master节点

根据key的有效部分计算哈希值,对16384取余【有效部分,如果key前面有大括号,大括号的内容就是有效部分,如果没有,则以key本身做为有效部分】余数作为插槽,寻找插槽所在的节点

Redis中一致性需要注意的点

  1. 在我们用Redis主从复制或者集群模式的时候,需要确保主丛节点的同步,并考虑节点失效和故障恢复
  2. 读写操作的一致性
  3. 用Redis分片需要选择合适的分片策略,确保数据能够均匀分布,避免负载不均衡
  4. 使用Redis的事务和乐观锁保证原子性和一致性
  5. Redis用作缓存的时候要考虑与数据库的缓存一致

大key问题

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

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

相关文章

Bentley二次开发教程24-交互式类工具

交互式工具概念简述 本次内容主要涉及到交互式工具的使用,在MicroStation中,超过一半的功能都是以交互式工具的形式而存在的,因此交互式工具在MicroStation二次开发中便显得非常重要。当我们的鼠标或键盘在视图中产生交互操作时,…

各平台奇怪问题备忘录

微信小程序 小程序报错Page 页面路径 has not been register yet 描述:uniapp做微信小程序开发时,新增某页面后,小程序跳转该页面报错Page 页面路径 has not been register yet 已知:page.json已添加该页面,小程序a…

【Linux】文件目录及路径表示

1. Linux目录结构 在 Linux 系统中,有几个目录是比较重要的,平时需要注意不要误删除或者随意更改内部文件。 /etc: 这个是系统中的配置文件,如果更改了该目录下的某个文件可能会导致系统不能启动。 /bin, /sbin, /usr/bin, /usr…

vue快速入门(四十一)组件通信-依赖注入

注释很详细&#xff0c;直接上代码 上一篇 新增内容 祖先组件向下传值子代组件接受数据 源码 App.vue <template><div id"app"><sonComponent></sonComponent></div> </template> <script> import sonComponent from &qu…

python绘制随机地形地图

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 当我们谈论计算机编程中的地图生成时&#xff0c;通常会想到游戏开发、仿真模拟或者数据可视…

vue3 修改路由中的meta属性

有些时候可能需要在路由跳转前后修改meta里面的相关属性值&#xff0c;这个时候就需要使用钩子函数&#xff08;路由守卫&#xff09;&#xff0c;钩子函数有全局钩子&#xff0c;局部组件钩子函数以及路由配置里面的钩子函数 &#xff08;这些也叫路由守卫&#xff09; 1.全局…

python数字验证码自动识别

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 在网络上&#xff0c;许多网站和应用程序使用验证码&#xff08;Completely Automated Publ…

Ubuntu系统开机长

Ubuntu系统开机长 1. 检查开机自启动软件的所占时间2. 将耗时最高的禁止开机自启动 1. 检查开机自启动软件的所占时间 systemd-analyze blame2. 将耗时最高的禁止开机自启动 sudo systemctl disable networking.service这个耗时是有阈值的&#xff0c;一般大于15s的算&#x…

【数据结构与算法】8.二叉树的基本概念|前序遍历|中序遍历|后序遍历

&#x1f4da;博客主页&#xff1a;爱敲代码的小杨. ✨专栏&#xff1a;《Java SE语法》 | 《数据结构与算法》 | 《C生万物》 |《MySQL探索之旅》 |《Web世界探险家》 ❤️感谢大家点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb;&#xff0c;您的三连就是我持续更…

vue封装请求、合并js、合并多个js

vue封装请求、合并js、合并多个js 作为一个后端开发&#xff0c;写前端时发现&#xff0c;每次导入api接口都会有一堆代码&#xff0c;像下面这样&#xff1a; import {footprintList, footprintDelete} from /api/userApi.js import {addressList} from /api/userApi.js impor…

设置Linux开发板开机自启动QT程序的报错解决办法

设置Linux开发板开机自启动QT程序报错解决办法 设置开发板开机自启动QT 打开 /etc/init.d/rsC 文件&#xff0c;添加以下内容 cd / ./my_start_run.shmy_start_run.sh 是自己编写的自启动脚本&#xff0c;内容例如下&#xff1a;(也可以将这些直接写到 /etc/init.d/rsC 文件…

【算法刷题 | 贪心算法02】4.24(摆动序列)

文章目录 3.摆动序列3.1题目3.2解法&#xff1a;贪心3.2.1贪心思路3.2.2代码实现 3.摆动序列 3.1题目 如果连续数字之间的差严格地在正数和负数之间交替&#xff0c;则数字序列称为 摆动序列 。 第一个差&#xff08;如果存在的话&#xff09;可能是正数或负数。仅有一个元素…

嵌入式总线协议基础教学

在嵌入式系统设计中&#xff0c;总线协议&#xff08;bus protocols&#xff09;扮演着至关重要的角色&#xff0c;它们定义了设备如何在共享通信路径上交换数据。 本文将介绍两种常见的嵌入式总线协议&#xff1a;IC&#xff08;Inter-Integrated Circuit&#xff09;和SPI&a…

BFS解决FloodFill算法:(Leetcode:733. 图像渲染)

题目链接&#xff1a;733. 图像渲染 - 力扣&#xff08;LeetCode&#xff09; 使用广度优先遍历算法解决该问题&#xff1a; 从初始位置开始搜索&#xff0c;初始位置符合条件就入栈&#xff0c;并修改初始位置值。初始位置出栈。 再从初始位置开始广度优先搜索&#xff08;…

IDM下载器安装cmd注册

一、下载注册 安装包去IDM官网下载最新的试用版即可 或者直达百度网盘下载&#xff08;担心被河蟹&#xff0c;放在txt中了&#xff09;包含IDM下载器安装包和注册软件 IDM下载器安装包和注册软件下载地址链接 https://download.csdn.net/download/qq_31237581/89215452 如果…

sscanf和scanf区别

sscandf 从字符串中提取数据 scanf 标准输入流读取数据 int num; sscanf("42", "%d", &num);float f; sscanf("3.14", "%f", &f);char str[20]; sscanf("Hello, World!", "%s", str);int a, b; sscanf(…

vue3 引入@tsparticles/vue3和@tsparticles/slim 实现粒子特效

1.安装&#xff1a; yarn add tsparticles/vue3 tsparticles/slim2.main.ts 引入 import Particles from "tsparticles/vue3"; import { loadSlim } from "tsparticles/slim";app.use(Particles as any, {init: async (engine: any) > {await loadSli…

POJO,Entity,model,domain,view,DTO,VO,Param这些分别都是什么含义?怎样理解?

目录 1. 前言 2. POJO的含义 3. entity(实体) 4. model(模型) 5. domain(域) 6. view(视图) 7. DTO(数据传输对象) 8. VO(真正视图层) 9. Param(参数) 10. 总结 1. 前言 在日常开发的过程中&#xff0c;如果我们接手一个新的项目之后&#xff0c;通常会有各种各样的…

edu邮箱官方购买渠道手把手选购指南记录

教育优惠&#xff0c;是一项针对于在校大学生和教职员工推出的特殊优惠活动。一些公司会将旗下产品或服务以一定的折扣&#xff0c;甚至免费提供给高校师生。想想自己上大学的时候啥都不知道,毕业后才发现浪费了这么多优秀的资源.如果你还是一名在校大学生&#xff0c;那么就不…

40. 【Android教程】AsyncTask:异步任务

在前面的章节有提到过&#xff0c;Android 系统默认会在主线程&#xff08;UI 线程&#xff09;执行任务&#xff0c;但是如果有耗时程序就会阻塞 UI 线程&#xff0c;导致页面卡顿。这时候我们通常会将耗时任务放在独立的线程&#xff0c;然后通过 Handler 等线程间通信机制完…
最新文章