项目的一些难点

1.不用redis?分布式锁,如何防止用户重复点击?

1.乐观锁

乐观锁是一种在数据库层面上避免并发冲突的机制。它通常通过在数据库记录中添加一个版本号(或时间戳)来实现。每次更新记录时,都会检查版本号是否与数据库中的版本号匹配,如果匹配,则更新数据并将版本号加一。这确保了在更新期间没有其他操作更改了记录。

  • 应用场景:适用于更新操作并不频繁,且冲突概率较低的场景

代码实现:

//实体类:注意@Version注解
@Entity
@Table(name = "form_submissions")
public class FormSubmission {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "user_id")
    private Long userId;

    @Column(name = "form_data")
    private String formData;

    @Column(name = "version")
    @Version
    private int version;

    // 构造函数、Getter和Setter略去
}

//业务层
@Service
public class FormSubmissionService {

    @Autowired
    private FormSubmissionRepository repository;

    @Transactional
    public String submitForm(Long userId, String formData) {
        Optional<FormSubmission> existingSubmission = repository.findById(userId);
        
        FormSubmission submission;
        if (existingSubmission.isPresent()) {
            // 更新现有记录
            submission = existingSubmission.get();
            submission.setFormData(formData);
        } else {
            // 创建新的记录
            submission = new FormSubmission();
            submission.setUserId(userId);
            submission.setFormData(formData);
            submission.setVersion(0); // 或根据需要设置初始版本号
        }
        
        try {
            repository.save(submission);
            return "表单提交成功。";
        } catch (org.springframework.orm.ObjectOptimisticLockingFailureException e) {
            // 捕获乐观锁异常,处理冲突
            return "提交失败,请不要重复提交。";
        }
    }
}

//没有注解的时候sql层
UPDATE form_submissions
SET form_data = '新的表单数据', version = version + 1
WHERE id = ? AND version = ?;

2.数据库悲观锁

悲观锁通常通过数据库提供的锁机制实现,如 SQL 的 SELECT FOR UPDATE 语句,这会锁定被选中的数据库行,直到事务完成。这种方法适用于高冲突环境,因为它会阻止其他任何尝试修改这些行的操作。

  • 应用场景:适用于更新操作频繁,且冲突概率高的场景。

代码实现:

@Service
public class FormSubmissionService {

    @Autowired
    private FormSubmissionRepository repository;

    @Transactional
    public boolean submitForm(Long userId, String formData) {
        Optional<FormSubmission> existingSubmission = repository.findByUserIdForUpdate(userId);
        if (existingSubmission.isPresent()) {
            // 存在记录,处理重复提交逻辑
            return false;
        } else {
            // 不存在记录,保存新的表单提交
            FormSubmission submission = new FormSubmission();
            submission.setUserId(userId);
            submission.setFormData(formData);
            repository.save(submission);
            return true;
        }
    }
}

//sql层代码
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT fs FROM FormSubmission fs WHERE fs.userId = :userId")
Optional<FormSubmission> findByUserIdForUpdate(@Param("userId") Long userId);

3.基于内存的锁

如果你的应用程序运行在单个实例或能够使用共享内存系统(如 Hazelcast、Apache Ignite),可以使用内存中的数据结构来实现锁逻辑。例如,使用一个全局哈希表存储正在进行的操作的标识符,来防止重复。

  • 应用场景:适用于单实例应用或者有共享内存系统的分布式应用。
@Service
public class FormSubmissionService {

    private final Map<Long, Lock> userLocks = new HashMap<>();

    public String submitForm(Long userId, String formData) {
        Lock lock = userLocks.computeIfAbsent(userId, k -> new ReentrantLock());
        
        if (lock.tryLock()) {
            try {
                // 模拟表单处理逻辑
                Thread.sleep(1000); // 假设处理需要一段时间
                System.out.println("表单数据处理: " + formData);
                return "表单提交成功。";
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return "表单处理中断。";
            } finally {
                lock.unlock();
            }
        } else {
            return "正在处理中,请不要重复提交。";
        }
    }
}

4.应用程序级的去重逻辑

在应用程序级别实现去重逻辑,例如,通过在前端禁用提交按钮,直到请求完成,或者在后端设置一个短暂的时间窗口,在这个窗口内忽略来自同一用户的重复请求。

  • 应用场景:适用于需要快速实现且冲突概率不高的场景。

5.唯一标识符

要求客户端在请求时生成一个唯一的标识符(如 UUID),并在服务器端检查这个标识符是否已经被处理。这个标识符可以存储在内存或数据库中,以确保每个请求只被处理一次。

  • 应用场景:适合于任何需要确保请求唯一性的场景,特别是在分布式系统中。

代码实现:

@Service
public class RequestService {

    @Autowired
    private RequestIdRepository requestIdRepository;

    public boolean processRequest(String requestId) {
        // 检查请求ID是否已存在
        Optional<RequestId> existingRequestId = requestIdRepository.findById(requestId);
        if (existingRequestId.isPresent()) {
            // 请求ID已存在,拒绝重复处理
            return false;
        } else {
            // 请求ID不存在,处理请求
            RequestId newRequestId = new RequestId();
            newRequestId.setId(requestId);
            requestIdRepository.save(newRequestId); // 保存请求ID标记为已处理
            // 在这里执行其他请求处理逻辑...
            return true;
        }
    }
}

在这个实现中,客户端需要生成一个 UUID 并在每次请求时发送这个 UUID 作为 `requestId` 参数。服务器通过检查这个 `requestId` 是否已经存在于 `request_ids` 表中来防止重复处理相同的请求。这种方法适用于分布式系统中确保请求的唯一性,有效地防止了用户因为多次点击导致的重复请求问题。

2.Cookie、Session、Token、JWT之间的区别

傻傻分不清之 Cookie、Session、Token、JWT - 掘金

3.40亿个QQ号,限制1G内存,如何去重?

1.位图(Bitmap)

图是一种非常高效的数据结构,通过使用1个位来标记某个元素是否存在。假设我们使用40亿位(或者说500MB)的内存空间,就可以表示40亿个不同的QQ号。

  1. 初始化位图:创建一个大约500MB大小的位图,每个位对应一个可能的QQ号。由于QQ号可能不会完全连续,我们需要根据实际QQ号的范围来调整位图的大小。

  2. 标记QQ号:遍历所有QQ号,对每个QQ号,计算它在位图中的位置,并将相应的位设置为1。

  3. 去重:再次遍历QQ号,通过检查位图中对应位的值,可以判断一个QQ号是否已经出现过。

这种方法的缺点是,如果QQ号的范围非常大,位图的大小可能会超出1GB的内存限制。

代码实现:

public class Bitmap {
    private byte[] bits;

    public Bitmap(int size) {
        bits = new byte[(size + 7) / 8];
    }

    public void set(int k) {
        int byteIndex = k / 8;
        int bitIndex = k % 8;
        bits[byteIndex] |= (1 << bitIndex);
    }

    public boolean get(int k) {
        int byteIndex = k / 8;
        int bitIndex = k % 8;
        return (bits[byteIndex] & (1 << bitIndex)) != 0;
    }
}

// 假设QQ号范围在一定区间内,这里简化处理
Bitmap bitmap = new Bitmap(4000000000); // 大约需要500MB内存
// 设置QQ号
bitmap.set(qqNumber);
// 检查QQ号是否已存在
boolean exists = bitmap.get(qqNumber);

2.布隆过滤器(Bloom Filter)

布隆过滤器是一种空间效率极高的概率型数据结构,用于判断一个元素是否在集合中:

  1. 初始化布隆过滤器:根据数据量和可接受的误判率初始化布隆过滤器。

  2. 添加元素:遍历QQ号,将每个QQ号添加到布隆过滤器中。

  3. 预过滤:再次遍历QQ号,首先使用布隆过滤器检查是否可能已经存在。由于布隆过滤器存在一定的误判率,对于判断存在的元素,需要进一步确认。

布隆过滤器适用于快速预过滤,减少需要进一步处理的数据量,但需要额外的机制来处理误判。

public class BloomFilter {
    private BitSet hashes;
    private int size;

    public BloomFilter(int size) {
        this.size = size;
        this.hashes = new BitSet(size);
    }

    private int hash(Object obj, int k) {
        return Math.abs(obj.hashCode() * k) % size;
    }

    public void add(Object obj) {
        for (int k = 1; k <= 3; k++) { // 使用3个不同的哈希函数
            int hash = hash(obj, k);
            hashes.set(hash);
        }
    }

    public boolean mightContain(Object obj) {
        for (int k = 1; k <= 3; k++) {
            int hash = hash(obj, k);
            if (!hashes.get(hash)) {
                return false; // 如果有一个位不是1,那么对象肯定没有添加
            }
        }
        return true; // 可能包含
    }
}

// 使用
BloomFilter filter = new BloomFilter(100000000); // 需要调整大小以适应内存限制
filter.add(qqNumber);
boolean mightExist = filter.mightContain(qqNumber);

4.假如博主有上百万个粉丝关注,那么怎么保证正确而又完整地通知到用户呢?

在面对上百万粉丝关注的场景下,确保每个用户都能正确而完整地接收到通知是一个挑战。这个问题通常出现在社交网络、内容分发平台、即时通讯服务等系统中。为了应对这种挑战,通常会采用"推拉结合"(Push-Pull Hybrid)的模式来优化信息分发机制。下面是对"推拉结合"策略及其在feed流系统中应用的解释:

推拉结合(Push-Pull Hybrid)模式

推送(Push)机制
  • 实时性:系统主动将新内容推送给用户,适用于需要高实时性的场景,如即时通讯。
  • 资源消耗:对于有大量粉丝的博主,一次性推送会消耗大量的服务器资源和网络带宽。
  • 适用场景:适合对实时性要求较高的通知,比如好友消息、直播开始通知等。
拉取(Pull)机制
  • 按需获取:用户主动请求(拉取)新内容,适用于不需要即时了解最新信息的场景。
  • 资源节约:服务器只在用户请求时才发送数据,减少了资源的浪费。
  • 适用场景:适合用户浏览feed流、查看更新列表等场景,用户可以在自己方便的时候拉取最新内容。

推拉结合策略在Feed流中的应用

  1. 初始内容加载使用拉取:用户打开应用时,通过拉取机制加载feed流的初始内容。这样做可以减少服务器压力,因为内容加载是基于用户的主动请求。

  2. 实时更新使用推送:对于新内容的更新,特别是用户关注的博主有新动态时,可以采用推送机制通知用户。为了减少资源消耗,可以采取一些优化措施:

    • 分批推送:将粉丝分成不同的批次,逐批推送更新,避免一次性对服务器造成过大压力。
    • 仅推送通知:而不是推送全部新内容,只通知用户有新内容,用户点击通知后再通过拉取获取具体内容。
    • 使用消息队列:利用消息队列管理推送任务,可以平衡负载,提高系统的扩展性和可靠性。
  3. 智能化推拉结合:系统可以根据用户的行为、偏好以及网络状况智能选择推送或拉取。例如,对于经常互动的粉丝使用推送,对于不活跃的用户则在其主动打开应用时通过拉取更新。

  4. 后台拉取:在某些应用场景中,可以在应用处于后台时做轻量级的数据拉取,保证用户回到应用时能看到较新的内容,同时不过度消耗用户的数据流量和电量。

推拉结合模式通过平衡系统资源消耗和用户体验的实时性,为处理大规模用户通知提供了一种可行且有效的解决方案。在实际应用中,根据具体的业务需求和用户行为,灵活调整推送和拉取的策略至关重要。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值