Skip to content

系统设计

电商系统中,如何实现秒杀功能?如何解决商品的超卖问题?

在电商系统中实现秒杀功能,需要考虑到如下几个方面:

  1. 确定秒杀商品的数量,保证秒杀商品的数量足够满足用户的需求,但不能过多以至于导致商品超卖的问题。
  2. 设计一个合理的倒计时机制,提醒用户秒杀活动即将开始,并且在秒杀活动开始时启动计时器,提醒用户秒杀时间已经过半或者秒杀活动即将结束。
  3. 开发一个高并发的秒杀接口,用于处理大量用户同时发起的秒杀请求。这一步可以通过缓存、队列等技术来实现。

解决商品超卖的问题,常见的方法有以下几种:

  1. 库存预留。在秒杀活动开始前,对商品的库存进行预留,以避免在秒杀过程中对商品库存进行修改,从而避免商品超卖的问题。
  2. 乐观锁。在秒杀过程中,当用户发起请求时,使用乐观锁的机制来更新商品库存,从而保证在并发情况下不会出现商品超卖的问题。
  3. 悲观锁。在秒杀过程中,当用户发起请求时,使用悲观锁的机制来更新商品库存,通过对商品的行级锁定来保证在并发情况下不会出现商品超卖的问题。悲观锁的方式通常是通过数据库的事务机制来实现的,能够很好的保证数据的一致性和完整性。

此外,还可以通过限流、防抖等技术来避免秒杀活动出现商品超卖的问题。通过限流,可以限制每个用户对秒杀接口的访问频率,从而避免大量请求同时发送到服务器,造成商品超卖的问题。通过防抖,可以让用户在短时间内只能发起一次秒杀请求,从而避免用户重复发送大量请求,造成商品超卖的问题。

简述 Zookeeper 基础原理以及使用场景

Zookeeper 是一个分布式的协调服务,提供了一个统一的接口,用于在分布式系统中维护配置信息、命名服务、组服务等。

Zookeeper 的基础原理是将数据以树形结构存储在内存中,并通过类似文件系统的方式来维护这些数据。每个节点都可以作为一个父节点,来维护它的子节点;同时,每个节点也可以作为一个子节点,来被它的父节点维护。每个节点都可以存储数据,也可以作为一个空节点,来维护其他节点。

Zookeeper 使用场景很多,常见的有以下几种:

  1. 分布式配置管理。通过 Zookeeper 维护集群中的配置信息,可以实时的同步配置信息,方便集群中各个节点之间的信息交换。
  2. 分布式命名服务。通过 Zookeeper 维护集群中的命名服务,可以方便集群中各个节点之间的通信,解决节点间通信的复杂性问题。
  3. 分布式协调服务。通过 Zookeeper 维护集群中的协调服务,可以方便集群中各个节点之间的协作,解决节点间协作的复杂性问题。例如,在分布式数据库中,可以使用 Zookeeper 协调各个节点之间的数据备份和恢复等操作。

此外,Zookeeper 还可以作为分布式锁服务,用于解决节点之间的资源竞争问题。例如,在分布式消息队列中,可以使用 Zookeeper 来协调各个节点之间的消息发送和消费。

总之,Zookeeper 是一个非常强大的分布式协调服务,可以用于解决分布式系统中各种复杂的协作问题。

简述什么是两阶段提交?

两阶段提交(Two-Phase Commit,简称 2PC)是一种分布式事务协议,用于解决分布式系统中的事务一致性问题。两阶段提交协议包括两个阶段:第一阶段是提交阶段,协调者向所有参与者发送提交指令,要求所有参与者提交事务;第二阶段是投票阶段,协调者向所有参与者发送投票指令,要求所有参与者投票表决是否提交事务。

两阶段提交协议能够保证分布式系统中的事务在所有参与者之间达成一致,并且能够在事务发生错误时进行回滚,从而保证事务的原子性和一致性。

两阶段提交协议的主要优点是保证了事务的原子性和一致性,能够在分布式系统中有效地维护事务的完整性。但是,两阶段提交协议也有一些缺点,比如性能较差,容易受单点故障的影响等。

总之,两阶段提交协议是一种非常有用的分布式事务协议,能够在分布式系统中有效地维护事务的一致性和完整性。

如何设计一个消息队列

设计一个消息队列需要考虑如下几个方面:

  1. 定义消息队列的结构体,包括队列的大小、队列中存储的消息数据、队列头和队列尾等信息。

    cpp
    struct MessageQueue {
        int size;
        int head;
        int tail;
        vector<Message> data;
    };
  2. 定义消息队列的操作函数,包括初始化、销毁、入队、出队等操作。

    cpp
    // 初始化消息队列
    void init(MessageQueue& queue, int size) {
        queue.size = size;
        queue.head = 0;
        queue.tail = 0;
        queue.data.resize(size);
    }
    
    // 销毁消息队列
    void destroy(MessageQueue& queue) {
        queue.size = 0;
        queue.head = 0;
        queue.tail = 0;
        queue.data.clear();
    }
    
    // 入队
    bool enqueue(MessageQueue& queue, const Message& message) {
        if (queue.tail == queue.head && queue.data[queue.head].valid) {
            // 队列已满
            return false;
        }
        queue.data[queue.tail] = message;
        queue.tail = (queue.tail + 1) % queue.size;
    
        return true;
    }
    
    // 出队
    bool dequeue(MessageQueue& queue, Message& message) {
        if (queue.head == queue.tail && !queue.data[queue.head].valid) {
            // 队列为空
            return false;
        }
        message = queue.data[queue.head];
        queue.data[queue.head].valid = false;
        queue.head = (queue.head + 1) % queue.size;
    
        return true;
    }
  3. 使用锁机制,保证消息队列的线程安全性。

    cpp
    std::mutex mtx;
    
    // 初始化消息队列
    void init(MessageQueue& queue, int size) {
        std::lock_guardstd::mutex lock(mtx);
        queue.size = size;
        queue.head = 0;
        queue.tail = 0;
        queue.data.resize(size);
    }
    
    // 销毁消息队列
    void destroy(MessageQueue& queue) {
        std::lock_guardstd::mutex lock(mtx);
        queue.size = 0;
        queue.head = 0;
        queue.tail = 0;
        queue.data.clear();
    }
    
    // 入队
    bool enqueue(MessageQueue& queue, const Message& message) {
        std::lock_guardstd::mutex lock(mtx);
        if (queue.tail == queue.head && queue.data[queue.head].valid) {
            // 队列已满
            return false;
        }
    
        queue.data[queue.tail] = message;
        queue.tail = (queue.tail + 1) % queue.size;
    
        return true;
    }
    
    // 出队
    bool dequeue(MessageQueue& queue, Message& message) {
        std::lock_guardstd::mutex lock(mtx);
        if (queue.head == queue.tail && !queue.data[queue.head].valid) {
            // 队列为空
            return false;
        }
    
        message = queue.data[queue.head];
        queue.data[queue.head].valid = false;
        queue.head = (queue.head + 1) % queue.size;
    
        return true;
    }

完整实现

cpp
#include <iostream>
#include <vector>
#include <mutex>

struct Message
{
    int id;
    std::string data;
    bool valid;
};

struct MessageQueue
{
    int size;
    int head;
    int tail;
    std::vector<Message> data;
};

std::mutex mtx;

// 初始化消息队列
void init(MessageQueue &queue, int size)
{
    std::lock_guardstd::mutex lock(mtx);
    queue.size = size;
    queue.head = 0;
    queue.tail = 0;
    queue.data.resize(size);
}

// 销毁消息队列
void destroy(MessageQueue &queue)
{
    std::lock_guardstd::mutex lock(mtx);
    queue.size = 0;
    queue.head = 0;
    queue.tail = 0;
    queue.data.clear();
}

// 入队
bool enqueue(MessageQueue &queue, const Message &message)
{
    std::lock_guardstd::mutex lock(mtx);
    if (queue.tail == queue.head && queue.data[queue.head].valid)
    {
        // 队列已满
        return false;
    }

    queue.data[queue.tail] = message;
    queue.data[queue.tail].valid = true;
    queue.tail = (queue.tail + 1) % queue.size;

    return true;
}

// 出队
bool dequeue(MessageQueue &queue, Message &message)
{
    std::lock_guardstd::mutex lock(mtx);
    if (queue.head == queue.tail && !queue.data[queue.head].valid)
    {
        // 队列为空
        return false;
    }

    message = queue.data[queue.head];
    queue.data[queue.head].valid = false;
    queue.head = (queue.head + 1) % queue.size;

    return true;
}

int main()
{
    // 创建消息队列
    MessageQueue queue;
    init(queue, 10);
    // 入队
    Message message1 = {1, "hello", false};
    enqueue(queue, message1);
    Message message2 = {2, "world", false};
    enqueue(queue, message2);

    // 出队
    Message message;
    dequeue(queue, message);
    std::cout << message.id << ": " << message.data << std::endl;

    // 销毁消息队列
    destroy(queue);

    return 0;
}

如何设计一个线程池

如果要用C++设计一个线程池,首先需要使用C++提供的多线程库,比如<thread>头文件。然后,可以定义一个线程池类,包含一个线程容器,用于存储创建的线程。

在线程池类中,需要定义一个方法用于添加新任务,该方法可以将新任务打包为一个函数对象,然后将其放入一个任务队列中。另外,还需要定义一个方法用于启动线程池,该方法会创建指定数量的线程,每个线程都会不断从任务队列中获取任务并执行。

线程池类还需要提供一些其他功能,比如停止线程池,等待所有线程完成任务等。以下是一个简单的C++线程池的示例实现:

cpp
#include <iostream>
#include <thread>
#include <vector>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <atomic>
#include <future>

class ThreadPool
{
public:
    ThreadPool(size_t num_threads)
        : stop_(false)
    {
        for (size_t i = 0; i < num_threads; ++i)
        {
            threads_.emplace_back([this]()
            {
                while (true)
                {
                    std::function<void()> task;
                    {
                        std::unique_lock<std::mutex> lock(mutex_);
                        cond_.wait(lock, [this]{ return stop_ || !tasks_.empty(); });
                        if (stop_ && tasks_.empty())
                            return;
                        task = std::move(tasks_.front());
                        tasks_.pop();
                    }
                    task();
                }
            });
        }
    }

    ~ThreadPool()
    {
        {
            std::unique_lock<std::mutex> lock(mutex_);
            stop_ = true;
        }
        cond_.notify_all();
        for (auto& thread : threads_)
            thread.join();
    }

    template <class T>
    auto AddTask(T task) -> std::future<decltype(task())>
    {
        auto wrapper = std::make_shared<std::packaged_task<decltype(task())()>>(std::move(task));
        {
            std::unique_lock<std::mutex> lock(mutex_);
            tasks_.emplace([=]{ (*wrapper)(); });
        }
        cond_.notify_one();
        return wrapper->get_future();
    }

    void Wait()
    {
        std::unique_lock<std::mutex> lock(mutex_);
        cond_.wait(lock, [this]{ return tasks_.empty(); });
    }

    size_t Size()
    {
        std::unique_lock<std::mutex> lock(mutex_);
        return tasks_.size();
    }

private:
    std::vector<std::thread> threads_;
    std::queue<std::function<void()>> tasks_;
    std::mutex mutex_;
    std::condition_variable cond_;
    std::atomic<bool> stop_;
};

上面的代码,可以使用以下方式来创建一个线程池,并通过调用AddTask方法添加任务:

cpp
// 创建一个线程池,包含4个线程
ThreadPool pool(4);

// 添加一个任务,该任务是一个普通的函数
auto task1 = pool.AddTask([]()
{
    std::cout << "Hello from a worker thread!" << std::endl;
    return 42;
});

// 添加一个任务,该任务是一个 Lambda 表达式
auto task2 = pool.AddTask([]()
{
    std::cout << "Hello from another worker thread!" << std::endl;
    return 123;
});

// 等待所有任务完成
pool.Wait();

// 获取任务的返回值
std::cout << "Task1 returned " << task1.get() << std::endl;
std::cout << "Task2 returned " << task2.get() << std::endl;

// 获取当前任务队列的长度
std::cout << "Task queue size: " << pool.Size() << std::endl;

// 线程池对象在析构时会自动停止所有线程

线程池类中还有很多可以改进的地方,比如可以支持线程池动态扩容、支持自定义线程启动参数等。完整实现需要考虑更多细节,这里仅提供了一个简单的实现供参考。

简述一致性哈希算法的实现方式及原理

简述 CAP 理论

项目上有什么技术难点?

了解高并发的解决方案吗?例如动静分离,缓存,负载均衡

有哪些实现服务发现的方法?

如何阅读大型项目的源码?

假如明天是活动高峰?QPS 预计会翻10倍,你要怎么做?

Kafka 发送消息是如何保证可靠性的?

Kafka 是一个分布式消息队列系统,用于存储和转发消息。它使用多种机制来保证消息的可靠性。

首先,Kafka 将消息分成一系列的分区,并将每个分区保存到一个或多个副本节点上。这种分区和副本机制可以保证消息的可用性,即使某个节点出现故障,也能通过其它节点访问消息。

其次,Kafka 使用了严格的消息发布顺序和消费顺序。每条消息都有一个唯一的编号,并且消费者在消费消息时必须按照消息的编号顺序进行消费。这样就能保证消息的顺序性和一致性。

此外,Kafka 还支持消息确认机制。消费者可以选择在消费消息后进行确认,Kafka 将在收到消费者的确认后才会删除消息,从而保证消息的持久性。

总之,Kafka 通过多种机制来保证消息的可靠性,包括副本机制、消息顺序性、消息确认等。这些机制的综合作用,为 Kafka 带来了高可用性和高可靠性。

吃好喝好 快乐地活下去