1 快取穿透
問題描述
快取穿透是指查詢一個一定不存在的資料,由于快取是不命中時需要從資料庫查詢,查不到資料則不寫入快取,這將導致這個不存在的資料每次請求都要到資料庫去查詢,進而給資料庫帶來壓力,
解決方案
快取空值,即對于不存在的資料,在快取中放置一個空物件(注意,設定過期時間)
2 快取擊穿
問題描述
快取擊穿是指熱點key在某個時間點過期的時候,而恰好在這個時間點對這個Key有大量的并發請求過來,從而大量的請求打到資料庫,
解決方案
加互斥鎖,在并發的多個請求中,只有第一個請求執行緒能拿到鎖并執行資料庫查詢操作,其他的執行緒拿不到鎖就阻塞等著,等到第一個執行緒將資料寫入快取后,直接走快取,
3 快取雪崩
問題描述
快取雪崩是指快取中資料大批量到過期時間,而查詢資料量巨大,引起資料庫壓力過大甚至down機,
解決方案
可以給快取的過期時間時加上一個隨機值時間,使得每個 key 的過期時間分布開來,不會集中在同一時刻失效,
4 快取服務器宕機
問題描述
并發太高,快取服務器連接被打滿,最后掛了
解決方案
- 限流:nginx、spring cloud gateway、sentinel等都支持限流
- 增加本地快取(JVM記憶體快取),減輕redis一部分壓力
5 Redis實作分布式鎖
問題描述
如果用redis做分布式鎖的話,有可能會存在這樣一個問題:key丟失,比如,master節點寫成功了還沒來得及將它復制給slave就掛了,于是slave成為新的master,于是key丟失了,后果就是沒鎖住,多個執行緒持有同一把互斥鎖,
解決方案
必須等redis把這個key復制給所有的slave并且都持久化完成后,才能回傳加鎖成功,但是這樣的話,對其加鎖的性能就會有影響,
zookeeper同樣也可以實作分布式鎖,在分布式鎖的的實作上,zookeeper的重點是CP,redis的重點是AP,因此,要求強一致性就用zookeeper,對性能要求比較高的話就用redis
5 示例代碼
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.6.7</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>demo426</artifactId> <version>0.0.1-SNAPSHOT</version> <name>demo426</name> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.17.1</version> </dependency> <dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>2.0.1</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.12.0</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>
Product.java
package com.example.demo426.domain;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* @Author ChengJianSheng
* @Date 2022/4/26
*/
@Data
public class Product implements Serializable {
private Long productId;
private String productName;
private Integer stock;
private LocalDateTime createTime;
private LocalDateTime updateTime;
private Integer isDeleted;
private Integer version;
}
ProductController.java
package com.example.demo426.controller;
import com.alibaba.fastjson.JSON;
import com.example.demo426.domain.Product;
import com.example.demo426.service.ProductService;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.time.Duration;
import java.util.Random;
import java.util.concurrent.TimeUnit;
/**
* @Author ChengJianSheng
* @Date 2022/4/26
*/
@RestController
@RequestMapping("/stock")
public class ProductController {
@Autowired
private RedissonClient redissonClient;
@Resource
private StringRedisTemplate stringRedisTemplate;
@Autowired
private ProductService productService;
private final Cache PRODUCT_LOCAL_CACHE = Caffeine.newBuilder()
.maximumSize(100)
.expireAfterWrite(Duration.ofMinutes(60))
.build();
private final String PRODUCT_CACHE_PREFIX = "cache:product:";
private final String PRODUCT_LOCK_PREFIX = "lock:product:";
private final String PRODUCT_RW_LOCK_PREFIX = "lock:rw:product:";
/**
* 更新
* 寫快取的方式有這么幾種:
* 1. 更新完資料庫后,直接洗掉快取
* 2. 更新完資料庫后,主動更新快取
* 3. 更新完資料庫后,發MQ訊息,由消費者去重繪快取
* 4. 利用canal等工具,監聽MySQL資料庫binlog,然后去重繪快取
*/
@PostMapping("/update")
public void update(@RequestBody Product productDTO) {
RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(PRODUCT_RW_LOCK_PREFIX + productDTO.getProductId());
RLock wLock = readWriteLock.writeLock();
wLock.lock();
try {
// 寫資料庫
// update product set name=xxx,...,version=version+1 where id=xx and version=xxx
Product product = productService.update(productDTO);
// 放入快取
PRODUCT_LOCAL_CACHE.put(product.getProductId(), product);
stringRedisTemplate.opsForValue().set(PRODUCT_CACHE_PREFIX + product.getProductId(), JSON.toJSONString(product), getProductTimeout(60), TimeUnit.MINUTES);
} finally {
wLock.unlock();
}
}
/**
* 查詢
*/
@GetMapping("/query")
public Product query(@RequestParam("productId") Long productId) {
// 1. 嘗試從快取讀取
Product product = getProductFromCache(productId);
if (null != product) {
return product;
}
// 2. 準備從資料庫中加載
// 互斥鎖
RLock lock = redissonClient.getLock(PRODUCT_LOCK_PREFIX + productId);
lock.lock();
try {
// 再次先查快取
product = getProductFromCache(productId);
if (null != product) {
return product;
}
// 為了避免快取與資料庫雙寫不一致
// 讀寫鎖
RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(PRODUCT_RW_LOCK_PREFIX + productId);
RLock rLock = readWriteLock.readLock();
rLock.lock();
try {
// 查資料庫
product = productService.getById(productId);
if (null == product) {
// 如果資料庫中沒有,則放置一個空物件,這樣做是為了避免”快取穿透“問題
product = new Product();
} else {
PRODUCT_LOCAL_CACHE.put(productId, product);
}
// 放入快取
stringRedisTemplate.opsForValue().set(PRODUCT_CACHE_PREFIX + productId, JSON.toJSONString(product), getProductTimeout(60), TimeUnit.MINUTES);
} finally {
rLock.unlock();
}
} finally {
lock.unlock();
}
return null;
}
/**
* 查快取
*/
private Product getProductFromCache(Long productId) {
// 1. 嘗試從本地快取讀取
Product product = PRODUCT_LOCAL_CACHE.getIfPresent(productId);
if (null != product) {
return product;
}
// 2. 嘗試從Redis中讀取
String key = PRODUCT_CACHE_PREFIX + productId;
String value = https://www.cnblogs.com/cjsblog/archive/2022/04/26/stringRedisTemplate.opsForValue().get(key);
if (StringUtils.isNotBlank(value)) {
product = JSON.parseObject(value, Product.class);
return product;
}
return null;
}
/**
* 為了避免快取集體失效,故而加了隨機時間
*/
private int getProductTimeout(int initVal) {
Random random = new Random(10);
return initVal + random.nextInt();
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/465112.html
標籤:其他
