消息推送-小众点评黑马点评项目Go语言实现--Hertz+Redis练手项目


在原版的黑马点评中没有消息通知的功能,在这里为了学习,采用了SSE功能实现一个点赞博客通知事件。

服务端实时推送技术之SSE(Server-Send Events)

服务端推送是一种允许应用服务器主动将信息发送到客户端的能力,为客户端提供了实时的信息更新和通知。
服务端推送的背景与需求主要基于以下几个诉求:

  1. 实时通知:在如点赞,评论、回复等情况下需要试试通知用户。
  2. 节省资源:如果没有服务端推送,客户端需要通过轮询的方式来获取新信息,会造成客户端、服务端的资源损耗。
  3. 增强用户体验/营销活动:针对特定用户或用户群发送有针对性的内容,如优惠活动、个性化推荐等。

常见的实时消息处理方案:

  • 轮询:在没有服务端推送时,要想试获得实时数据智能依赖客户端发起轮询。对实时性要求越高轮询越频繁,服务端和客户端的开销和压力就越大。并且存在数据长时间没有更新的情况会浪费很多轮询。
  • Websocket:基于TCP的全双工协议,能够实现客户端和服务端双向通信,非常适合实时性极强的通信场景。
  • SSE:基于HTTP协议的推送技术,是 HTML5 的一部分,通过设置content-type为text/stream来告诉客户端,内容不是一次性返回的,而是返回流。允许服务端主动向客户端发送消息,但是不允许客户端通过sse向服务端实时发送数据,即只允许单向数据交互。与websocket相比,更简单、更轻量。
  • 第三方推送平台:各家操作系统厂商一般都会提供推送渠道。同时,也有一些跨平台的推送服务,如个推、极光推送、友盟推送等,帮助开发者在不同平台上实现统一的推送功能。

案例实现

redis中有三种方式可以实现消息队列,分别是list,pub/sub,stream,他们的区别如下

在本案例中结合redis的stream消息队列来做sse推送。如果对于消息队列有较高的要求,请考虑其他专业的消息队列。

在这里插入图片描述
我们先修改点赞博客时的逻辑,当有人点赞时往redis的消息队列中添加一条消息。

	// 推送消息
	streamKey := constants.MESSAGE_STREAM_KEY + strconv.FormatInt(interBlog.UserId, 10)
	msg := &message.Message{
		From:    u,
		To:      interBlog.UserId,
		Content: "点赞了你的博客",
		Type:    "like",
		Time:    time.Now().Format("2006-01-02 15:04:05"),
	}
	err = redis.ProduceMq(h.Context, streamKey, msg)
	if err != nil {
		return nil, err
	}
	return &blog.LikeResp{IsLiked: true}, nil

这样博主的消息队列中就存在一条待消费的消息,然后编写sse逻辑。当客户端连接到sse后,通过一个无限循环监听消息队列,如果有新消息则返回。同时通过go的上下文监听退出状态,及时释放资源。

s := sse.NewStream(c)
	//c.Status(consts.StatusOK)
	subCtx, cancel := context.WithCancel(ctx)
	defer cancel()
	for {
		select {
		case <-subCtx.Done():
			hlog.Debugf("SSE stream closed")
			return
		default:
			req := ">"
			serv := service.NewSseService(subCtx, c)
			resp, err := serv.Run(req)
			if err != nil {
				hlog.Errorf("Error running SSE service: %v", err)
				continue
			}
			if resp == nil {
				continue
			}
			event := &sse.Event{
				Event: "message",
				Data:  []byte(*resp),
			}
			hlog.Debugf("SSE event: %v", event)
			err = s.Publish(event)
			if err := PublishWithRetry(s, event); err != nil {
				hlog.Errorf("Error publishing SSE event: %v", err)
			}
		}
	}

通过redis查询消息队列时,设置阻塞式查询,可以减少长时间得到空信息的问题。需要注意的是,redis的stream使用消费者组读取方式,读取前需要创建消费者组,读取后需要确认消息。


func (h *SseService) Run(req string) (resp *string, err error) {
	defer func() {
		hlog.CtxInfof(h.Context, "req = %+v", req)
		hlog.CtxInfof(h.Context, "resp = %+v", resp)
	}()
	xSet, err := h.consumeMq(">")
	// 有更新的话,发送给前端
	if err != nil {
		if errors.Is(err, redis2.Nil) {
			hlog.CtxDebugf(h.Context, "No messages found in Redis")
			return nil, nil
		}
		hlog.CtxErrorf(h.Context, "redis.ConsumeMq err = %+v", err)
		return nil, err
	}
	msgResp, err := h.handleMessage(xSet)
	if err != nil {
		hlog.CtxErrorf(h.Context, "handleMessage failed with data: %v, error: %v", xSet, err)
		return nil, err
	}
	bytes, err := utils.SerializeStruct(msgResp)
	if err != nil {
		return nil, err
	}
	return &bytes, nil
}

使用的工具类

redis


// ProduceMq 写stream消息队列
func ProduceMq(ctx context.Context, key string, message interface{}) error {
	messageJSON, err := utils.SerializeStruct(message)
	if err != nil {
		return err
	}
	// xadd写stream
	err = RedisClient.XAdd(ctx, &redis.XAddArgs{
		Stream: key,
		ID:     "*",
		Values: []interface{}{"message", messageJSON},
	}).Err()
	// 错误处理
	if err != nil {
		return err
	}
	return nil
}

// ConsumeMq 读取stream消息队列
func ConsumeMq(ctx context.Context, key string, consumer string, block time.Duration, count int64, id string) ([]redis.XStream, error) {
	if id == "" {
		id = ">"
	}
	xSet, err := RedisClient.XReadGroup(ctx, &redis.XReadGroupArgs{
		Group:    constants.STREAM_READ_GROUP,
		Consumer: consumer,
		Streams:  []string{key, id},
		Count:    count,
		Block:    block,
		NoAck:    false,
	}).Result()
	if err != nil {
		return nil, err
	}
	return xSet, nil
}
// 创建消费者组
func CreateConsumerGroup(ctx context.Context, key string) {
	RedisClient.XGroupCreateMkStream(ctx, key, constants.STREAM_READ_GROUP, "0")
}

//确认消息
func AckMq(ctx context.Context, key string, id string) error {
	return RedisClient.XAck(ctx, key, constants.STREAM_READ_GROUP, id).Err()
}

  • 13
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值