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 流系统,满足不同应用场景的需求。

相关推荐
BinaryBardC1 小时前
Bash语言的数据类型
开发语言·后端·golang
Pandaconda1 小时前
【Golang 面试题】每日 3 题(二十一)
开发语言·笔记·后端·面试·职场和发展·golang·go
_院长大人_2 小时前
使用 Spring Boot 实现钉钉消息发送消息
spring boot·后端·钉钉
土豆凌凌七2 小时前
GO随想:GO的并发等待
开发语言·后端·golang
AI向前看2 小时前
C语言的数据结构
开发语言·后端·golang
SomeB1oody2 小时前
【Rust自学】10.8. 生命周期 Pt.4:方法定义中的生命周期标注与静态生命周期
开发语言·后端·rust
自律小仔3 小时前
Go语言的 的继承(Inheritance)核心知识
开发语言·后端·golang
爱在心里无人知3 小时前
Go语言的 的数据封装(Data Encapsulation)核心知识
开发语言·后端·golang
悟道茶一杯3 小时前
Go语言的 的注解(Annotations)核心知识
开发语言·后端·golang
m0_748248774 小时前
十七:Spring Boot依赖 (2)-- spring-boot-starter-web 依赖详解
前端·spring boot·后端