master
guangxin.yuan 5 months ago
parent ba523eb7fe
commit 7f426ccfdf

@ -125,6 +125,14 @@ Redis中setnx不支持设置过期时间做分布式锁时要想避免某一
1. 服务端缓存:即将热点数据缓存至服务端的内存中.(利用Redis自带的消息通知机制来保证Redis和服务端热点Key的数据一致性对于热点Key客户端建立一个监听当热点Key有更新操作的时候服务端也随之更新。)
2. 备份热点Key即将热点Key+随机数随机分配至Redis其他节点中。这样访问热点key的时候就不会全部命中到一台机器上了。
### Redis的RedLock算法
1. RedLock算法是由Redis的作者Salvatore Sanfilippo也称为Antirez提出的一种分布式锁的实现方案旨在解决在分布式系统中实现可靠锁的问题特别是解决单个Redis实例作为分布式锁时可能出现的单点故障问题。
2. 过程1获取当前时间戳客户端在尝试获取锁时首先记录当前的时间戳t0。
3. 过程2在多个Redis实例上尝试获取锁客户端会向N个通常建议是奇数个如5个独立的Redis实例发送请求尝试获取相同的锁。每个请求都会设置相同的锁名、唯一标识符如UUID和过期时间。
4. 过程3统计获取锁成功的实例数量客户端统计在多少个Redis实例上成功获取了锁。
5. 过程4判断锁是否获取成功如果在过半数即N/2+1的Redis实例上成功获取了锁并且从获取第一个锁到最后一个锁的总时间小于锁的过期时间的一半那么认为锁获取成功。否则认为锁获取失败客户端需要释放已经获取的锁如果有的话
### 如何解决 Redis 缓存雪崩问题
1. 使用 Redis 高可用架构:使用 Redis 集群来保证 Redis 服务不会挂掉
@ -834,6 +842,13 @@ Spring使用了三级缓存解决了循环依赖的问题。在populateBean()给
5. 异常被 catch 捕获导致@Transactional失效。
6. 数据库引擎不支持事务。
### @Transactional配合分布式锁的使用
1. 在分布式系统中,先获取分布式锁还是先开始事务,取决于具体的应用场景和业务需求。这两种方式各有优缺点,选择哪种方式需要根据实际需求权衡。
2. 先获取分布式锁,再开始事务:如果锁的持有者在事务中发生故障,可能会导致锁无法释放,从而引发死锁。如果锁的粒度过大,可能会降低系统的并发性能。
3. 先获取分布式锁,再开始事务适用场景:需要严格互斥访问共享资源的场景如对数据库中的某个表或某个记录进行更新操作,操作复杂且需要多个步骤的场景
4. 先开始事务,再获取分布式锁:这种方式较少见,通常用于一些特殊的场景,例如事务的执行时间非常短,或者锁的获取成本较高。需要在事务中处理锁的获取和释放逻辑,增加了实现的复杂性。如果事务在获取锁之前提交,可能会导致数据不一致。
5. 先开始事务,再获取分布式锁适用场景:事务执行时间非常短的场景:例如,简单的数据库更新操作。锁的获取成本较高的场景:例如,锁服务的响应时间较长,或者锁的粒度非常细。
### Spring中的事务传播机制
1. REQUIRED默认常用支持使用当前事务如果当前事务不存在创建一个新事务。eg:方法B用REQUIRED修饰方法A调用方法B如果方法A当前没有事务方法B就新建一个事务若还有C则B和C在各自的事务中独立执行如果方法A有事务方法B就加入到这个事务中当成一个事务。

@ -0,0 +1,81 @@
public class Solution {
// 找到旋转数组的最小值位置
private static int findMinIndex(int[] nums) {
int left = 0;
int right = nums.length - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] > nums[right]) {
left = mid + 1;
} else {
right = mid;
}
}
return left; // 最小值的位置
}
// 快速选择算法(类似快速排序的分区操作)
private static int quickSelect(int[] nums, int start, int end, int k) {
if (start == end) {
return nums[start];
}
int pivot = partition(nums, start, end);
if (pivot == k) {
return nums[pivot];
} else if (pivot > k) {
return quickSelect(nums, start, pivot - 1, k);
} else {
return quickSelect(nums, pivot + 1, end, k);
}
}
// 快速排序的分区操作
private static int partition(int[] nums, int start, int end) {
int pivot = nums[end];
int i = start - 1;
for (int j = start; j < end; j++) {
if (nums[j] >= pivot) { // 从大到小排序
i++;
swap(nums, i, j);
}
}
swap(nums, i + 1, end);
return i + 1;
}
// 交换数组中的两个元素
private static void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
// 主函数:在旋转数组中找到第 k 大的数字
public static int findKthLargest(int[] nums, int k) {
int minIndex = findMinIndex(nums); // 找到最小值的位置
int n = nums.length;
// 将数组分为两部分,分别查找第 k 大的数字
if (k <= n - minIndex) {
// 第 k 大的数字在后半部分
return quickSelect(nums, minIndex, n - 1, k - 1);
} else {
// 第 k 大的数字在前半部分
return quickSelect(nums, 0, minIndex - 1, k - (n - minIndex) - 1);
}
}
public static void main(String[] args) {
int[] nums = {4, 5, 6, 7, 0, 1, 2};
int k = 3;
int result = findKthLargest(nums, k);
System.out.println("第 " + k + " 大的数字是: " + result);
}
}

@ -0,0 +1,93 @@
package .lru;
import java.util.HashMap;
import java.util.Map;
public class LRUCache {
class DLinkedNode {
int key;
int value;
DLinkedNode prev;
DLinkedNode next;
public DLinkedNode() {
}
public DLinkedNode(int _key, int _value) {
key = _key;
value = _value;
}
}
private Map<Integer, DLinkedNode> cache = new HashMap<>();
private int size;
private int capacity;
private DLinkedNode head, tail;
public LRUCache(int capacity) {
this.size = 0;
this.capacity = capacity;
// 使用伪头部和伪尾部节点
head = new DLinkedNode();
tail = new DLinkedNode();
head.next = tail;
tail.prev = head;
}
public int get(int key) {
DLinkedNode node = cache.get(key);
if (node == null) {
return -1;
}
// 如果 key 存在,先通过哈希表定位,再移到头部
moveToHead(node);
return node.value;
}
public void put(int key, int value) {
DLinkedNode node = cache.get(key);
if (node == null) {
// 如果 key 不存在,创建一个新的节点
DLinkedNode newNode = new DLinkedNode(key, value);
// 添加进哈希表
cache.put(key, newNode);
// 添加至双向链表的头部
addToHead(newNode);
++size;
if (size > capacity) {
// 如果超出容量,删除双向链表的尾部节点
DLinkedNode tail = removeTail();
// 删除哈希表中对应的项
cache.remove(tail.key);
--size;
}
} else {
// 如果 key 存在,先通过哈希表定位,再修改 value并移到头部
node.value = value;
moveToHead(node);
}
}
private void addToHead(DLinkedNode node) {
node.prev = head;
node.next = head.next;
head.next.prev = node;
head.next = node;
}
private void removeNode(DLinkedNode node) {
node.prev.next = node.next;
node.next.prev = node.prev;
}
private void moveToHead(DLinkedNode node) {
removeNode(node);
addToHead(node);
}
private DLinkedNode removeTail() {
DLinkedNode res = tail.prev;
removeNode(res);
return res;
}
}

@ -0,0 +1,47 @@
package .k;
public class QuickSelect {
public static int findKthLargest(int[] nums, int k) {
return quickSelect(nums, 0, nums.length - 1, nums.length - k);
}
private static int quickSelect(int[] nums, int left, int right, int kSmallest) {
if (left == right) { // 如果只剩一个元素那么它就是第k小的
return nums[left];
}
int pivotIndex = partition(nums, left, right);
if (kSmallest == pivotIndex) {
return nums[kSmallest];
} else if (kSmallest < pivotIndex) {
return quickSelect(nums, left, pivotIndex - 1, kSmallest);
} else {
return quickSelect(nums, pivotIndex + 1, right, kSmallest);
}
}
private static int partition(int[] nums, int left, int right) {
int pivot = nums[right]; // 选择最右边的元素作为pivot
int i = left; // i是小于pivot的元素的最后一个位置
for (int j = left; j < right; j++) {
if (nums[j] < pivot) {
swap(nums, i++, j);
}
}
swap(nums, i, right); // 把pivot放到中间位置
return i; // 返回pivot的正确位置
}
private static void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
public static void main(String[] args) {
int[] nums = {3, 2, 1, 5, 6, 4};
int k = 2; // 找第2大的数字即第k大的数字
System.out.println("第 " + k + " 大的数字是: " + findKthLargest(nums, k)); // 输出应该是5
}
}
Loading…
Cancel
Save