当前位置: 首页 > news >正文

《Redis实战篇》七、Redis消息队列

7.1 Redis消息队列-认识消息队列

什么是消息队列:字面意思就是存放消息的队列。最简单的消息队列模型包括3个角色:

  • 消息队列:存储和管理消息,也被称为消息代理(Message Broker)
  • 生产者:发送消息到消息队列
  • 消费者:从消息队列获取消息并处理消息

1653574849336

使用队列的好处在于 **解耦:**所谓解耦,举一个生活中的例子就是:快递员(生产者)把快递放到快递柜里边(Message Queue)去,我们(消费者)从快递柜里边去拿东西,这就是一个异步,如果耦合,那么这个快递员相当于直接把快递交给你,这事固然好,但是万一你不在家,那么快递员就会一直等你,这就浪费了快递员的时间,所以这种思想在我们日常开发中,是非常有必要的。

这种场景在我们秒杀中就变成了:我们下单之后,利用redis去进行校验下单条件,再通过队列把消息发送出去,然后再启动一个线程去消费这个消息,完成解耦,同时也加快我们的响应速度。

这里我们可以使用一些现成的mq,比如kafka,rabbitmq等等,但是呢,如果没有安装mq,我们也可以直接使用redis提供的mq方案,降低我们的部署和学习成本。

7.2 Redis消息队列-基于List实现消息队列

基于List结构模拟消息队列

消息队列(Message Queue),字面意思就是存放消息的队列。而Redis的list数据结构是一个双向链表,很容易模拟出队列效果。

队列是入口和出口不在一边,因此我们可以利用:LPUSH 结合 RPOP、或者 RPUSH 结合 LPOP来实现。
不过要注意的是,当队列中没有消息时RPOP或LPOP操作会返回null,并不像JVM的阻塞队列那样会阻塞并等待消息。因此这里应该使用BRPOP或者BLPOP来实现阻塞效果。

1653575176451

案例演示:

######生产者######
127.0.0.1:6379> LPUSH l1 e1 e2; 
(integer) 2
######消费者######
127.0.0.1:6379> BRPOP l1 20
1) "l1"
2) "e1"
(11.81s)
127.0.0.1:6379> BRPOP l1 20
1) "l1"
2) "e2;"
127.0.0.1:6379> BRPOP l1 20 #队列没有元素会被阻塞,到超时时间还没有就返货null,结束获取
(nil)
(20.10s)

基于List的消息队列有哪些优缺点?
优点:

  • 利用Redis存储,不受限于JVM内存上限
  • 基于Redis的持久化机制,数据安全性有保证
  • 可以满足消息有序性

缺点:

  • 无法避免消息丢失【比如刚从消息队列取出一条消息,还没来得及处理,Redis就发生宕机,这个消息就会丢失】
  • 只支持单消费者【一条消息只能被一个消费者消费,无法被多个消费者消费】

7.3 Redis消息队列-基于PubSub的消息队列

PubSub(发布订阅)是Redis2.0版本引入的消息传递模型。顾名思义,消费者可以订阅一个或多个channel,生产者向对应channel发送消息后,所有订阅者都能收到相关消息。

SUBSCRIBE channel [channel]:订阅一个或多个频道
PUBLISH channel msg :向一个频道发送消息
PSUBSCRIBE pattern[pattern] :订阅与pattern格式匹配的所有频道

1653575506373

案例演示

基于PubSub的消息队列有哪些优缺点?
优点:

  • 采用发布订阅模型,支持多生产、多消费【一个消息可以发给多个/部分消费者,不同生产者往相同的频道发】

缺点:

  • 不支持数据持久化
    • 为什么list作为消息队列可以持久化?是因为list本身是一个链表,用来做数据存储的。而我们把他当做消息队列来用了。而Redis中所有用来做数据存储的结构都支持持久化~ 而Pubsub就是用来做消息发送的,因此当我们发送一条消息,而这个消息没有被任何人订阅,频道没有被任何人订阅,那么这个消息就直接丢失了。
  • 无法避免消息丢失
    • 有人订阅消息就会被使用,没人订阅消息就会丢失~
  • 消息堆积有上限,超出时数据丢失
    • 如果发送消息时,有消费者在监听,在消费者那里有一个缓存区域,把消息缓存下来,让消费者去处理。如果消息处理的很慢,并且还有源源不断的消息到来,因为消费者那里的空间是有上限的,超出就会消息丢失~

总结:这种模式的缺点较多,不适合做可靠性较高的消息模式~

7.4 Redis消息队列-基于Stream的消息队列

Stream 是 Redis 5.0 引入的一种新数据类型,可以实现一个功能非常完善的消息队列。

发送消息的命令:

1653577301737

例如:

1653577349691

读取消息的方式之一:XREAD

1653577445413

例如,使用XREAD读取第一个消息:

1653577643629

XREAD阻塞方式,读取最新的消息:

1653577659166

在业务开发中,我们可以循环的调用XREAD阻塞方式来查询最新消息,从而实现持续监听队列的效果,伪代码如下

1653577689129

注意:当我们指定起始ID为$时,代表读取最新的消息,如果我们处理一条消息的过程中,又有超过1条以上的消息到达队列,则下次获取时也只能获取到最新的一条,会出现漏读消息的问题

案例演示:

###生产者
127.0.0.1:6379> XADD s1 * k1 v1 # 向队列中发送消息
"1675947993952-0"
127.0.0.1:6379> XLEN s1 #查看队列中的消息个数
(integer) 1
###消费者1&&消费者2
127.0.0.1:6379> XREAD COUNT 1 STREAMS s1 0 #读取队列中的第一条消息 [可以说明消息可回溯]
1) 1) "s1"2) 1) 1) "1675947993952-0"2) 1) "k1"2) "v1"###生产者
127.0.0.1:6379> XADD s1 * k2 v2 # 向队列中发送消息
"1675948153658-0"###消费者1
127.0.0.1:6379> XREAD COUNT 1 BLOCK 0 STREAMS s1 $ #阻塞读
1) 1) "s1"2) 1) 1) "1675948153658-0"2) 1) "k2"2) "v2"

STREAM类型消息队列的XREAD命令特点:

  • 消息可回溯【消息读完不消失,永久的保存在消息队列中,啥时候还想看可以随时回来】
  • 一个消息可以被多个消费者读取
  • 可以阻塞读取
  • 有消息漏读的风险【在消息处理的过程中,如果来了很多消息,我看不到,只能看到最新的消息】

7.5 Redis消息队列-基于Stream的消息队列-消费者组

消费者组(Consumer Group):将多个消费者划分到一个组中,监听同一个队列。具备下列特点:

image-20230210142845558

创建消费者组:
1653577984924
key:队列名称
groupName:消费者组名称
ID:起始ID标示,$代表队列中最后一个消息,0则代表队列中第一个消息
MKSTREAM:队列不存在时自动创建队列
其它常见命令:

删除指定的消费者组

XGROUP DESTORY key groupName

给指定的消费者组添加消费者

XGROUP CREATECONSUMER key groupname consumername

删除消费者组中的指定消费者

XGROUP DELCONSUMER key groupname consumername

从消费者组读取消息:

XREADGROUP GROUP group consumer [COUNT count] [BLOCK milliseconds] [NOACK] STREAMS key [key ...] ID [ID ...]
  • group:消费组名称

  • consumer:消费者名称,如果消费者不存在,会自动创建一个消费者

  • count:本次查询的最大数量

  • BLOCK milliseconds:当没有消息时最长等待时间

  • NOACK:无需手动ACK,获取到消息后自动确认

  • STREAMS key:指定队列名称

  • ID:获取消息的起始ID:

    • “>”:从下一个未消费的消息开始 【正常情况下】
    • 其它:根据指定id从pending-list中获取已消费但未确认的消息,例如0,是从pending-list中的第一个消息开始 【异常情况下】

案例演示:

###生产者
127.0.0.1:6379> XADD s1 * k1 v1  #向s1中加入消息
"1676013442138-0"
127.0.0.1:6379> XADD s1 * k2 v2
"1676013446154-0"
127.0.0.1:6379> XADD s1 * k3 v3
"1676013453085-0"
127.0.0.1:6379> XADD s1 * k4 v4
"1676013459707-0"
127.0.0.1:6379> XADD s1 * k5 v5
"1676013469043-0"
127.0.0.1:6379> XADD s1 * k6 v6
"1676013473875-0"
127.0.0.1:6379> XADD s1 * k7 v7
"1676013478635-0"
127.0.0.1:6379> XLEN s1 # 查看队列长度
(integer) 7
127.0.0.1:6379> XGROUP CREATE s1 g1 0 # 创建消费者组
OK### 消费者1
127.0.0.1:6379> XREADGROUP GROUP g1 c1 COUNT 1 BLOCK 2000 STREAMS s1 >
1) 1) "s1"2) 1) 1) "1676013442138-0"2) 1) "k1"2) "v1"
127.0.0.1:6379> XREADGROUP GROUP g1 c1 COUNT 1 BLOCK 2000 STREAMS s1 >
1) 1) "s1"2) 1) 1) "1676013446154-0"2) 1) "k2"2) "v2"
127.0.0.1:6379> XREADGROUP GROUP g1 c1 COUNT 1 BLOCK 2000 STREAMS s1 >
1) 1) "s1"2) 1) 1) "1676013469043-0"2) 1) "k5"2) "v5"### 消费者2
127.0.0.1:6379> XREADGROUP GROUP g1 c1 COUNT 1 BLOCK 2000 STREAMS s1 > 
1) 1) "s1"2) 1) 1) "1676013453085-0"2) 1) "k3"2) "v3"
127.0.0.1:6379> XREADGROUP GROUP g1 c1 COUNT 1 BLOCK 2000 STREAMS s1 >
1) 1) "s1"2) 1) 1) "1676013459707-0"2) 1) "k4"2) "v4"
###根据消费者1&消费者2的消费,可以看出消费组中的消费者是竞争关系的,并且同一个消费组中不会出现重复消费~### 消费者1
127.0.0.1:6379> XACK s1 g1 1676013442138-0 1676013446154-0 1676013453085-0 1676013459707-0 1676013469043-0
(integer) 5 #对前五条消息进行ACK确认
127.0.0.1:6379> XREADGROUP GROUP g1 c1 COUNT 1 BLOCK 2000 STREAMS s1 > #继续消费消息
1) 1) "s1"2) 1) 1) "1676013473875-0"2) 1) "k6"2) "v6"
127.0.0.1:6379> XREADGROUP GROUP g1 c1 COUNT 1 BLOCK 2000 STREAMS s1 >
1) 1) "s1"2) 1) 1) "1676013478635-0"2) 1) "k7"2) "v7"
127.0.0.1:6379> XREADGROUP GROUP g1 c1 COUNT 1 BLOCK 2000 STREAMS s1 >
(nil)
(2.08s)
# 由于1676013473875-0 和 1676013478635-0 被消费者消费后并没有ACK确认,会进入pending_list
127.0.0.1:6379> XPENDING s1 g1 - + 10 #查看pending_list中的消息,可以看出共两条消费失败的消息
1) 1) "1676013473875-0"2) "c1"3) (integer) 371544) (integer) 1
2) 1) "1676013478635-0"2) "c1"3) (integer) 353534) (integer) 1
127.0.0.1:6379> XREADGROUP GROUP g1 c1 COUNT 1 BLOCK 2000 STREAMS s1 0 #从pending_list中获取消息
1) 1) "s1"2) 1) 1) "1676013473875-0"2) 1) "k6"2) "v6"
127.0.0.1:6379> XACK s1 g1 1676013473875-0 # 消费完进行ACK确认
(integer) 1
127.0.0.1:6379> XREADGROUP GROUP g1 c1 COUNT 1 BLOCK 2000 STREAMS s1 0 #从pending_list中获取消息
1) 1) "s1"2) 1) 1) "1676013478635-0"2) 1) "k7"2) "v7"
127.0.0.1:6379> XACK s1 g1 1676013478635-0 # 消费完进行ACK确认
(integer) 1
127.0.0.1:6379> XREADGROUP GROUP g1 c1 COUNT 1 BLOCK 2000 STREAMS s1 0 #再次获取,pending_list已经为空
1) 1) "s1"2) (empty array)

消费者监听消息的基本思路:

image-20230210153808554

STREAM类型消息队列的XREADGROUP命令特点:

  • 消息可回溯
  • 可以多消费者争抢消息,加快消费速度
  • 可以阻塞读取
  • 没有消息漏读的风险[因为读取过的消息会有标记,下次直接从有标记的下一条消息读取即可]
  • 有消息确认机制,保证消息至少被消费一次

最后我们来个小对比

image-20230210145602447

Redis的Stream基本满足中小项目的需求,如果是大型项目,则可以使用专门的MQ:RocketMQ、RabbitMQ、Kfaka等

7.6 基于Redis的Stream结构作为消息队列,实现异步秒杀下单

需求:

  • 创建一个Stream类型的消息队列,名为stream.orders

  • 修改之前的秒杀下单Lua脚本,在认定有抢购资格后,直接向stream.orders中添加消息,内容包含voucherId、userId、orderId

  • 项目启动时,开启一个线程任务,尝试获取stream.orders中的消息,完成下单

①创建消息队列

127.0.0.1:6379> XGROUP CREATE stream.orders g1 0 MKSTREAM
OK

②修改lua表达式,新增3.5

image-20230211163737153

③VoucherOrderServiceImpl

 /*** 优惠券订单处理器【基于消息队列】*/
private class VoucherOrderHandler implements Runnable {private final static String queueName = "stream.orders";@Overridepublic void run() {while (true) {try {// 1.获取消息队列中的订单信息  XREADGROUP GROUP g1 c1 COUNT 1 BLOCK 2000 STREAMS stream.orders >List <MapRecord <String, Object, Object>> list = stringRedisTemplate.opsForStream().read(Consumer.from("g1", "c1"),StreamReadOptions.empty().count(1).block(Duration.ofSeconds(2)),StreamOffset.create(queueName, ReadOffset.lastConsumed()));// 2.判断消息是否获取成功if (list == null || list.isEmpty()) {// 如果获取失败,说明没有消息,继续下一次循环continue;}// 3.解析消息中的订单信息MapRecord <String, Object, Object> record = list.get(0);Map <Object, Object> values = record.getValue();VoucherOrder voucherOrder = BeanUtil.fillBeanWithMap(values, new VoucherOrder(),true);// 4.如果获取成功,可以下单handleVoucherOrder(voucherOrder);// 5.ACK确认 SACK strea.orders g1 idstringRedisTemplate.opsForStream().acknowledge(queueName,"g1",record.getId());} catch (Exception e) {log.error("处理订单异常:", e);handlePendingList();}}}/*** 处理PendingList中的订单*/private void handlePendingList() {while (true) {try {// 1.获取pending-list中的订单信息  XREADGROUP GROUP g1 c1 COUNT 1  STREAMS stream.orders 0List <MapRecord <String, Object, Object>> list = stringRedisTemplate.opsForStream().read(Consumer.from("g1", "c1"),StreamReadOptions.empty().count(1),StreamOffset.create(queueName, ReadOffset.from("0")));// 2.判断消息是否获取成功if (list == null || list.isEmpty()) {// 如果获取失败,说明pending-list没有异常消息,结束循环break;}// 3.解析消息中的订单信息MapRecord <String, Object, Object> record = list.get(0);Map <Object, Object> values = record.getValue();VoucherOrder voucherOrder = BeanUtil.fillBeanWithMap(values, new VoucherOrder(),true);// 4.如果获取成功,可以下单handleVoucherOrder(voucherOrder);// 5.ACK确认 SACK stream.orders g1 idstringRedisTemplate.opsForStream().acknowledge(queueName,"g1",record.getId());} catch (Exception e) {log.error("处理pending-list订单异常:", e);try {// 如果出现异常,休眠一会再尝试,避免一直尝试一直异常~Thread.sleep(20);} catch (InterruptedException interruptedException) {interruptedException.printStackTrace();}}}}
}
/*** 使用Lua脚本 + Stream消息队列实现秒杀下单** @param voucherId* @return*/
@Override
public Result seckillVoucher(Long voucherId) {// 获取用户idLong userId = UserHolder.getUser().getId();// 获取订单idlong orderId = redisIdWorker.nextId("order");// 1.执行Lua脚本Long result = stringRedisTemplate.execute(SECKILL_SCRIPT,Collections.emptyList(),voucherId.toString(),userId.toString(),String.valueOf(orderId));// 2.判断结果是否为0if (result != 0) {// 2.1 不为0,代表没有购买资格return Result.fail(result == 1 ? "库存不足" : "不能重复下单");}// 2.2 为0,有购买资格,把下单信息保存到消息队列【已经在LUA做过了】// 3.获取代理对象proxy = (IVoucherOrderService) AopContext.currentProxy();// 4. 返回订单idreturn Result.ok(orderId);
}

秒杀压测

image-20230211163645535

可以看出咱们秒杀的接口性能非常好~

相关文章:

《Redis实战篇》七、Redis消息队列

7.1 Redis消息队列-认识消息队列 什么是消息队列&#xff1a;字面意思就是存放消息的队列。最简单的消息队列模型包括3个角色&#xff1a; 消息队列&#xff1a;存储和管理消息&#xff0c;也被称为消息代理&#xff08;Message Broker&#xff09;生产者&#xff1a;发送消息…...

android组件化

学习流程&#xff1a;1.开源最佳实践&#xff1a;Android平台页面路由框架ARouter-阿里云开发者社区 (aliyun.com)2.中文ARouter使用API&#xff1a;https://github.com/alibaba/ARouter/blob/master/README_CN.md3.看当前文档后面的代码4.这是通俗易懂的文章&#xff1a;https…...

华为OD机试真题Python实现【特异性双端队列】真题+解题思路+代码(20222023)

🔥系列专栏 华为OD机试(Python)真题目录汇总华为OD机试(JAVA)真题目录汇总华为OD机试(C++)真题目录汇总华为OD机试(JavaScript)真题目录汇总文章目录 🔥系列专栏题目输入输出示例一输入输出解题思路核心知识点Python 代码实现代码运行结果版权说明<...

24.架构能力

文章目录24. 架构能力24.1 Competence of Individuals: Duties, Skills, and Knowledge of Architects 个人能力&#xff1a;架构师的职责、技能和知识24.2 Competence of a Software Architecture Organization 软件架构组织的能力24.3 Summary 小结24.4 For Further Reading …...

前端原生 CSS 跑马灯效果,无限轮播(横竖版本,带渐变遮罩,简单实用)

一、横版跑马灯 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevice-wid…...

4.8 注解与自定义注解

文章目录1.概述2.注解的分类2.1 JDK注解2.2 元注解2.2.1 Target ElementType…2.2.2 Retention RetentionPolicy…3 自定义注解1.概述 在注解刚出现时&#xff0c;曾受到过好多程序员的鄙夷&#xff0c;觉得这就是多此一举的操作&#xff1b; 但随着时间的推移&#xff0c;越…...

webpack 的热更新是如何做到的?原理是什么?

Hot Module Replacement&#xff0c;简称 HMR&#xff0c;在不需要刷新整个页面的同时更新模块&#xff0c;能够提升开发的效率和体验。热更新时只会局部刷新页面上发生了变化的模块&#xff0c;同时可以保留当前页面的状态&#xff0c;比如复选框的选中状态等。 在 webpack 中…...

嵌入式ARM设计编程(一) 简单数据搬移

文章和代码已归档至【Github仓库&#xff1a;hardware-tutorial】&#xff0c;需要的朋友们自取。或者公众号【AIShareLab】回复 嵌入式 也可获取。 一、实验目的 熟悉实验开发环境&#xff0c;掌握简单ARM汇编指令的使用方法。 二、实验环境 硬件&#xff1a;PC机 软件&am…...

【Selenium】十分钟手把手带你学会WebDriver API

目录 1、定位元素【8种】 2、操作测试对象 3、添加等待 4、弹窗类型 5、浏览器的操作 6、键盘事件 7、选择框 8、上传文件 1、定位元素【8种】 元素定位是自动化测试的核心&#xff0c;想要去操作一个对象&#xff0c;第一步就是需要我们先去识别这个对象。每个对象就会…...

3DMAX高级弯曲插件使用教程

3dMax高级弯曲插件是对3dmax原生“弯曲&#xff08;Bend&#xff09;”修改器的一个增强&#xff0c;给用户更多控制弯曲修改器的参数设置&#xff0c;它让用户输入宽度&#xff0c;插件脚本将移动中心以获得正确的宽度。 主要特性&#xff1a; - 使用智能捕捉捕捉到自定义网格…...

前端面试题之性能优化大杂烩

主要内容为下面几大类&#xff1a;移动端、图片、JavaScript、css、html、页面内容、服务器、cookie。 移动端性能优化&#xff1a; 保持单个文件小于25KB 移动网站页面要求下载资源&#xff0c;如果文件过大&#xff0c;会大大减慢页面加载速度。 打包内容为分段multipart文…...

SpringBoot+Vue实现养老智慧服务平台

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7/8.0 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.3.9 浏…...

tigervnc2023

sudo apt-get install tigervnc-standalone-server 配置用户 /etc/tigervnc/vncserver.users :1user1 :2user2 :3user3 全局配置 /etc/tigervnc/vncserver-config-defaults $localhost"no"; $geometry "1920x1200"; 分别进入user1 user2 user3 用户…...

智能三子棋(人机大战)—— 你会是最终赢家吗?万字讲解让你实现与自己对弈

魔王的介绍&#xff1a;&#x1f636;‍&#x1f32b;️一名双非本科大一小白。魔王的目标&#xff1a;&#x1f92f;努力赶上周围卷王的脚步。魔王的主页&#xff1a;&#x1f525;&#x1f525;&#x1f525;大魔王.&#x1f525;&#x1f525;&#x1f525; ❤️‍&#x1…...

【自制开发板】自制STM32F407开发板(含TFT 8080串口屏幕接口)

【2023 年 2 月 14 日】 许久没有更新&#xff0c;最近做了个小开发板玩了玩。更新一下吧&#xff0c;作为记录&#xff01;&#xff01; 主要是象试一下LVGL在STM32上的应用&#xff0c;所以开发板的大小都是基于屏幕大小来设计的。 分享出来&#xff0c;给大家一个板子结构…...

openvino yolov5/ssd 实时推流目标检测在html上显示

安装ffmepg并添加到环境变量中&#xff0c;流媒体使用m7s 运行效果 SSD&#xff1a;检测在10ms左右&#xff0c;yolov5在100ms左右 app.py #!/usr/local/bin/python3 # encodin: utf-8import subprocess import threading import time import cv2 import osfrom OpenVinoYoloV…...

基于FPGA的 SPI通信 设计(1)

引言 低速通信目前搞过 UART串口通信、IIC通信。其实 SPI 也算是中低速&#xff08;有时也可以用作高速通信&#xff09;串行通信的范畴&#xff0c;但是一直还没真正实现过&#xff0c;所以此系列就 SPI的协议以及FPGA设计作几篇博客记录。欢迎订阅关注~ SPI 标准协议 x1模式…...

为什么西门子、美的等企业这样进行架构升级,看看改造效果就知道了

在工业领域&#xff0c; 生产、测试、运行阶段都可能会产生大量带有时间戳的传感器数据&#xff0c;这都属于典型的时序数据。时序数据主要由各类型实时监测、检查与分析设备所采集或产生&#xff0c;涉及制造、电力、化工、工程作业等多个行业&#xff0c;具备写多读少、量非常…...

open3d点云配准函数registration_icp

文章目录基本原理open3d调用绘图基本原理 ICP, 即Iterative Closest Point, 迭代点算法。 ICP算法有多种形式&#xff0c;其中最简单的思路就是比较点与点之间的距离&#xff0c;对于点云P{pi},Q{qi}P\{p_i\}, Q\{q_i\}P{pi​},Q{qi​}而言&#xff0c;如果二者是同一目标&am…...

HTML编码规范

本篇文章是基于王叨叨大佬师父维护的文档梳理的&#xff0c;有兴趣可以去看一下原文HTML编码规范。 1. 缩进与换行 【建议】 使用 2 个空格作为一个缩进层级&#xff0c;不允许使用tab字符 解释&#xff1a; ​ 具体项目&#xff0c;可以使用2个空格&#xff0c;也可以使用…...

PDF SDK for Linux 8.4.2 Crack

PDF SDK for Linux 是适用于任何 Linux 企业或云应用程序的强大解决方案&#xff0c;非常适合需要完全可定制的 PDF 查看器或后端流程的任何 Linux 开发人员。 将 Foxit PDF SDK 嵌入到基于 Linux 的应用程序中非常容易。只需打开您最喜欢的 Linux IDE&#xff0c;复制您需要的…...

vb 模块和作用域的关系

模块在VB中有三种类型的模块&#xff0c;分别是窗体模块、标准模块和类模块。窗体模块窗体模块中包含了窗体以及窗体中所有控件的事件过程&#xff0c;文件扩展名为&#xff08;*.frm)&#xff0c;窗体文件中不仅包含窗体对象的外观设计&#xff0c;也包含窗体模块&#xff08;…...

Redis分布式锁

一、背景 与分布式锁相对应的是「单机锁」&#xff0c;我们在写多线程程序时&#xff0c;避免同时操作一个共享变量产生数据问题&#xff0c;通常会使用一把锁来「互斥」&#xff0c;以保证共享变量的正确性&#xff0c;其使用范围是在「同一个进程」中。单机环境下&#xff0…...

京东前端经典面试题整理

img的srcset属性的作⽤&#xff1f; 响应式页面中经常用到根据屏幕密度设置不同的图片。这时就用到了 img 标签的srcset属性。srcset属性用于设置不同屏幕密度下&#xff0c;img 会自动加载不同的图片。用法如下&#xff1a; <img src"image-128.png" srcset&qu…...

django+mysql实现一个简单的web登录页面

目录 一、使用pyacharm创建一个django项目 二、启动django项目验证 三、配置mysql数据库 1、本地安装mysql数据库 1&#xff09;安装mysql数据库 2&#xff09;自己创建一个数据库 2、安装 pymysql 3、配置mysql数据库 1&#xff09;在项目同名包下的_init_.py里面添加…...

python cartopy手动导入地图数据绘制底图/python地图上绘制散点图:Downloading:warnings/散点图添加图里标签

……开学回所&#xff0c;打开电脑spyder一看一脸懵逼&#xff0c;简直不敢相信这些都是我自己用过的代码&#xff0c;想把以前的自己喊过来科研了&#xff08;&#xff09; 废话少说&#xff0c;最近写小综述论文&#xff0c;需要绘制一个地图底图&#xff0b;散点图&#xff…...

JavaScript中常用的数组方法

在日常开发中&#xff0c;我们会接触到js中数组的一些方法&#xff0c;这些方法对我们来说&#xff0c;可以很便利的达到我们想要的结果&#xff0c;但是因为方法比较多&#xff0c;有些方法也不常用&#xff0c;可能会过一段时间就会忘记&#xff0c;那么在这里我整理了一些数…...

磁疗为什么“没效果”?原来真相是这样!

很多人磁疗之后&#xff0c; 总爱迫不及待问一个问题&#xff1a; “这个多长时间见效啊&#xff1f;” …… 还有些人几天没有效果&#xff0c; 就果断下结论&#xff1a; “这东西没用&#xff01;” …… 有不少人错误地把磁疗等同于“药品”一样看待&#xff0c;总觉得…...

【直击招聘C++】5.1函数模板

5.1函数模板一、要点归纳1.定义函数模板2.实例化函数模板3.重载模板函数4.函数调用的匹配顺序一、要点归纳 1.定义函数模板 定义函数模板的一般格式如下&#xff1a; template<类型形参表> 返回类型 函数名&#xff08;形参表&#xff09; {函数体&#xff1b; }例如以…...

谈谈Java多线程离不开的AQS

如果你想深入研究Java并发的话&#xff0c;那么AQS一定是绕不开的一块知识点&#xff0c;Java并发包很多的同步工具类底层都是基于AQS来实现的&#xff0c;比如我们工作中经常用的Lock工具ReentrantLock、栅栏CountDownLatch、信号量Semaphore等&#xff0c;而且关于AQS的知识点…...

wordpress附件/站内优化主要从哪些方面进行

清除浮动之父级添加overflow 这样就能很好的解决这个问题了。 缺点&#xff1a;无法显示溢出的部分呀...

b2b网站论文/今日足球赛事推荐

以下是全部VolleyError实例可能&#xff0c;如果需要逐条判断可以方便用户了解原因。 //正如前面代码看到的&#xff0c;在创建一个请求时&#xff0c;需要添加一个错误监听onErrorResponse。如果请求发生异常&#xff0c;会返回一个VolleyError实例。 //以下是Volley的异常列…...

衢州网络公司做网站/深圳seo推广公司

关于 Python 的字符串处理相关的方法还是许多的。因为我正在学习 Python&#xff0c;于是就把 Python 中这些混杂的用于 string 的函数总结出来&#xff0c;在自己忘记的时候便于查找&#xff0c;希望对于有相似需求的人有所帮助。captalize() 函数功能将一个字符串的第一个字母…...

网站建设售前说明书/如何制作一个自己的网站

Open*** 服务器部署 当时公司架设 服务器的目的是能让工作在外地员工通过单点登录的方式&#xff08;PC-LAN方式&#xff09;连公司内部的服务器&#xff0c;协同内部员工工作&#xff0c;同时也要考虑以后办事处与总部的&#xff08;LAN-LAN方式&#xff09;互连&#xff0c;认…...

上海免费网站建设/宁波seo教学

作者&#xff1a;张志朋出处&#xff1a;https://blog.52itstyle.com题记工作也有几多年了&#xff0c;无论是身边遇到的还是耳间闻到的&#xff0c;多多少少也积攒了自己的一些经验和思考&#xff0c;当然&#xff0c;博主并没有太多接触高大上的分布式架构实践&#xff0c;相…...

北京市建设工程招投标/seo个人博客

一、LongAdder简介 1.下图是JDK-API文档的的截图信息 我们可以得知这两个类都是1.8开始提供的&#xff0c;并且都具有顶级的并发性。这两类的区别点主要在于LongAdder初始值为0&#xff0c;只能做累加操作&#xff0c;而LongAccumulator可以完成一些复杂的计算&#xff0c;本…...