乐观锁是一种在数据库层面上避免并发冲突的机制。它通常通过在数据库记录中添加一个版本号(或时间戳)来实现。每次更新记录时,都会检查版本号是否与数据库中的版本号匹配,如果匹配,则更新数据并将版本号加一。这确保了在更新期间没有其他操作更改了记录。
代码实现:
- //实体类:注意@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
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 = ?;
-
悲观锁通常通过数据库提供的锁机制实现,如 SQL 的 SELECT FOR UPDATE 语句,这会锁定被选中的数据库行,直到事务完成。这种方法适用于高冲突环境,因为它会阻止其他任何尝试修改这些行的操作。
代码实现:
- @Service
- public class FormSubmissionService {
-
- @Autowired
- private FormSubmissionRepository repository;
-
- @Transactional
- public boolean submitForm(Long userId, String formData) {
- Optional
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
findByUserIdForUpdate(@Param("userId") Long userId);
如果你的应用程序运行在单个实例或能够使用共享内存系统(如 Hazelcast、Apache Ignite),可以使用内存中的数据结构来实现锁逻辑。例如,使用一个全局哈希表存储正在进行的操作的标识符,来防止重复。
- @Service
- public class FormSubmissionService {
-
- private final Map
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 "正在处理中,请不要重复提交。";
- }
- }
- }
在应用程序级别实现去重逻辑,例如,通过在前端禁用提交按钮,直到请求完成,或者在后端设置一个短暂的时间窗口,在这个窗口内忽略来自同一用户的重复请求。
要求客户端在请求时生成一个唯一的标识符(如 UUID),并在服务器端检查这个标识符是否已经被处理。这个标识符可以存储在内存或数据库中,以确保每个请求只被处理一次。
代码实现:
- @Service
- public class RequestService {
-
- @Autowired
- private RequestIdRepository requestIdRepository;
-
- public boolean processRequest(String requestId) {
- // 检查请求ID是否已存在
- Optional
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` 表中来防止重复处理相同的请求。这种方法适用于分布式系统中确保请求的唯一性,有效地防止了用户因为多次点击导致的重复请求问题。
傻傻分不清之 Cookie、Session、Token、JWT - 掘金
图是一种非常高效的数据结构,通过使用1个位来标记某个元素是否存在。假设我们使用40亿位(或者说500MB)的内存空间,就可以表示40亿个不同的QQ号。
初始化位图:创建一个大约500MB大小的位图,每个位对应一个可能的QQ号。由于QQ号可能不会完全连续,我们需要根据实际QQ号的范围来调整位图的大小。
标记QQ号:遍历所有QQ号,对每个QQ号,计算它在位图中的位置,并将相应的位设置为1。
去重:再次遍历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);
布隆过滤器是一种空间效率极高的概率型数据结构,用于判断一个元素是否在集合中:
初始化布隆过滤器:根据数据量和可接受的误判率初始化布隆过滤器。
添加元素:遍历QQ号,将每个QQ号添加到布隆过滤器中。
预过滤:再次遍历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);
在面对上百万粉丝关注的场景下,确保每个用户都能正确而完整地接收到通知是一个挑战。这个问题通常出现在社交网络、内容分发平台、即时通讯服务等系统中。为了应对这种挑战,通常会采用"推拉结合"(Push-Pull Hybrid)的模式来优化信息分发机制。下面是对"推拉结合"策略及其在feed流系统中应用的解释:
初始内容加载使用拉取:用户打开应用时,通过拉取机制加载feed流的初始内容。这样做可以减少服务器压力,因为内容加载是基于用户的主动请求。
实时更新使用推送:对于新内容的更新,特别是用户关注的博主有新动态时,可以采用推送机制通知用户。为了减少资源消耗,可以采取一些优化措施:
智能化推拉结合:系统可以根据用户的行为、偏好以及网络状况智能选择推送或拉取。例如,对于经常互动的粉丝使用推送,对于不活跃的用户则在其主动打开应用时通过拉取更新。
后台拉取:在某些应用场景中,可以在应用处于后台时做轻量级的数据拉取,保证用户回到应用时能看到较新的内容,同时不过度消耗用户的数据流量和电量。
推拉结合模式通过平衡系统资源消耗和用户体验的实时性,为处理大规模用户通知提供了一种可行且有效的解决方案。在实际应用中,根据具体的业务需求和用户行为,灵活调整推送和拉取的策略至关重要。