建设网站建站公司,想学网页设计报考什么专业,推广文章的步骤,网站优化建设扬州1.不用redis?分布式锁#xff0c;如何防止用户重复点击#xff1f;
1.乐观锁
乐观锁是一种在数据库层面上避免并发冲突的机制。它通常通过在数据库记录中添加一个版本号#xff08;或时间戳#xff09;来实现。每次更新记录时#xff0c;都会检查版本号是否与数据库中的…1.不用redis?分布式锁如何防止用户重复点击
1.乐观锁
乐观锁是一种在数据库层面上避免并发冲突的机制。它通常通过在数据库记录中添加一个版本号或时间戳来实现。每次更新记录时都会检查版本号是否与数据库中的版本号匹配如果匹配则更新数据并将版本号加一。这确保了在更新期间没有其他操作更改了记录。
应用场景适用于更新操作并不频繁且冲突概率较低的场景
代码实现
//实体类注意Version注解
Entity
Table(name form_submissions)
public class FormSubmission {IdGeneratedValue(strategy GenerationType.IDENTITY)private Long id;Column(name user_id)private Long userId;Column(name form_data)private String formData;Column(name version)Versionprivate int version;// 构造函数、Getter和Setter略去
}//业务层
Service
public class FormSubmissionService {Autowiredprivate FormSubmissionRepository repository;Transactionalpublic String submitForm(Long userId, String formData) {OptionalFormSubmission 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 {Autowiredprivate FormSubmissionRepository repository;Transactionalpublic boolean submitForm(Long userId, String formData) {OptionalFormSubmission 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)
OptionalFormSubmission findByUserIdForUpdate(Param(userId) Long userId);
3.基于内存的锁
如果你的应用程序运行在单个实例或能够使用共享内存系统如 Hazelcast、Apache Ignite可以使用内存中的数据结构来实现锁逻辑。例如使用一个全局哈希表存储正在进行的操作的标识符来防止重复。
应用场景适用于单实例应用或者有共享内存系统的分布式应用。
Service
public class FormSubmissionService {private final MapLong, 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 {Autowiredprivate RequestIdRepository requestIdRepository;public boolean processRequest(String requestId) {// 检查请求ID是否已存在OptionalRequestId 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号。 初始化位图创建一个大约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);2.布隆过滤器Bloom Filter
布隆过滤器是一种空间效率极高的概率型数据结构用于判断一个元素是否在集合中 初始化布隆过滤器根据数据量和可接受的误判率初始化布隆过滤器。 添加元素遍历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);