Feed流系统全解析:构建实时、个性化的用户体验

为什么会写这篇文章?

近期正在完成零工系统中职位推荐的功能,为了能让用户能够有个性化推荐、实时更新、提高用户参与度等友好的体验,最终以feed流的方式呈现给用户。

(零工系统中,通过用户收藏的职位、浏览的职位、发布的简历、投递的职位、历史完成的订单等多维度进行不同权重加权并计算,结合TimeLine(职位发布的时间倒序,即取最新的职位)设计智能推荐功能。游客模式,则通过点击次数最多的职位、收藏次数最多的职位、完工订单最多的职位进行加权并结合TimeLine进行推荐。

智能推荐+TimeLine结合的方式,实现起来比较简单,同时又能在一定程度上避免"信息茧房"问题。("信息茧房":只关注自己感兴趣的信息,从而被限制到这一小范围之中))

个性化推荐:

Feed 流能够根据用户的行为数据(如浏览历史、搜索记录、简历信息等)进行个性化的职位推荐。例如,如果用户之前浏览过软件开发相关的职位,系统可以在 Feed 流中推送更多同领域的职位,像前端开发、后端开发、软件测试等不同细分方向的岗位。

实时更新展示:

职位信息在招聘市场中是动态变化的,新的职位不断涌现,旧的职位可能很快就招满。Feed 流可以实时推送最新的职位信息,让求职者第一时间了解到市场上的职位动态。

提高用户参与度:

其类似于社交媒体的呈现方式,能够吸引用户的注意力。职位以卡片或者信息流的形式展示,用户可以方便地浏览、点赞、评论或者分享职位信息。

1. 什么是Feed流?

Feed流(信息流)是一种常见的内容展示方式,用户可以在一个连续的列表中浏览由系统推送的内容。这些内容可以是新闻、动态、图片、视频等。典型的应用场景包括社交媒体平台(如微博、微信朋友圈)、新闻客户端(如今日头条)、短视频平台(如抖音)等。

简单来说,其实就是能够实时/智能推送信息的数据流。

Feed流本质上来说是一个数据流,是将"N个发布者的信息单元"通过"关注关系"传递给"M个接收者"。

2. Feed流的核心特点

  • 个性化推荐:根据用户的兴趣、行为和社交关系,推送符合用户偏好的内容。
  • 实时性:内容更新频繁,用户可以随时获取最新的信息。
  • 无限滚动:用户可以通过上下滑动不断加载更多内容,体验流畅。

3. Feed流的几种常见形式

3.1 时间线(Timeline)

时间线是最简单的 Feed 流形式,按照时间顺序展示内容。例如,微博的时间线会按照发布时间先后顺序展示用户的动态。

伪代码:

java 复制代码
// 时间线排序示例
public class TimelineFeed {
    private List<Post> posts = new ArrayList<>();

    public void addPost(Post post) {
        posts.add(post);
        Collections.sort(posts, Comparator.comparing(Post::getCreatedAt).reversed());
    }

    public List<Post> getPosts() {
        return posts;
    }
}

class Post {
    private String content;
    private LocalDateTime createdAt;

    // 构造函数、getter 和 setter 省略
}

3.2 关注流(Follow Stream)

关注流只展示用户关注的对象发布的内容。例如,微博的"关注"页面只会显示用户关注的博主发布的动态。

伪代码:

java 复制代码
// 关注流示例
public class FollowStream {
    private Map<User, Set<User>> followRelations = new HashMap<>();
    private Map<User, List<Post>> userPosts = new HashMap<>();

    public void follow(User follower, User followed) {
        followRelations.computeIfAbsent(follower, k -> new HashSet<>()).add(followed);
    }

    public List<Post> getFollowStream(User user) {
        Set<User> followedUsers = followRelations.getOrDefault(user, Collections.emptySet());
        List<Post> stream = new ArrayList<>();
        for (User followed : followedUsers) {
            stream.addAll(userPosts.getOrDefault(followed, Collections.emptyList()));
        }
        Collections.sort(stream, Comparator.comparing(Post::getCreatedAt).reversed());
        return stream;
    }
}

class User {
    private String username;

    // 构造函数、getter 和 setter 省略
}

3.3 推荐流(Recommendation Stream)

推荐流基于算法推荐内容,通常结合用户的兴趣、历史行为和社交关系进行个性化推荐。例如,抖音的"推荐"页面会根据用户的观看历史推荐相似的视频。

其中,需要注意的是,推荐流需要依赖推荐系统,推荐质量的好坏与推荐算法有直接关系。

推荐系统的相关文献把它们分为三类:

  • 协同过滤系统(仅使用用户与商品的交互信息生成推荐)
  • 基于内容系统(利用用户偏好/或商品偏好)
  • 混合推荐模型系统(使用交互信息、用户和商品的元数据)

零工推荐使用的是混合推荐模型系统,通过用户对职位的投递与收藏对其进行深度分析,从而进行推荐。

伪代码:

java 复制代码
// 推荐流示例
public class RecommendationStream {
    private Map<User, List<Post>> recommendedPosts = new HashMap<>();

    public void recommendPosts(User user, List<Post> posts) {
        recommendedPosts.put(user, posts);
    }

    public List<Post> getRecommendedPosts(User user) {
        return recommendedPosts.getOrDefault(user, Collections.emptyList());
    }
}

4. 设计Feed流系统的注意事项

4.1 数据量与性能

信息流拉取性能直接应用用户的使用体验。随着用户数量和内容数量的增加,Feed 流的数据量会迅速增长。聚合这么多的数据就需要查询多次缓存、数据库、计数器,而在每秒几十万次的请求下,如何保证在100ms之内完成这些查询操作,在技术上是比较大的挑战。因此,必须考虑如何高效地存储和查询数据,避免性能瓶颈。常用的优化手段包括分库分表、缓存、索引等。

4.2 实时性与延迟

Feed 流要求内容能够快速推送给用户,尤其是在热点事件或突发新闻时。为了保证实时性,可以采用消息队列、异步处理等方式来加速数据流转。

4.3 高并发

以微博为例,信息流是微博的主体模块,是用户进入到微博之后最先看到的模块,因此它的并发请求量是最高的,可以达到每秒几十万次请求。

4.4 用户隐私与安全

在设计 Feed 流时,必须确保用户数据的安全性和隐私保护。敏感信息应加密存储,访问权限严格控制,防止数据泄露。

4.5 可扩展性

随着业务的发展,Feed 流系统需要具备良好的可扩展性,支持水平扩展和垂直扩展。微服务架构、分布式数据库等技术可以帮助实现这一目标。

5. Feed流架构设计

5.1 拉取模式(Pull Model)

拉取模式是指客户端主动向服务器请求最新内容。这种方式简单易实现,但存在频繁请求导致的带宽浪费和服务器负载过高的问题。

拉取模式存储成本虽然比较低,但是查询和聚合这两个操作的成本会比较高。尤其是对于单个用户关注了很多人的情况来说,需要定时获取他关注的所有人的动态然后再做聚合,成本很高。

适用于产品前期,数据量比较小的情况。另外,拉取模式下的数据流的实时性要比推模式差。

伪代码:

java 复制代码
// 拉取模式示例
public class PullFeedService {
    private List<Post> posts = new ArrayList<>();

    public List<Post> fetchNewPosts(User user) {
        // 模拟从数据库中获取新内容
        return posts.stream()
                .filter(post -> post.getCreatedAt().isAfter(user.getLastFetchTime()))
                .collect(Collectors.toList());
    }
}

5.2 推送模式(Push Model)

推送模式是指服务器主动将新内容推送给客户端。这种方式可以减少客户端的频繁请求,降低带宽消耗,提高用户体验。常用的技术包括 WebSocket、长轮询、Server-Sent Events (SSE) 等。

推模式中对同步库的要求只有一个:写入能力强。

但是推送模式下,存储成本是比较高的。比如一个微博大V每发一条动态,就需要将动态插入到每个粉丝对应的feed表中,存储成本可想而知。

伪代码:

java 复制代码
// 推送模式示例(使用WebSocket)
@WebSocketEndpoint("/feed")
public class PushFeedService {
    @OnOpen
    public void onOpen(Session session) {
        // 注册新的连接
        System.out.println("New client connected");
    }

    @OnMessage
    public void onMessage(String message, Session session) {
        // 处理客户端发送的消息
        System.out.println("Received message: " + message);
    }

    @OnClose
    public void onClose(Session session) {
        // 移除断开的连接
        System.out.println("Client disconnected");
    }

    public void pushNewPost(Post post) {
        // 将新内容推送给所有连接的客户端
        for (Session session : sessions) {
            try {
                session.getBasicRemote().sendText(post.toJson());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

5.3 混合模式(Hybrid Model)

混合模式结合了拉取和推送的优点,既保证了实时性,又减少了不必要的请求。例如,客户端可以定期拉取一次内容,同时通过 WebSocket 接收实时推送。

以微博为例,混合模式的核心其实是针对微博大V和不活跃用户的特殊处理。首先,我们需要判断出哪些用户属于该大V的活跃用户与不活跃用户;针对活跃用于采用推送模式,对于不活跃用户采用自己拉取的模式。

大部分用户的消息都是写扩散,只有大V是读扩散,这样既控制了资源浪费,又减少了系统设计复杂度。但是整体设计复杂度还是要比推模式复杂。

伪代码:

java 复制代码
// 混合模式示例
public class HybridFeedService {
    private final PullFeedService pullService = new PullFeedService();
    private final PushFeedService pushService = new PushFeedService();

    public void initialize(User user) {
        // 初始化时拉取一次内容
        List<Post> initialPosts = pullService.fetchNewPosts(user);
        // 同时启动推送服务
        pushService.onOpen(user.getSession());
    }

    public void handleNewPost(Post post) {
        // 新内容到达时,先推送给客户端
        pushService.pushNewPost(post);
        // 定期拉取以确保完整性
        pullService.fetchNewPosts(user);
    }
}

6. Feed流的存储

为了构建一个高效、可扩展且可靠的 Feed 流系统,选择合适的存储方案至关重要。不同的数据类型和访问模式需要不同的存储技术来优化性能和成本。通过查阅资料总结出以下多种存储需求的可行方案:

6.1 用户与元数据存储(关系型数据库)

选择:MySQL 或 PostgreSQL

用户信息、帖子元数据等结构化数据最适合存储在关系型数据库中。这些数据库提供了强大的事务支持和复杂的查询能力,确保数据的一致性和完整性。

示例:用户和帖子表设计

sql 复制代码
-- 创建用户表
CREATE TABLE users (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) NOT NULL UNIQUE,
    password_hash VARCHAR(64) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 创建帖子表
CREATE TABLE posts (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT NOT NULL,
    content TEXT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id)
);

-- 创建关注关系表
CREATE TABLE follows (
    follower_id BIGINT NOT NULL,
    followed_id BIGINT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (follower_id, followed_id),
    FOREIGN KEY (follower_id) REFERENCES users(id),
    FOREIGN KEY (followed_id) REFERENCES users(id)
);

6.2 动态内容存储(NoSQL 数据库)

选择:MongoDB

动态内容如评论、点赞、分享等半结构化数据适合存储在 NoSQL 数据库中。MongoDB 支持灵活的文档模型,能够高效处理高并发写入,并且易于水平扩展。

示例:帖子评论存储

javascript 复制代码
// 使用MongoDB存储帖子评论
const mongoose = require('mongoose');
const CommentSchema = new mongoose.Schema({
  postId: { type: mongoose.Schema.Types.ObjectId, ref: 'Post', required: true },
  userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
  content: { type: String, required: true },
  createdAt: { type: Date, default: Date.now }
});

const Comment = mongoose.model('Comment', CommentSchema);

6.3 大文件存储(分布式文件系统)

选择:HDFS 或 Ceph

对于大文件(如图片、视频),应使用分布式文件系统进行存储。这些系统提供了高可用性和容错能力,确保数据的安全性和可靠性。同时,它们可以轻松应对大规模数据存储的需求。

示例:使用 HDFS 存储图片

python 复制代码
# 使用HDFS存储图片
from hdfs import InsecureClient

client = InsecureClient('http://namenode:50070', user='hadoop')
with open('local_image.jpg', 'rb') as f:
    client.upload('/user/hadoop/remote_image.jpg', f)

6.4 热点数据缓存(Redis)

选择:Redis

为了提高频繁访问数据的查询效率,可以使用 Redis 进行缓存。Redis 是一个高性能的内存数据库,支持多种数据结构(如字符串、哈希、列表等),并且具有持久化功能,确保数据不会因服务器重启而丢失。

示例:缓存热门帖子

java 复制代码
// 使用Redis缓存热门帖子
public class RedisCacheService {
    private Jedis jedis = new Jedis("localhost");

    public void cachePopularPosts(List<Post> posts) {
        for (Post post : posts) {
            jedis.set("post:" + post.getId(), post.toJson());
        }
    }

    public Post getCachedPost(long postId) {
        String json = jedis.get("post:" + postId);
        if (json != null) {
            return Post.fromJson(json);
        }
        return null;
    }
}

6.5 实时数据处理与分析(消息队列 + 流处理框架)

选择:Kafka + Flink

为了实现高效的实时数据处理和分析,可以结合使用消息队列(如 Kafka)和流处理框架(如 Apache Flink)。Kafka 负责数据的可靠传输,Flink 则用于实时处理和分析流数据,生成个性化推荐结果或监控系统状态。

示例:使用 Kafka 和 Flink 处理实时数据

java 复制代码
// 使用Kafka生产者发送新帖子事件
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");

KafkaProducer<String, String> producer = new KafkaProducer<>(props);
producer.send(new ProducerRecord<>("new_posts", "post_id", "post_content"));
producer.close();

// 使用Flink消费Kafka中的数据并进行实时处理
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
DataStream<String> stream = env.addSource(new FlinkKafkaConsumer<>("new_posts", new SimpleStringSchema(), props));

stream.map(post -> {
    // 处理逻辑
    return post;
}).print();

env.execute("Real-time Post Processing");

7. 总结

Feed 流系统是现代互联网应用中不可或缺的一部分,它为用户提供个性化的信息展示和实时的内容更新。本文详细介绍了 Feed 流的基础概念、常见形式以及设计架构中的关键点。通过合理的推送模式选择和高效的存储方案,可以构建出高性能、可扩展的 Feed 流系统,满足不同应用场景的需求。

相关推荐
Victor35620 小时前
Netty(20)如何实现基于Netty的WebSocket服务器?
后端
缘不易20 小时前
Springboot 整合JustAuth实现gitee授权登录
spring boot·后端·gitee
Kiri霧21 小时前
Range循环和切片
前端·后端·学习·golang
WizLC21 小时前
【Java】各种IO流知识详解
java·开发语言·后端·spring·intellij idea
Victor35621 小时前
Netty(19)Netty的性能优化手段有哪些?
后端
爬山算法21 小时前
Netty(15)Netty的线程模型是什么?它有哪些线程池类型?
java·后端
Solar202521 小时前
TOB企业智能获客新范式:基于数据驱动与AI的销售线索挖掘与孵化架构实践
人工智能·架构
白宇横流学长21 小时前
基于SpringBoot实现的冬奥会科普平台设计与实现【源码+文档】
java·spring boot·后端
Python编程学习圈1 天前
Asciinema - 终端日志记录神器,开发者的福音
后端
bing.shao1 天前
Golang 高并发秒杀系统踩坑
开发语言·后端·golang