首页
统计
留言
友链
更多
图片
关于
Search
1
软件开发 | flutter使用camera插件在安卓11以上的机器上调用availableCameras无法正常获得usb相机列表
5 阅读
2
scoop包管理器
3 阅读
3
利用深度搜索求解回溯问题的通用模板
3 阅读
4
java开发知识点
2 阅读
5
codeforces-cpp模板
2 阅读
默认分类
后端开发
相关配置
知识分享
异常处理
前端开发
建站教程
开发教程
知识回顾
软件开发
杂项
登录
Search
标签搜索
java
golang
scoop
go
vue
hexo
sublime text
cpp
windows
redis
mc
mod
fabric
mysql
word
计算机网络
channel
html
flutter
camera
Wisansiiz
累计撰写
24
篇文章
累计收到
2
条评论
首页
栏目
默认分类
后端开发
相关配置
知识分享
异常处理
前端开发
建站教程
开发教程
知识回顾
软件开发
杂项
页面
统计
留言
友链
图片
关于
搜索到
2
篇与
的结果
2024-10-13
面试复盘之10-11
面试复盘之10-11redis设置过期时间(setex,setnx)的最大用处?Redis 设置过期时间的最大用处在于它为缓存管理提供了一种自动和高效的方式来处理数据的生命周期。以下是过期时间设置的几个关键优势:自动清理:过期时间允许 Redis 自动删除旧的或不再需要的键,这意味着缓存可以自我维护,释放内存空间,避免无限增长。缓存新鲜数据:在数据频繁变化的场景中,通过设置过期时间可以确保缓存中的数据不会过时,从而保持数据的新鲜度和准确性。减少数据库负载:对于热点数据,使用 Redis 缓存可以显著减少对后端数据库的查询次数,从而降低数据库的负载。会话管理:在 Web 应用中,用户会话信息通常具有临时性,设置过期时间可以自动清除过期的会话,释放服务器资源。限流和防刷:通过设置过期时间,可以实现令牌桶或漏桶等限流算法,限制用户在一定时间内的请求次数,防止滥用服务。分布式锁:在分布式系统中,使用过期时间可以安全地实现分布式锁,防止锁持有者崩溃导致的死锁问题。避免内存滥用:过期时间可以防止缓存中的数据无限期占用内存,从而避免内存资源被滥用。提高响应速度:由于 Redis 是基于内存的存储系统,设置过期时间的数据可以快速被访问,这有助于提高应用程序的响应速度。实现定时任务:虽然 Redis 不是定时任务调度器,但可以通过设置过期时间来模拟定时任务,例如,定期清理或更新数据。优化资源分配:通过过期时间,Redis 可以更有效地分配内存资源,确保重要的数据被保留,而不重要的数据被清除。总的来说,Redis 设置过期时间的最大用处在于它为缓存数据提供了一种自动和灵活的过期机制,这有助于提高应用程序的性能,减少系统资源的消耗,并确保数据的时效性。可以设计类似jwt的模式设计邀请链接将服务器id和过期时间等内容生成一个加密的串,使用时由后端进行解析,而不是生成随机数存入redis。jwt是什么?JWT(JSON Web Token)是一种开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间以JSON对象的形式安全地传输信息。每个JWT都是经过数字签名的,因此可以被验证和信任。JWT可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名。JWT通常包含三部分,用点(.)分隔:Header(头部):通常包含两部分:令牌的类型(即JWT)和所使用的签名算法,如HMAC SHA256或RSA。Payload(负载):包含所要传递的信息。负载可以包含多个声明(claim)。声明是关于实体(通常是用户)和其他数据的声明。声明有三种类型:注册的声明、公共的声明和私有的声明。注册的声明:一组预定义的声明,它们不是强制的,但是推荐使用,以提供一组有用的、可互操作的声明。例如iss(发行者)、exp(过期时间)、sub(主题)、aud(受众)等。公共的声明:这些可以由使用JWT的各方自定义,但为了避免冲突,建议使用公共的声明名称。私有的声明:这些是为在客户端和服务器之间传递信息而创建的自定义声明。Signature(签名):用于验证消息在传输过程中未被篡改,并且,对于使用私钥签名的令牌,还可以验证发送者的身份。签名是使用头部指定的算法和密钥生成的。JWT的使用场景包括但不限于:身份验证:用户登录后,服务器可以生成一个JWT,将其发送给客户端。客户端在随后的每个请求中将JWT发送回服务器,服务器通过验证JWT来识别用户。信息交换:JWT可以安全地在各方之间传输信息,因为JWT是经过签名的,接收方可以验证发送方的身份并确保数据未被篡改。授权:通过JWT中的声明,可以轻松地检查用户是否有权限执行特定的操作。JWT的一个关键特点是它的状态lessness(无状态),这意味着一旦JWT被签发,服务器就不需要存储任何关于它的信息,直到它被验证。这使得JWT非常适合分布式系统和大规模应用。go中的 \`\` 反引号是什么,写在校验后时的反引号又是什么?在Go语言中,反引号 ` 用于表示 raw string literals,即原始字符串字面量。与传统的双引号 " " 包围的字符串不同,原始字符串字面量中的任何字符都被视为字符串的一部分,不会进行转义处理。这意味着你可以在字符串中包含双引号、反引号本身以及其他特殊字符,而不需要使用反斜杠 \ 进行转义。type UserListReq struct { g.Meta `path:"/users/{serverId}" method:"get" tags:"UserService" summary:""` ServerId uint64 `p:"serverId" v:"required"` }这里的 \`p:"serverId" v:"required"\` 是键值对。validated是如何进行校验的,为什么添加这个注解就能进行校验?在Java中,Validator 是 Java Bean Validation API(JSR 303/JSR 380)的核心组件,它用于验证Java对象的属性是否符合特定的约束条件。当你在代码中添加了 @Valid 或 @Validated 注解时,实际上是在告诉Spring框架,需要在运行时对这些对象进行验证。@Valid 注解是JSR 303规范的一部分,它用于触发对象的验证过程。当你在一个对象上使用 @Valid 注解时,框架会自动对该对象进行验证,确保它符合所有定义的约束条件。@Valid 支持嵌套验证,这意味着如果一个对象中包含了其他对象,@Valid 可以递归地验证这些对象,确保整个数据结构都是有效的。@Validated 注解是Spring框架对JSR 303规范的扩展,它提供了额外的功能,比如分组验证。分组验证允许你为不同的验证场景定义不同的验证规则集合,这样你就可以在不同的上下文中对同一个对象应用不同的验证逻辑。@Validated 通常用于方法级别的验证,并且可以与Spring的AOP功能结合使用,以实现方法执行前的自动验证。当你在方法参数上添加 @Valid 或 @Validated 注解时,Spring会使用 MethodValidationPostProcessor 在方法执行前进行增强,以确保参数通过验证。如果验证失败,会抛出 MethodArgumentNotValidException 异常,这通常由全局异常处理器捕获并处理,以返回一个友好的错误响应给客户端。此外,Validator 接口的 forExecutables() 方法用于获取一个专门用于验证方法和构造函数的 ExecutableValidator 实例。这允许你不仅验证对象的属性,还可以验证方法参数和返回值,以及构造函数的参数。总结来说,添加 @Valid 或 @Validated 注解后,Spring框架会在运行时自动触发验证机制,这是通过Java Bean Validation API提供的基础设施来实现的。这些注解使得开发者可以以声明的方式添加验证规则,而不需要编写冗长的验证逻辑代码,从而提高了代码的可读性和可维护性。为什么前端项目build后会生成 名字-8位哈希 的文件形式?将这个文件转化成8位哈希为了确保这个文件没被更改,如果文件或者说项目有更新那么这8位哈希就会发生变化。ai是这么说的:缓存优化:当浏览器加载资源时,它会根据文件名来决定是否使用缓存中的资源。如果文件内容没有变化,浏览器会使用缓存中的版本,而不是重新下载。通过在文件名中包含哈希值,可以确保当文件内容发生变化时,文件名也会变化,这样浏览器就会认为这是一个新文件,并重新下载它。这有助于减少不必要的网络请求和提高页面加载速度。并行下载:在构建过程中,可能会生成多个版本的同一资源文件,例如不同主题或语言版本的CSS或JavaScript文件。哈希值确保了即使文件内容相似,它们的文件名也是唯一的,这允许浏览器并行下载这些文件,而不是等待一个文件下载完成后再下载另一个。避免硬编码:在构建过程中,资源文件可能会被压缩、合并或其他方式处理。哈希值作为文件名的一部分,可以自动反映文件内容的变化,开发者无需手动更新引用路径。自动化部署:在自动化部署流程中,哈希值可以帮助版本控制和回滚。如果新版本的构建出现问题,可以通过哈希值快速定位并回滚到之前的稳定版本。安全性:哈希值可以作为一种安全措施,防止攻击者通过篡改文件名来替换资源文件。由于哈希值是内容的函数,任何未经授权的更改都会导致哈希值不匹配,从而可以检测到文件被篡改。为什么orm框架(mybatis)写sql时要使用 ? server_id = ?,为什么不直接使用字符串拼接呢?sql注入,学mybatis的时候就讲了,防止sql注入,如果以字符串的形式进行拼接,假设前端在传来字符的同时传来了 where=1 形成 XXX where=1 那么由于是拼接字符串所以这一整串就会被当成sql执行,因为这串sql是正确的不报错的,那么就会执行前端传来的这一串字符,在增删改查时就会出现可怕的问题。哈希是什么?哈希是一种将任意长度的输入(通常是数据,如文本、数字、图片等)通过哈希算法转换成固定长度的输出(哈希值)的单向转换过程。哈希值通常被用来表示原始数据的“指纹”或“摘要”。什么是bean?在Spring框架中,"Bean" 是一个被广泛使用的术语,它指的是由Spring容器管理的对象。在Spring中,几乎所有的对象都是Beans。Spring框架的核心是其控制反转(IoC)容器,它负责实例化、配置和组装对象,这些对象被称为"Beans"。以下是Spring中Bean的一些关键概念:由Spring容器管理:Bean的生命周期由Spring容器管理,包括它们的创建、销毁、以及它们之间的依赖关系。配置元数据:Bean的定义通常在XML配置文件、注解或Java配置类中进行声明。这些配置元数据告诉Spring如何创建和组装Bean。单例或原型:Spring Beans默认是单例模式,意味着每个由Spring容器管理的Bean有且仅有一个实例;然而,也可以配置为原型模式,即每次请求都会创建一个新的Bean实例。依赖注入(DI):Spring容器使用DI来满足Bean的依赖关系。它可以将Bean的依赖关系以setter方法、构造函数或字段注入的方式注入到Bean中。作用域:除了单例(singleton)和原型(prototype),Spring还支持其他作用域,如请求(request)、会话(session)和应用(application)等,这些作用域通常用于Web应用程序。生命周期回调:Spring提供了几种生命周期回调方法,如init-method和destroy-method,以及通过实现InitializingBean和DisposableBean接口来定义自定义的初始化和销毁逻辑。自动装配:Spring可以根据配置自动装配Bean之间的依赖关系,这减少了代码中的硬编码耦合。模板方法模式:Spring框架本身也使用了模板方法模式,定义了一组模板方法(如getBean、registerBean等),具体的子类(如XmlBeanFactory、AnnotationConfigApplicationContext等)实现了这些模板方法的细节。在Spring应用程序中,几乎所有的对象都是Beans,包括服务、数据访问对象(DAO)、控制器、工具类等。通过使用Spring框架,开发者可以更加专注于业务逻辑的实现,而不必关心对象的创建和管理。要学会(好)go开发后端就要先熟练java,spring那一套反问和思考@Component和@Bean的区别是什么?@Component 和 @Bean 是Spring框架中两个非常重要的注解,它们都用于定义Spring容器中的bean,但它们的使用场景和含义有所不同。@Component:@Component 是一个类级别的注解,用于声明一个类是Spring容器中的一个组件。被 @Component 注解的类会自动被Spring扫描并注册为bean,这意味着你可以在需要的时候通过依赖注入(DI)来使用这个类的实例。@Component 可以放在接口的实现类上,也可以放在具体的类上,但通常不推荐放在接口或者抽象类上,因为这样不会实例化。@Component 还有几个衍生注解,如 @Service、@Repository 和 @Controller,它们是 @Component 的特化,提供了额外的语义信息,但本质上都是用来声明组件的。@Bean:@Bean 是一个方法级别的注解,用于声明一个方法的返回值应该是Spring容器中的一个bean。当Spring容器创建和初始化带有 @Bean 注解方法的类时,会调用这些方法并将返回值注册为bean。@Bean 注解通常用在配置类中,这些配置类通常带有 @Configuration 注解,表示它们包含了一系列的bean定义。使用 @Bean 注解可以更灵活地定义bean,因为你可以在方法中编写复杂的逻辑来生成bean实例,比如进行一些计算或者根据条件返回不同的实例。@Bean 注解提供了更多的配置选项,比如 initMethod 和 destroyMethod,允许你指定bean的初始化和销毁方法。总结区别:@Component 用于声明类作为bean,而 @Bean 用于声明方法的返回值为bean。@Component 通常用于简单的bean定义,而 @Bean 用于更复杂的bean定义,需要在创建过程中进行额外的处理。@Component 是类级别的注解,而 @Bean 是方法级别的注解。@Component 可以自动被Spring扫描并注册,而 @Bean 需要在配置类中显式定义。在实际开发中,你可以根据需要选择合适的方式来定义你的bean。
2024年10月13日
2 阅读
0 评论
0 点赞
2024-09-18
使用go-channel实现消息队列(转载)
使用go-channel实现消息队列前言这周姐姐入职了新公司,老板想探探他的底,看了一眼他的简历,呦呵,精通kafka,这小姑娘有两下子,既然这样,那你写一个消息队列吧。因为要用go语言写,这可给姐姐愁坏了。赶紧来求助我,我这么坚贞不屈一人,在姐姐的软磨硬泡下还是答应他了,所以接下来我就手把手教姐姐怎么写一个消息队列。下面我们就来看一看我是怎么写的吧~~~。本代码已上传到我的github:有需要的小伙伴,可自行下载,顺便给个小星星吧~~~什么是消息队列姐姐真是把我愁坏了,自己写的精通kafka,竟然不知道什么是消息队列,于是,一向好脾气的我开始给姐姐讲一讲什么是消息队列。消息队列,我们一般称它为MQ(Message Queue),两个单词的结合,这两个英文单词想必大家都应该知道吧,其实最熟悉的还是Queue吧,即队列。队列是一种先进先出的数据结构,队列的使用还是比较普遍的,但是已经有队列了,怎么还需要MQ呢?我:问你呢,姐姐,知道吗?为什么还需要MQ?姐姐:快点讲,想挨打呀?我:噗。。。 算我多嘴,哼~~~欠欠的我开始了接下来的耐心讲解......举一个简单的例子,假设现在我们要做一个系统,该登陆系统需要在用户登陆成功后,发送封邮件到用户邮箱进行提醒,需求还是很简单的,我们先开看一看没有MQ,我们该怎么实现呢?画一个时序图来看一看:看这个图,邮件发送在请求登陆时进行,当密码验证成功后,就发送邮件,然后返回登陆成功。这样是可以的,但是他是有缺陷的。这让我们的登陆操作变得复杂了,每次请求登陆都需要进行邮件发送,如果这里出现错误,整个登陆请求也出现了错误,导致登陆不成功;还有一个问题,本来我们登陆请求调用接口仅仅需要100ms,因为中间要做一次发送邮件的等待,那么调用一次登陆接口的时间就要增长,这就是问题所在,一封邮件他的优先级 不是很高的,用户也不需要实时收到这封邮件,所以这时,就体现了消息队列的重要性了,我们用消息队列进行改进一下。这里我们将发送邮件请求放到Mq中,这样我们就能提高用户体验的吞吐量,这个很重要,顾客就是上帝嘛,毕竟也没有人喜欢用一个很慢很慢的app。这里只是举了MQ众多应用中的其中一个,即异步应用,MQ还在系统解藕、削峰/限流中有着重要应用,这两个我就不具体讲解了,原理都一样,好好思考一下,你们都能懂得。channel好啦,姐姐终于知道什么是消息队列了,但是现在还是没法进行消息队列开发的,因为还差一个知识点,即go语言中的channel。这个很重要,我们还需要靠这个来开发我们的消息队列呢。因篇幅有限,这里不详细介绍channel,只介绍基本使用方法。什么是channelGoroutine 和 Channel 是 Go 语言并发编程的两大基石。Goroutine 用于执行并发任务,Channel 用于 goroutine 之间的同步、通信。Go提倡使用通信的方法代替共享内存,当一个Goroutine需要和其他Goroutine资源共享时,Channel就会在他们之间架起一座桥梁,并提供确保安全同步的机制。channel本质上其实还是一个队列,遵循FIFO原则。具体规则如下:先从 Channel 读取数据的 Goroutine 会先接收到数据;先向 Channel 发送数据的 Goroutine 会得到先发送数据的权利;创建通道创建通道需要用到关键字 make ,格式如下:通道实例 := make(chan 数据类型)数据类型:通道内传输的元素类型。通道实例:通过make创建的通道句柄。无缓冲通道的使用Go语言中无缓冲的通道(unbuffered channel)是指在接收前没有能力保存任何值的通道。这种类型的通道要求发送 goroutine 和接收 goroutine 同时准备好,才能完成发送和接收操作。无缓冲通道的定义方式如下:通道实例 := make(chan 通道类型)通道类型:和无缓冲通道用法一致,影响通道发送和接收的数据类型。缓冲大小:0通道实例:被创建出的通道实例。写个例子来帮助大家理解一下吧:package main import ( "sync" "time" ) func main() { c := make(chan string) var wg sync.WaitGroup wg.Add(2) go func() { defer wg.Done() c <- `Golang梦工厂` }() go func() { defer wg.Done() time.Sleep(time.Second * 1) println(`Message: `+ <-c) }() wg.Wait() }带缓冲的通道的使用Go语言中有缓冲的通道(buffered channel)是一种在被接收前能存储一个或者多个值的通道。这种类型的通道并不强制要求 goroutine 之间必须同时完成发送和接收。通道会阻塞发送和接收动作的条件也会不同。只有在通道中没有要接收的值时,接收动作才会阻塞。只有在通道没有可用缓冲区容纳被发送的值时,发送动作才会阻塞。有缓冲通道的定义方式如下:通道实例 := make(chan 通道类型, 缓冲大小)通道类型:和无缓冲通道用法一致,影响通道发送和接收的数据类型。缓冲大小:决定通道最多可以保存的元素数量。通道实例:被创建出的通道实例。来写一个例子讲解一下:package main import ( "sync" "time" ) func main() { c := make(chan string, 2) var wg sync.WaitGroup wg.Add(2) go func() { defer wg.Done() c <- `Golang梦工厂` c <- `asong` }() go func() { defer wg.Done() time.Sleep(time.Second * 1) println(`公众号: `+ <-c) println(`作者: `+ <-c) }() wg.Wait() }好啦,通道的概念就介绍到这里了,如果需要,下一篇我出一个channel详细讲解的文章。消息队列编码实现准备篇终于开始进入主题了,姐姐都听的快要睡着了,我轰隆一嗓子,立马精神,但是呢,asong也是挨了一顿小电炮,代价惨痛呀,呜呜呜............在开始编写代码编写直接,我需要构思我们的整个代码架构,这才是正确的编码方式。我们先来定义一个接口,把我们需要实现的方法先列出来,后期对每一个代码进行实现就可以了。因此可以列出如下方法:type Broker interface { publish(topic string, msg interface{}) error subscribe(topic string) (<-chan interface{}, error) unsubscribe(topic string, sub <-chan interface{}) error close() broadcast(msg interface{}, subscribers []chan interface{}) setConditions(capacity int) }publish:进行消息的推送,有两个参数即topic、msg,分别是订阅的主题、要传递的消息subscribe:消息的订阅,传入订阅的主题,即可完成订阅,并返回对应的channel通道用来接收数据unsubscribe:取消订阅,传入订阅的主题和对应的通道close:这个的作用就是很明显了,就是用来关闭消息队列的broadCast:这个属于内部方法,作用是进行广播,对推送的消息进行广播,保证每一个订阅者都可以收到setConditions:这里是用来设置条件,条件就是消息队列的容量,这样我们就可以控制消息队列的大小了细心的你们有没有发现什么问题,这些代码我都定义的是内部方法,也就是包外不可用。为什么这么做呢,因为这里属于代理要做的事情,我们还需要在封装一层,也就是客户端能直接调用的方法,这样才符合软件架构。因此可以写出如下代码:package mq type Client struct { bro *BrokerImpl } func NewClient() *Client { return &Client{ bro: NewBroker(), } } func (c *Client)SetConditions(capacity int) { c.bro.setConditions(capacity) } func (c *Client)Publish(topic string, msg interface{}) error{ return c.bro.publish(topic,msg) } func (c *Client)Subscribe(topic string) (<-chan interface{}, error){ return c.bro.subscribe(topic) } func (c *Client)Unsubscribe(topic string, sub <-chan interface{}) error { return c.bro.unsubscribe(topic,sub) } func (c *Client)Close() { c.bro.close() } func (c *Client)GetPayLoad(sub <-chan interface{}) interface{}{ for val:= range sub{ if val != nil{ return val } } return nil }上面只是准好了代码结构,但是消息队列实现的结构我们还没有设计,现在我们就来设计一下。type BrokerImpl struct { exit chan bool capacity int topics map[string][]chan interface{} // key: topic value : queue sync.RWMutex // 同步锁 }exit:也是一个通道,这个用来做关闭消息队列用的capacity:即用来设置消息队列的容量topics:这里使用一个map结构,key即是topic,其值则是一个切片,chan类型,这里这么做的原因是我们一个topic可以有多个订阅者,所以一个订阅者对应着一个通道sync.RWMutex:读写锁,这里是为了防止并发情况下,数据的推送出现错误,所以采用加锁的方式进行保证好啦,现在我们已经准备的很充分啦,开始接下来方法填充之旅吧~~~Publish和broadcast这里两个合在一起讲的原因是braodcast是属于publish里的。这里的思路很简单,我们只需要把传入的数据进行广播即可了,下面我们来看代码实现:func (b *BrokerImpl) publish(topic string, pub interface{}) error { select { case <-b.exit: return errors.New("broker closed") default: } b.RLock() subscribers, ok := b.topics[topic] b.RUnlock() if !ok { return nil } b.broadcast(pub, subscribers) return nil } func (b *BrokerImpl) broadcast(msg interface{}, subscribers []chan interface{}) { count := len(subscribers) concurrency := 1 switch { case count > 1000: concurrency = 3 case count > 100: concurrency = 2 default: concurrency = 1 } pub := func(start int) { for j := start; j < count; j += concurrency { select { case subscribers[j] <- msg: case <-time.After(time.Millisecond * 5): case <-b.exit: return } } } for i := 0; i < concurrency; i++ { go pub(i) } }publish方法中没有什么好讲的,这里主要说一下broadcast的实现:这里主要对数据进行广播,所以数据推送出去就可以了,没必要一直等着他推送成功,所以这里我们我们采用goroutine。在推送的时候,当推送失败时,我们也不能一直等待呀,所以这里我们加了一个超时机制,超过5毫秒就停止推送,接着进行下面的推送。可能你们会有疑惑,上面怎么还有一个switch选项呀,干什么用的呢?考虑这样一个问题,当有大量的订阅者时,,比如10000个,我们一个for循环去做消息的推送,那推送一次就会耗费很多时间,并且不同的消费者之间也会产生延时,,所以采用这种方法进行分解可以降低一定的时间。subscribe 和 unsubScribe我们先来看代码:func (b *BrokerImpl) subscribe(topic string) (<-chan interface{}, error) { select { case <-b.exit: return nil, errors.New("broker closed") default: } ch := make(chan interface{}, b.capacity) b.Lock() b.topics[topic] = append(b.topics[topic], ch) b.Unlock() return ch, nil } func (b *BrokerImpl) unsubscribe(topic string, sub <-chan interface{}) error { select { case <-b.exit: return errors.New("broker closed") default: } b.RLock() subscribers, ok := b.topics[topic] b.RUnlock() if !ok { return nil } // delete subscriber var newSubs []chan interface{} for _, subscriber := range subscribers { if subscriber == sub { continue } newSubs = append(newSubs, subscriber) } b.Lock() b.topics[topic] = newSubs b.Unlock() return nil }这里其实就很简单了:subscribe:这里的实现则是为订阅的主题创建一个channel,然后将订阅者加入到对应的topic中就可以了,并且返回一个接收channel。unsubScribe:这里实现的思路就是将我们刚才添加的channel删除就可以了。closefunc (b *BrokerImpl) close() { select { case <-b.exit: return default: close(b.exit) b.Lock() b.topics = make(map[string][]chan interface{}) b.Unlock() } return }这里就是为了关闭整个消息队列,这句代码b.topics = make(map[string][]chan interface{})比较重要,这里主要是为了保证下一次使用该消息队列不发生冲突。setConditions GetPayLoad还差最后两个方法,一个是设置我们的消息队列容量,另一个是封装一个方法来获取我们订阅的消息:func (b *BrokerImpl)setConditions(capacity int) { b.capacity = capacity } func (c *Client)GetPayLoad(sub <-chan interface{}) interface{}{ for val:= range sub{ if val != nil{ return val } } return nil }测试好啦,代码这么快就被写完了,接下来我们进行测试一下吧。单元测试正式测试之前,我们还是需要先进行一下单元测试,养成好的习惯,只有先自测了,才能有底气说我的代码没问题,要不直接跑程序,会出现很多bug的。这里我们测试方法如下:我们向不同的topic发送不同的信息,当订阅者收到消息后,就行取消订阅。func TestClient(t *testing.T) { b := NewClient() b.SetConditions(100) var wg sync.WaitGroup for i := 0; i < 100; i++ { topic := fmt.Sprintf("Golang梦工厂%d", i) payload := fmt.Sprintf("asong%d", i) ch, err := b.Subscribe(topic) if err != nil { t.Fatal(err) } wg.Add(1) go func() { e := b.GetPayLoad(ch) if e != payload { t.Fatalf("%s expected %s but get %s", topic, payload, e) } if err := b.Unsubscribe(topic, ch); err != nil { t.Fatal(err) } wg.Done() }() if err := b.Publish(topic, payload); err != nil { t.Fatal(err) } } wg.Wait() }测试通过,没问题,接下来我们在写几个方法测试一下测试这里分为两种方式测试测试一:使用一个定时器,向一个主题定时推送消息.// 一个topic 测试 func OnceTopic() { m := mq.NewClient() m.SetConditions(10) ch,err :=m.Subscribe(topic) if err != nil{ fmt.Println("subscribe failed") return } go OncePub(m) OnceSub(ch,m) defer m.Close() } // 定时推送 func OncePub(c *mq.Client) { t := time.NewTicker(10 * time.Second) defer t.Stop() for { select { case <- t.C: err := c.Publish(topic,"asong真帅") if err != nil{ fmt.Println("pub message failed") } default: } } } // 接受订阅消息 func OnceSub(m <-chan interface{},c *mq.Client) { for { val := c.GetPayLoad(m) fmt.Printf("get message is %s\n",val) } }测试二:使用一个定时器,定时向多个主题发送消息://多个topic测试 func ManyTopic() { m := mq.NewClient() defer m.Close() m.SetConditions(10) top := "" for i:=0;i<10;i++{ top = fmt.Sprintf("Golang梦工厂_%02d",i) go Sub(m,top) } ManyPub(m) } func ManyPub(c *mq.Client) { t := time.NewTicker(10 * time.Second) defer t.Stop() for { select { case <- t.C: for i:= 0;i<10;i++{ //多个topic 推送不同的消息 top := fmt.Sprintf("Golang梦工厂_%02d",i) payload := fmt.Sprintf("asong真帅_%02d",i) err := c.Publish(top,payload) if err != nil{ fmt.Println("pub message failed") } } default: } } } func Sub(c *mq.Client,top string) { ch,err := c.Subscribe(top) if err != nil{ fmt.Printf("sub top:%s failed\n",top) } for { val := c.GetPayLoad(ch) if val != nil{ fmt.Printf("%s get message is %s\n",top,val) } } }总结终于帮助姐姐解决了这个问题,姐姐开心死了,给我一顿亲,啊不对,是一顿夸,夸的人家都不好意思了。这一篇你学会了吗?没学会不要紧,赶快去把源代码下载下来,好好通读一下,很好理解的~~~。其实这一篇是为了接下来的kafka学习打基础的,学好了这一篇,接下来学习的kafka就会容易很多啦~~~转载自:使用go-channel实现消息队列 - Golang梦工厂 - 博客园 (cnblogs.com)
2024年09月18日
2 阅读
0 评论
0 点赞