Redis 高级特性 Redis Stream使用

Redis Stream 简介

Redis Stream 是 Redis 5.0 版本新增加的数据结构。
Stream从字面上看是流类型,但其实从功能上看,应该是Redis对消息队列(MQ,Message Queue)的完善实现。下文称Stream为队列

Stream 出现原因
Stream的出现是为了给Redis提供完善的消息队列功能

基于Reids的消息队列实现有很多种,例如:

  • PUB/SUB,订阅/发布模式
  • 基于List的 LPUSH+BRPOP 的实现
  • 基于有序集合的实现
类型 优点 缺点
List 支持阻塞式的获取消息 没有消息多播功能,没有ACK机制,无法重复消费等等
Pub/Sub 支持消息多播 消息无法持久化,只管发送,如果出现网络断开、Redis宕机等,消息就直接没了,自然也没有ACK机制。
Sorted Set 支持延时消息 不支持阻塞式获取消息、不允许重复消费、不支持分组。

发布订阅模式

Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。
image

当发布者向channel中发布消息时,所有订阅了channel的客户端都会收到消息。

订阅者首先订阅channel

psubscribe news

image

发布者发布消息

publish news "hello world"

image

所有的订阅者都收到了消息。

致命缺点
Redis的Pub/Sub为什么被抛弃?
最主要的原因是它无法持久化,没有实现持久化机制的Pub/Sub,无法做到消息的不丢失,在客户端宕机或者Redis服务宕机的情况下,都会导致消息丢失。

Stream

Stream弥补了Redis作为消息队列技术选型上的不足之处。
Redis 5.0发布的Stream相比Pub/Sub模块,Stream支持消息持久化,结合集群使其成为了一个比较可靠的消息队列。

队列结构图:
image

Stream 实现的功能包括如下:

  1. 提供了消息多播的功能,同一个消息可被分发给多个单消费者和消费者组

  2. 提供了消息持久化的功能,可以让任何消费者访问任何时刻的历史消息

  3. 提供了对于消费者和消费者组的阻塞、非阻塞的获取消息的功能

  4. 提供了强大的消费者组的功能:

  • 消费者组实现同组多个消费者并行但不重复消费消息的能力,提升消费能力;
  • 消费者组能够记住最新消费的信息,保证消息连续消费;
  • 消费者组提供了ACK确认机制,保证消息被成功消费,不丢失;

Stream本质上是Redis中的key,相关指令根据可以分为两类,分别是消息队列相关指令,消费组相关指令。

消息队列相关指令:

指令名称 指令作用
XADD 添加消息到队列末尾
XTRIM 限制Stream的长度,如果已经超长会进行截取
XDEL 删除消息
XLEN 获取Stream中的消息长度
XRANGE 获取消息列表(可以指定范围),忽略删除的消息
XREVRANGE 和XRANGE相比区别在于反向获取,ID从大到小
XREAD 获取消息(阻塞/非阻塞),返回大于指定ID的消息

消费者相关指令:

指令名称 指令作用
XGROUP CREATE 创建消费者组
XREADGROUP 读取消费者组中的消息
XACK ack消息,消息被标记为“已处理”
XGROUP SETID 设置消费者组最后递送消息的ID
XGROUP DELCONSUMER 删除消费者组
XPENDING 打印待处理消息的详细信息
XCLAIM 转移消息的∂归属权(长期未被处理/无法处理的消息,转交给其他消费者组进行处理)
XINFO 打印Stream\Consumer\Group的详细信息
XINFO GROUPS 打印消费者组的详细信息
XINFO STREAM 打印Stream的详细信息

消息队列操作

XADD

使用XADD命令添加消息到队列末尾,如果指定的 队列不存在,则该命令执行时会新建一个队列。
添加的消息是一个和多个键值对。XADD也是唯一可以向队列中添加数据的 Redis 命令。

语法格式:

XADD key ID field value [field value ...]
  • key:队列名称,如果不存在就创建
  • ID:消息id,使用*表示由redis生成。可以自定义,但是要自己保证递增性
  • field value:记录,当前消息内容,由一个或多个key-value构成

命令使用:
创建两条消息,分别是(name=tom, age=22),(height=180, use=iphone)

127.0.0.1:6379> xadd mystream * name tom age 22
"1674984765438-0"
127.0.0.1:6379> xadd mystream * height 180 use iphone
"1674985213802-0"

创建消息时会生成一个序号,支持自定义序号和自动生成序号。*表示自动生成序号

XLEN

使用XLEN获取队列包含的元素数量,即消息长度
语法格式:

XLEN key

命令使用:

127.0.0.1:6379> xlen mystream
(integer) 2

XDEL

使用XDEL删除消息。语法格式:

XDEL key ID [ID ...]

XDEL删除消息的指令,并不会从内存上删除消息,它只是给消息打上标记位,下次通过XRANGE指令忽略这些消息

XRANGE

使用XRANGE获取消息列表,会自动过滤已经删除的消息,语法格式:

XRANGE key start end [COUNT count]
  • key:队列名
  • start:开始值,-表示最小值
  • end:结束值,+表示最大值
  • count:数量

命令使用:
不指定count默认查询所有

127.0.0.1:6379> xrange mystream - + 
1) 1) "1674984765438-0"
   2) 1) "name"
      2) "tom"
      3) "age"
      4) "22"
2) 1) "1674985213802-0"
   2) 1) "height"
      2) "180"
      3) "use"
      4) "iphone"
127.0.0.1:6379> 

XREAD

XREAD命令提供读取队列消息的能力,返回大于指定ID的消息。
XREAD常用于用于迭代队列的消息,所以传递给 XREAD 的通常是上一次从该队列接收到的最后一个消息的ID。

语法格式:

XREAD [COUNT count] [BLOCK milliseconds] STREAMS key [key ...] id [id ...]
  • count:用于限定获取的消息数量
  • BLOCK milliseconds:用于设置XREAD为阻塞模式以及阻塞的时长,单位毫秒,默认为非阻塞模式
  • ID:设置开始读取的消息ID,使用0表示从第一条消息开始。
    消息队列ID是单调递增的,所以通过设置起点,可以向后读取。
    在阻塞模式中,可以使用$,表示最新的消息ID, block 0表示永久阻塞。(非阻塞模式下$无意义)。

命令使用:

非阻塞读取
从第一条消息开始

127.0.0.1:6379> xread streams mystream 0
1) 1) "mystream"
   2) 1) 1) "1674984765438-0"
         2) 1) "name"
            2) "tom"
            3) "age"
            4) "22"
      2) 1) "1674985213802-0"
         2) 1) "height"
            2) "180"
            3) "use"
            4) "iphone"
127.0.0.1:6379> 

阻塞读取

127.0.0.1:6379> xread block 10000 streams mystream $
(nil)
(10.04s)
127.0.0.1:6379>

阻塞模式读,阻塞时长为10s。如果10s内未读取到消息则退出阻塞。另开一个终端向队列中写入一条消息,阻塞读的终端就能接收到消息。
image

消费者操作

XGROUP CREATE

创建消费组。消费组用于管理消费者和队列读取记录。Stream中的消费组有两个特点:

  1. 从资源结构上说消费者从属于一个消费组
  2. 一个队列可以拥有多个消费组。不同消费组之间读取队列互不干扰

语法格式:

XGROUP [CREATE key groupname id-or-$] [SETID key groupname id-or-$] [DESTROY key groupname] [DELCONSUMER key groupname consumername]
  • key:队列名称,如果不存在就创建
  • groupname:组名
  • id: $表示从尾部开始消费,只接受新消息,当前Stream消息会全部忽略

命令使用:

为队列mystream创建一个消费组 mqGroup,从第一个消息开始读

127.0.0.1:6379> XGROUP CREATE mystream mqGroup 0
OK

XREADGROUP

读取队列的消息。在读取消息时需要指定消费者,只需要指定名字,不用预先创建。

语法格式:

XREADGROUP GROUP group consumer [COUNT count] [BLOCK milliseconds]
  [NOACK] STREAMS key [key ...] id [id ...]
  • group:消费组名
  • consumer:消费者名
  • count:读取数量
  • BLOCK milliseconds:阻塞读以及阻塞毫秒数。默认非阻塞。和XREAD类似
  • key:队列名
  • id:消息ID。ID可以填写特殊符号>,表示未被组内消费的起始消息

命令使用:
创建消费者consumerA和consumerB,各读取一条消息

127.0.0.1:6379> XREADGROUP GROUP mqGroup consumerA COUNT 1 STREAMS mystream >
1) 1) "mystream"
   2) 1) 1) "1674984765438-0"
         2) 1) "name"
            2) "tom"
            3) "age"
            4) "22"
            
127.0.0.1:6379> XREADGROUP group mqGroup consumerB count 1 streams mystream >
1) 1) "mystream"
   2) 1) 1) "1674985213802-0"
         2) 1) "height"
            2) "180"
            3) "use"
            4) "iphone"

可以进行组内消费的基本原理是,STREAM类型会为每个组记录一个最后读取的消息ID(last_delivered_id),这样在组内消费时,就可以从这个值后面开始读取,保证不重复消费。

消费组消费时,还有一个必须要考虑的问题,就是若某个消费者,消费了某条消息,但是并没有处理成功时(例如消费者进程宕机),这条消息可能会丢失,因为组内其他消费者不能再次消费到该消息了

XPENDING

为了解决组内消息读取但处理期间消费者崩溃带来的消息丢失问题,Stream 设计了 Pending 列表,用于记录读取但并未确认完毕的消息。
语法格式:

XPENDING key group [[IDLE min-idle-time] start end count [consumer]]
  • key:队列名
  • group: 消费组名
  • start:开始值,-表示最小值
  • end:结束值,+表示最大值
  • count:数量

命令使用:

首先查看队列中的消息数量有3个,然后查看已读取未处理的消息有两个。

127.0.0.1:6379> xlen mystream
(integer) 3

127.0.0.1:6379> xpending mystream mqGroup
1) (integer) 2 # 2个已读取但未处理的消息
2) "1674984765438-0" # 起始ID
3) "1674985213802-0" # 结束ID
4) 1) 1) "consumerA"  # 消费者A有1个
      2) "1"
   2) 1) "consumerB"  # 消费者B有1个
      2) "1"

队列中一共三条信息,有两条被消费但未处理完毕,也就是上面XREADGROUP消费的两条。一个是消费者consumerA,另一个是consumerB。

获取未确认的详细信息

127.0.0.1:6379> xpending mystream mqGroup - + 10
1) 1) "1674984765438-0"
   2) "consumerA"
   3) (integer) 12110001
   4) (integer) 1
2) 1) "1674985213802-0"
   2) "consumerB"
   3) (integer) 89140701
   4) (integer) 1

XACK

对于已读取未处理的消息,使用命令 XACK 完成告知消息处理完成
XACK 命令确认消费的信息,一旦信息被确认处理,就表示信息被完善处理。

语法格式:

XACK key group id [id ...]
  • key: stream 名
  • group:消费组
  • id:消息ID

命令使用:

确认消息1674985213802-0

127.0.0.1:6379> XACK mystream mqGroup 1674985213802-0
(integer) 1
127.0.0.1:6379> 

XCLAIM

某个消费者读取了消息但没有处理,这时消费者宕机或重启等就会导致该消息失踪。那么就需要该消息转移给其他的消费者处理,就是消息转移。XCLAIM来实现消息转移的操作。

语法格式:

XCLAIM key group consumer min-idle-time id [id ...] [IDLE ms]
  [TIME unix-time-milliseconds] [RETRYCOUNT count] [FORCE] [JUSTID]
  [LASTID id]
  • key: 队列名称
  • group :消费组
  • consumer:消费组里的消费者
  • min-idle-time 最小时间。空闲时间大于min-idle-time的消息才会被转移成功
  • id:消息的ID

转移除了要指定ID外,还需要指定min-idle-time,min-idle-time是最小空闲时间,该值要小于消息的空闲时间,这个参数是为了保证是多于多长时间的消息未处理的才被转移。比如超过24小时的处于pending未xack的消息要进行转移
同时min-idle-time还有一个功能是能够避免两个消费者同时转移一条消息。被转移的消息的IDLE会被重置为0。假设两个消费者都以2min来转移,第一个成功之后IDLE被重置为0,第二个消费者就会因为min-idle-time大与空闲时间而是失败。

命令使用:
目前未确认的消息

127.0.0.1:6379> xpending mystream mqGroup - + 10
1) 1) "1674984765438-0"
   2) "consumerA"
   3) (integer) 12145595
   4) (integer) 1

id: 1674984765438-0
空闲时间:12145595,单位ms
读取次数:1

将cosumerA未处理的消息转移给consumerB。

127.0.0.1:6379> XCLAIM mystream mqGroup consumerB 3600000 1674984765438-0
1) 1) "1674984765438-0"
   2) 1) "name"
      2) "tom"
      3) "age"
      4) "22"

查看未确认的消息
消息已经从consumerA转移给consumerB,IDLE重置,读取次数加1。转移之后就可以继续处理这条消息。

127.0.0.1:6379> xpending mystream mqGroup - + 10
1) 1) "1674984765438-0"
   2) "consumerB"
   3) (integer) 5729 # 注意IDLE,被重置了
   4) (integer) 2 # 注意,读取次数也累加了1次

通常转移操作的完整流程是:

  1. 先用xpending命令找出所有未确认的消息
  2. 再用xclaim命令转移所有未确认消息

在redis6.2.0之后有一个命令XAUTOCLAIM,可以将xpending查找未确认消息和xclaim转移消息合并成一个操作。

XINFO

Stream提供了XINFO来实现对服务器信息的监控

查看队列信息

127.0.0.1:6379> xinfo stream mystream
 1) "length"
 2) (integer) 3
 3) "radix-tree-keys"
 4) (integer) 1
 5) "radix-tree-nodes"
 6) (integer) 2
 7) "groups"
 8) (integer) 1
 9) "last-generated-id"
10) "1674985995856-0"
11) "first-entry"
12) 1) "1674984765438-0"
    2) 1) "name"
       2) "tom"
       3) "age"
       4) "22"
13) "last-entry"
14) 1) "1674985995856-0"
    2) 1) "name"
       2) "jack"

消费组信息

127.0.0.1:6379> xinfo groups mystream
1) 1) "name"
   2) "mqGroup"
   3) "consumers"
   4) (integer) 2
   5) "pending"
   6) (integer) 1
   7) "last-delivered-id"
   8) "1674985213802-0"

消费者组成员信息

127.0.0.1:6379> xinfo consumers mystream mqGroup
1) 1) "name"
   2) "consumerA"
   3) "pending"
   4) (integer) 0
   5) "idle"
   6) (integer) 12904777
2) 1) "name"
   2) "consumerB"
   3) "pending"
   4) (integer) 1
   5) "idle"
   6) (integer) 696457
127.0.0.1:6379> 

项目中中Stream的使用

项目中部分web请求的处理是异步处理,web服务调用底层模块异步执行。当底层模块处理完成后需要保存结果并通知web服务,所以使用Stream作为保存的载体。
image

Stream 的生产和消费

生产
向队列中写消息

def batch_xadd(self, name: str, payloads: List[Dict]) -> None:
    pipe = self._redis.pipeline()
    for payload in payloads:
        pipe.xadd(name, payload)
    pipe.execute()

消费
定时任务间隔10s从队列中读消息

while True:
    
    _, payloads = await self._conn.xautoclaim(
        self.stream_name, self.group_name, self.consumer_name, min_idle_time
    )
    
    id_ = last_id if check_backlog else ">"
    for _, messages in await self._conn.xreadgroup(
        groupname=self.group_name,
        consumername=self.consumer_name,
        streams={self.stream_name: id_},
        block=block_timeout,
    ):
        ...
        last_id = messages[-1][0]
        payloads += messages
    
    # 处理队列中取出的消息,耗时操作
    successful_ids = await f_processor(payloads)
    if successful_ids:
        await self._conn.xack(self.stream_name, self.group_name, *successful_ids)

Stream和专业消息队列对比

专业的消息队列包括:

  1. RabbitMQ
  2. RocketMQ
  3. Kafka

一个专业的消息队列,必须要满足两个条件:

  1. 消息不丢
  2. 消息可堆积

下面从这两个方面来对比Stream和专业消息队列。

消息不丢

消息队列的使用模型如下:
image

要保证消息不丢,就需要在生产者、中间件、消费者这三个方面来分析。

生产者:消息发送失败或发送超时,这两种情况会导致数据丢失,可以使用重试来解决。不依赖消息中间件,需要业务实现。

消费者:消费者存在读取消息未处理完就异常宕机了,消费者要还能重新读取消息。Stream和其他消息中间件都能做到。

队列中间件:中间件要保证数据不丢失。 Redis 在以下 2 个场景下,都会导致数据丢失:

  1. AOF 持久化配置为每秒写盘,Redis 宕机时会存在丢失最后1秒数据的可能
  2. 主从复制的集群,主从切换时,从库还未同步完成主库发来的数据,就被提成主库,也存在丢失数据的可能。

基于以上原因可以推断出,Redis 本身的无法保证严格的数据完整性。

专业队列如何解决数据丢失问题:
RabbitMQ 或 Kafka 这类专业的队列中间件,在使用时一般是部署一个集群。生产者在发布消息时,队列中间件通常会写「多个节点」,以此保证消息冗余。这样一来,即便其中一个节点挂了,集群也能的数据不丢失。

消息积压

因为 Redis 的数据都存储在内存中,这就意味着一旦发生消息积压,则会导致 Redis 的内存持续增长,如果超过机器内存上限,就会面临 OOM 的风险。

所以,Redis 的 Stream 提供了可以指定队列最大长度的功能,就是为了避免这种情况发生。

但 Kafka、RabbitMQ 这类消息队列就不一样了,它们的数据都会存储在磁盘上,磁盘的成本要比内存小得多,当消息积压时,无非就是多占用一些磁盘空间,磁盘相比于内存在面对积压时能轻松应对。

总结

综上可以看到,把 Redis 当作队列来使用时,始终面临两个问题:

  1. Redis 本身可能会丢数据
  2. 面对消息积压,Redis 内存资源紧张

优缺点

优点

  1. 使用成本低。几乎每一个项目都会使用Redis,用Stream做消息队列就不需要额外再引入中间件,减少系统复杂性,运维成本,硬件资源。

缺点

  1. Redis 的数据都存储在内存中,内存持续增长超过机器内存上限,就会面临 OOM 的风险
  2. Stream 作为Redis的一种数据结构,Redis 在持久化或主从切换时有丢失数据的风险,所以Stream也有丢失消息的风险
  3. 所有的消息会一直保存在Stream中,没有删除机制。要么定时清除,那么设置队列的长度自动丢弃先入列消息

使用场景

适用
适用业务场景:

  • 场景足够简单
  • 对于数据丢失不敏感
  • 消息积压概率比较小

满足以上场景把 Redis 当作队列是完全可以的。
基于redis的高性能和使用内存的机制使得其的性能优于大部分消息队列。在小规模场景会有更出色的表现。

不适用
不适用业务场景:

  • 对于数据丢失非常敏感,如订单系统
  • 写入量非常大,并发请求大
  • 消息积压时会占用很多的内存资源,消息数据量大

这些业务场景下建议使用专业的消息队列中间件。

题外话
技术选型出了技术本身之外还要考虑公司团队能否匹配技术。

Kafka、RabbitMQ 是非常专业的消息中间件,但它们的部署和运维,相比于 Redis 来说,也会更复杂一些。

如果在一个大公司,公司本身就有优秀的运维团队,那么使用这些中间件肯定没问题,因为有足够优秀的人能 hold 住这些中间件,公司也会投入人力和时间在这个方向上。

但是在一个初创公司,业务正处在快速发展期,暂时没有能 hold 住这些中间件的团队和人,如果贸然使用这些组件,当发生故障时,排查问题也会变得很困难,甚至会阻碍业务的发展。

实际案例讨论

同一个大型项目中子项目的互相调用。TMS调用ATS获取数据集
image

改用Stream完成
image

理由:

  • 丢失数据不敏感
  • 业务场景简单
  • 消息积压概率比较小

参考:
http://zhuanlan.zhihu.com/p/60501638
http://redis.io/commands/xclaim/
http://liziba.blog.csdn.net/article/details/120320018
http://juejin.cn/post/6962423461071290375#heading-2

欢迎关注公众号,查阅python高性能编程系列文章

本文转载于网络 如有侵权请联系删除

相关文章

  • 实战性价比,腾讯Arm云实例评测 - 视频云编码

    在线视频市场持续快速增长,越来越多的人观看流媒体在线内容,实时视频的使用量正在飙升,为了能减少存储空间和提升网络带宽的利用率,视频编码压缩技术已经普遍被行业采用。如今的客户在视频流方面要求360°的用户体验,除了友好的界面、简单的内容搜索方式,更重要的是接收低延迟无缓冲视频。为了满足如此高的流媒体标准,无论是个人内容提供商、初创企业和行业领先者,都开始意识到高弹性可扩展的云平台在高质量流媒体服务中不可替代的作用。借助云服务器,内容服务商可以在公有云中按需定制容量和算力,更轻松的应对突发的流量高峰和更灵活的控制成本。所以测试云服务器的编码能力有着确切的现实意义。视频编码压缩技术有多个标准,根据Bitmovin视频开发者调查报告,自2017年,一直以来AVC/H.264为主要的视频编解码标准,使用H.264受访者始终保持在90%以上,在2021年略有下降至83%。2020年,42%的受访者正在使用HEVC,另有47%的受访者表示他们计划在2021年使用。实际在2021年,HEVC的采用率增长到49%,尽管与2020年的采访数据有些差距,但增幅明显。由此,本文将基于腾讯云SR1云服务器(基于

  • 大数据平台设计思路

    一、什么是大数据平台一般情况下,大数据平台指的是使用了Hadoop、Spark、Storm、Flink、Blink等这些分布式、实时或者离线计算框架,并在上面运行各种计算任务的平台。建设大数据平台的最终目的是服务于业务需求,解决现有业务问题或者创造新的机会。业务部门可能并不关心是采用大数据技术,还是传统的数据库技术,是否采用大数据技术的主要依据是数据量。如果出现任务运行很久的情况,或者因为计算量太大现有技术不能满足,又或者有大量半结构化、非结构化数据需要处理的时候,可能就有大数据的诉求了。二、大数据平台架构设计大数据平台架构的设计包括整体框架设计和整体技术架构设计。1、大数据平台整体架构大数据平台整体架构可分为七大部分:目录管理、数据集成、数据资产管理、数据治理、数据开发、数据分析、数据共享及数据安全。目录管理通过盘点和梳理业务数据,编制、发布数据目录,规划和指导数据的接入、管理、治理、开发、共享等。数据集成为大数据平台提供基础支撑性服务,提供多种数据接入工具,实现结构化和非结构化的数据的汇聚接入,并支持数据的预处理,为大数据平台提供原始数据支撑。数据资产管理通过管理数据标准、元数据、

  • tp6调试(trace)

    作者:陈业贵华为云享专家51cto(专家博主明日之星TOP红人)文章目录前言一、调试二、使用步骤三、代码:三:效果:总结前言学会trace调试。在控制器中调试一、调试tp6自带的调试二、使用步骤三、代码:<?php //+---------------------------------------------------------------------- //|Trace设置开启调试模式后有效 //+---------------------------------------------------------------------- return[ //内置Html和Console两种方式支持扩展 'type'=>'console',//把调试信息放到控制台上 //读取的日志通道名 'channel'=>'', 'tabs'=>[//调试信息,面板 'base'=>'基本', 'file'

  • 关于 Spartacus 开源项目的 peerDependencies

    ngnewapp生成的Angular应用,自带11个依赖:使用Schematics安装了library之后的客户Storefront:本地新建一个空的文件夹,在里面执行命令行:npmi@spartacus/storefront里面只有一个node_modules文件夹,里面包含了很多js文件和TypeScript的.d.ts文件:这个@Spartacus/storefront的package.json里,仍旧只有一个tslib的dependencies:但是peerDependencies里,包含了不少在Spartacus项目源代码package.json里定义的dependencies:"peerDependencies":{ "@angular/common":"^12.0.5", "@angular/core":"^12.0.5", "@angular/forms":"^12.0.5", "@angular/platform-bro

  • 「HTML+CSS」--自定义加载动画【040】

    前言Hello!小伙伴! 首先非常感谢您阅读海轰的文章,倘若文中有错误的地方,欢迎您指出~ 哈哈自我介绍一下 昵称:海轰 标签:程序猿一只|C++选手|学生 简介:因C语言结识编程,随后转入计算机专业,有幸拿过国奖、省奖等,已保研。目前正在学习C++/Linux(真的真的太难了~) 学习经验:扎实基础+多做笔记+多敲代码+多思考+学好英语! 日常分享:微信公众号【海轰Pro】记录生活、学习点滴,分享一些源代码或者学习资料,欢迎关注~ 效果展示Demo代码HTML<!DOCTYPEhtml> <htmllang="en"> <head> <metacharset="UTF-8"> <metahttp-equiv="X-UA-Compatible"content="IE=edge"> <metaname="viewport"content="width=device-width,initial-scale=1.0&q

  • FPGA系统性学习笔记连载_Day2-3开发流程篇之Quartus prime 18.0

    FPGA系统性学习笔记连载_Day2-3开发流程篇之Quartusprime18.0本系列为FPGA系统性学习学员学习笔记整理分享,如有学习或者购买开发板意向,可加交流群联系群主。连载《叁芯智能fpga设计与研发-第2-3天》【工程建立、verilog代码编写、分析综合、仿真、程序下载、程序固化】之《quartusprime18.0》原创作者:紫枫术河转载请联系群主授权,否则追究责任这篇文章记录《IntelCycloneIV》系列的基本开发流程(我用的是quartusprime18.0)一、建立工程1、打开quartus18.0的新建工程向导2、点击下一步3、选择工程位置,输入工程名4、点击下一步5、点击下一步6、选择芯片(EP4CE6E22C8),他属于CycloneIVE系列,封装QFP,引脚数量1447、选择仿真工具ModelSim-Altera,和工程的HDL语言VerilogHDL8、点击Finish完成9、现在工程里没有任何文件如下10、点击file的new11、在弹出的对话框,选择verilogHDL12、刚创建的文件还没有保存,首先进行保存13、另存为and_gate2

  • 文本查询TextQuery类文件编写

    读取用户指定的任意文本文件,然后允许用户从该文件中查找单词。查询的结果是该单词出现的次数,并列出每次出现所在的行。如果某单词在同一行中多次出现,程序将只显示该行一次。行号按升序显示,即第7行应该在第9行之前输出,依此类推。textquery.h头文件定义文件#ifndefTEXTQUERY_H //防止重复定义,名称大写 #defineTEXTQUERY_H #include<map> #include<set> #include<string> #include<fstream> #include<vector> classtextquery { public: typedefstd::vector<std::string>::size_typeline_no; voidread_file(std::ifstream&is)//定义函数,读取文件 { store_file(is);//调用函数,把打开的文件流存入文件 build_map();//调用函数,生成map容器 } std::stringtex

  • java锁:第二章:可重入锁和递归锁

    可重入锁是什么?可以防止死锁,是同一把锁代码:packagecom.javaliao.backstage; classPhone{ publicsynchronizedvoidsendSMS(){ System.out.println(Thread.currentThread().getName()+"\t发短信"); sendEmail(); } publicsynchronizedvoidsendEmail(){ System.out.println(Thread.currentThread().getName()+"\t发邮件"); } } publicclassDemo{ publicstaticvoidmain(String[]args){ Phonephone=newPhone(); newThread(()->{ phone.sendSMS(); },"t1").start(); newThread(()->{ phone.sendSMS(); },"t2").start();

  • python 枚举法选择最优策略参数

    版权声明:本文为博主原创文章,遵循CC4.0BY-SA版权协议,转载请附上原文出处链接和本声明。本文链接:https://blog.csdn.net/weixin_44580977/article/details/102477238枚举20~60作为移动平均天数参数,选出金额最高的做为参数 例程代码importdatetime importmatplotlib.pyplotasplt importnumpyasnp importpandas_datareaderasweb classQuantAverBreak: def__init__(self): self.skip_days=0#持股/持币状态 self.cash_hold=100000#初始资金 self.posit_num=0#持股数目 self.market_total=0#持股市值 defrun_factor_plot(self,stock_df,N): stock_df['Ma_n']=stock_df.Close.rolling(window=N).mean()#增加N移动平均线 list_d

  • [Leetcode][广度优先/哈希表/纯思路]相关题目汇总/分析/总结

    题目汇总以下链接均为我博客内对应博文,有解题思路和代码,不定时更新补充。目前范围:Leetcode前150题BFS广度优先题目WordLadder/WordLadderII/单词接龙/单词接龙II难给定一个起始字符串和一个目标字符串,现在将起始字符串按照特定的变换规则转换为目标字符串,求最少要进行多少次转换。转换规则为每次只能改变字符串中的一个字符,且每次转换后的字符串都要在给定的字符串集合中。 给定一个起始字符串和一个目标字符串,现在将起始字符串按照特定的变换规则转换为目标字符串,求所有转换次数最少的转换过程。转换规则为每次只能改变字符串中的一个字符,且每次转换后的字符串都要在给定的字符串集合中。 纯哈希表题目哈希表很多题目都和多指针息息相关,需要和多指针一起看[双指针/多指针]相关题目汇总/分析/总结 https://blog.csdn.net/qqxx6661/article/details/78841302 RomantoInteger/罗马数字转整数将罗马数字转为整数 GroupAnagrams/字母异位词分组将所含字母相同,但排列顺序不同的字符串归并到一起。 将罗马数字转为

  • PL/SQL --> DML 触发器

    --=======================--PL/SQL-->DML触发器--=======================何谓触发器?简言之,是一段命名的PL/SQL代码块,只不过该代码块在特定的条件下被触发并且执行。对于这样的代码我们称之为触发器。触发器根据触发类型的不同又分为不同级别的触发器,下面将给出触发器的分类,定义,以及使用的示例。一、触发器的相关概念1.触发器的分类通常根据触发条件以及触发级别的不同分为DML触发器,INSTEADOF触发器,系统事件触发器。DML触发器ORACLE对DML语句进行触发,可以在DML操作前或操作后进行触发,并且可以对每个行或语句操作上进行触发。INSTEADOF触发器在ORACLE里,对于简单视图,可以直接使用DML进行操作,而复杂视图则不能直接使用DML,因此INSTEADOF触发器应运而生。INSTEADOF触发器主要是为解决复杂视图不能执行DML而创建。系统事件触发器在ORACLE数据库系统的事件中进行触发,如ORACLE系统的启动与关闭等.使用系统触发器,便于系统跟踪,监测数据库变化情况等。2.触发器的组成(一段PL

  • 数据分析:什么样性格的创始人容易成功?

    导读本文摘自《从0到1》,作者彼得·蒂尔,PayPal创始人、Facebook第一位外部投资者。闻名硅谷的“Paypal黑帮”(Thepaypalmafia)的老大,来谈一谈他当年的创业经历,创业者的教父,值得一看。PayPal的六个创始人中有四个在高中时期造过炸弹。当时,其中的五个人仅有23岁,甚至更年轻。四个人不是在美国本土出生的,其中三人来自社会主义国家或前社会主义国家:潘宇来自中国,卢克·诺塞克来自波兰,马克斯·列夫琴来自乌克兰。当时在那些国家制造炸弹可不是小孩子该做的。我们六个被视为怪胎。我和卢克的第一次交谈是关于他为什么要签约参加人体冷冻的实验,即人死后将尸体冷冻,并期待未来医学进步可以起死回生。马克斯自称无国籍,而且以此为傲,苏联解体,他们全家来到美国,其家人处于外交尴尬境地。拉斯·西蒙斯原以停在公园里的拖车为家,后来进入伊利诺伊州的理工学校。只有肯·霍威利有着美国孩子的优渥童年:他是PayPal唯一的鹰级童子军。但是肯的同学认为他加入我们是疯狂的,况且薪水只有聘用他的那家大银行的1/3。所以他也并不完全正常。所有的创始人都特立独行吗?或者我们只记住或夸大了创始人身上那些

  • 腾讯云云呼叫中心通话功能总览

    腾讯云呼叫中心提供丰富的通话功能,座席可在通话过程中使用。 功能名称 功能描述 挂断 挂断当前通话。 通话保持/通话取回 支持座席与用户通话过程中单击通话保持使用户听到通话保持放音而听不到坐席说话的声音,单击通话取回可回到正常通话。您可在管理工作台进行通话保持设置。 静音/解除静音 支持座席与用户通话过程中单击静音使用户听不到座席说话的声音,单击解除静音可回到正常通话。 转技能组 支持座席在通话中单击转技能组将该通话转接至指定技能组接听,相应的呼叫数据及用户信息可同步地随呼叫转移。 转坐席 支持坐席在通话中单击转座席将该通话转接至指定座席接听,相应的呼叫数据及用户信息可同步地随呼叫转移。 通话中收号 支持座席在通话中选择管理员配置的收号模板,播放提示音并收取用户输入的号码串(如:身份证号、订单号等)。TCCC在收取号码串后将返回至企业预先配置的指定回调地址。您可在管理工作台进行收号设置。 输入分机号 支持坐席在外呼后输入分机号。 转外线 支持座席在通话中单击转外线将该电话转接至第三方号码接听,相应的呼叫数据及用户信息可同步地随呼叫转移。 自助

  • JAXB--obj2xml&amp;xml2obj

    参考: https://www.cnblogs.com/mumuxinfei/p/8948299.html  去掉 xsi:type="echoBody" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"   测试 publicvoidtest6()throwsException{ LogisticsOrderListModelmodel=newLogisticsOrderListModel(); model.setAccessCode("111111111"); JAXBContextjaxbContext=JAXBContext.newInstance(model.getClass()); Marshallermarshaller=jaxbContext.createMarshaller(); //marshaller.setProperty(Marshaller.JAXB_FRAGMENT,Boolean.TRUE); marshaller.setProperty(Mar

  • Spring 初探(一) IoC 图集

    Spring框架总体结构 IoC直观 SpringIoC容器 Spring提供两种容器类型: -BeanFactory -ApplicationContext ApplicationContext是Spring在BeanFactory基础容器上,提供的另一个IoC容器实现,拥有许多新特性。 Bean 所有注册到容器中的业务对象,在Spring中称之为Bean 容器背后的秘密 容器启动阶段 Bean实例化阶段 Bean的一生

  • border-box和CSS3 calc()解决盒模型加边框或边距后尺寸变大的问题

      box-sizing   box-sizing的CSS属性是用来改变默认的CSS框模型   属性   初始值:content-box 适用于:接受的所有元素的宽度或高度 继承:无 媒体:visual 指定的:asspecified 动画:no 规范秩序:独特的无歧义的正式语法定义的顺序 语法   box-sizing:content-box(默认样式)|padding-box|border-box复制 content-box,border和padding不计算入width之内 padding-box,padding计算入width内 border-box,border和padding计算入width之内,其实就是怪异模式了~ 兼容性: -webkit-box-sizing:    /*chromesafariandroid*/ -moz-box-sizing:     /*Firefox*/ box-sizing:  &nb

  • 为什么5分钟macd的背离比较准确,

    5分钟macd两个面积背离,基本就是5分钟一个线段,对应是段内背离,看的是黄白线,这时候背离,说明5分钟线段,即1分钟走势类型背离,背离就会有1分钟走势类型的回撤,这个回撤,最少,在5分钟上表现为1笔,而5分钟的1笔,至少25分钟,所以,这也就是为什么,5分钟的背离比较能有较好涨幅的原因, 更细看,看到它的次级别1分钟图,三段之间,第三段和第一段面积明显背离,(注意,段和段比较,只需要看面积,段内的笔要看黄白线),更加加强了1分钟走势类型背离的结论   发散思维:如果30分钟图上,看到黄白线背离,这时候是否可以买入,30分钟图,黄白线背离,基本意味着5分钟级别走势类型背离了,这时候会有5分钟走势类型的回撤,最差构造5分钟中枢,往次级别即5分钟图上看,分析第三段和第一段,如发现明显面积背离,即出现盘背,这时候就加强了上述结论 1分钟的黄白线背离,往往意味着是一个1分钟以下级别的走势类型背离,会有1分钟以下级别的回撤,而这个最低在分钟图上只表现为1笔,也就是5分钟上涨,反应时间很短,涨幅也很有限,所以看股票买股票,一般没必要看1分钟图   总结:综上,有参与价值的图形有

  • tensorflow_目标识别object_detection_api,RuntimeError: main thread is not in main loop,fig = plt.figure(frameon=False)_tkinter.TclError: no display name and no $DISPLAY environment variable

      最近在使用目标识别api,但是报错了: File"/usr/local/lib/python2.7/dist-packages/tensorflow/python/ops/script_ops.py",line158,in__call__ret=func(*args) File"/home/lyz/code/share_pro/models/research/object_detection/utils/visualization_utils.py",line694,incdf_plotfig=plt.figure(frameon=False) File"/usr/local/lib/python2.7/dist-packages/matplotlib/pyplot.py",line535,infigure**kwargs) File"/usr/local/lib/python2.7/dist-packages/matplotlib/backends/backend_tkagg.py",line81,innew_figure_managerreturnnew_f

  • java EE 梳理

    Servlet规范 https://www.cnblogs.com/yumu77/p/13888346.html#_label0**** tomcat I'mafucKingfakecoder!

  • Failed to read HTTP message: org.springframework.http.converter.HttpMessageN

    一、报错信息 FailedtoreadHTTPmessage:org.springframework.http.converter.HttpMessageN 二、解决 https://xingyun.blog.csdn.net/article/details/107995920 https://www.cnblogs.com/soficircle/p/8681361.html https://blog.csdn.net/csdnyq/article/details/105238937  

  • Oracle判断是否包含字符串的方法

    转自:https://blog.csdn.net/jumtre/article/details/38020389 。 1、contains select*fromstudentswherecontains(address,'beijing')复制 但是,使用contains谓词有个条件,那就是列要建立索引,也就是说如果上面语句中students表的address列没有建立索引,那么就会报错。 2、instr select*fromstudentswhereinstr(address,'beijing')>0复制 3、like select*fromstudentswhereaddresslike'%beijing%'复制   量变会引起质变。

相关推荐

推荐阅读