緩存的使用場景
1.1、作為數據庫的緩存,為數據庫減壓
通常情況下,數據是存儲在數據庫的,應用程序也是直接操作數據庫。在訪問量較小的時候幾乎沒有什么影響。
一旦讀寫請求量超過1w,數據庫壓力劇增,此時可以從數據庫角度做處理,比如:
做讀寫分離,一主一從或者一主多從。
如果壓力還持續增大,做分庫分表,根據業務將數據庫拆分為多個,根據需要,將數據庫表拆分為多張表,分別放在多個庫,又可以支撐一定的請求。再增大呢,我們繼續增加分庫分表嗎?
當訪問量超過10w, 100w, 1000w呢,其實這時候我們需要引入緩存,因為大多數的操作都是查詢操作。
將訪問過的數據存儲起來,當再次訪問時,先找到緩存,緩存命中就直接返回。找不到再查詢數據庫,并且回填緩存,下次訪問就能直接命中緩存了。
1.2、提高系統響應速度
數據庫的數據實際上是存儲在文件里的,比如mysql,你可以在它的data目錄下面看到,當數據需要遷移時,甚至可以直接拷貝磁盤文件,再稍作修改就可以實現數據遷移。與磁盤打交道,就需要與內存做交換,做swap操作。性能時比較差的。
當大量的并發請求,數據庫可能因為太過頻繁的IO操作導致無法正常返回結果。而將數據存儲在緩存中(redis), 也就是存儲在內存中。
而內存天然就支持高并發,可以處理瞬時大量的并發請求。
比如redis的單機qps能后達到11w/s讀請求,8w/s寫請求。可以說是甩開數據庫無數倍。當然我們的響應速度也是得到了一個質的飛躍。
1.3、session共享
我們知道,當我們后臺啟動多臺tomcat之后,上層再加了一層nginx做負載均衡的話,我們就會驚奇的發現,有時能夠正常訪問,,有時不能后正常訪問。就是因為兩個tomcat的session是不一樣的。當然,可以做session復制等操作解決,但是性能比較低下。并且難以保證各個session之間完全同步。
如何解決呢?當然是使用redis來存儲session,讓他它們使用同一個session就ok了。這樣就實現了session的共享。
登錄成功之后,將session存儲到redis,獲取session時從redis查詢。
1.4、存儲token令牌,短信驗證碼等
session共享雖然解決了問題,但是這些都是基于pc端,或者存在session的內置瀏覽器中。但是比如app等沒有session的,那就是基于token實現登錄,登錄前的驗證碼發送也都會存儲在redis中。
1.5、做分布式鎖
通常來說我們Java程序中的鎖,是多線程的鎖,是在一個JVM當眾生效的,管不了其他JVM中的線程。
而多個進程(JVM)在并發時也會產生問題,也要控制時序性,此時就需要使用分布式鎖。
1.5.1、使用Redis的setNX實現分布式鎖
當然,這種方式存在并發問題,不值得討論
1.5.2、使用redission實現分布式鎖
public class DistributedRedisLock {
? ? //從配置類中獲取redisson對象
? ? private static Redisson redisson = RedissonManager.getRedisson();
? ? private static final String LOCK_PREFIX = "RedisLock_";
? ? //加鎖
? ? public static boolean acquire(String lockName) {
? ? ? ? //聲明key對象
? ? ? ? String key = LOCK_PREFIX + lockName;
? ? ? ? //獲取鎖對象
? ? ? ? RLock mylock = redisson.getLock(key);
? ? ? ? //加鎖,并且設置鎖過期時間3秒,防止死鎖的產生 uuid+threadId
? ? ? ? mylock.lock(2, 3, TimeUtil.SECOND);
? ? ? ? //加鎖成功
? ? ? ? return true;
? ? }
? ? //鎖的釋放
? ? public static void release(String lockName) {
? ? ? ? //必須是和加鎖時的同一個key
? ? ? ? String key = LOCK_PREFIX + lockName;
? ? ? ? //獲取所對象
? ? ? ? RLock mylock = redisson.getLock(key);
? ? ? ? //釋放鎖(解鎖)
? ? ? ? mylock.unlock();
? ? }
}
1.6、做樂觀鎖
Mysql中,同步鎖和數據庫中的行鎖、表鎖都是悲觀鎖
Java中 synchronized和可重入鎖等都是悲觀鎖
悲觀鎖的性能是比較低的,響應性比較差,而高性能、高響應的鎖一般都是使用樂觀鎖
Redis可以實現樂觀鎖 watch + incr
public static void main(String[] arg) {
? ? String redisKey = "lock";
? ? ExecutorService executorService = Executors.newFixedThreadPool(20);
? ? try {
? ? ? ? Jedis jedis = new Jedis("127.0.0.1", 6378);
? ? ? ? // 初始值
? ? ? ? jedis.set(redisKey, "0");
? ? ? ? jedis.close();
? ? } catch (Exception e) {
? ? ? ? e.printStackTrace();
? ? }
? ? for (int i = 0; i < 1000; i++) {
? ? ? ? executorService.execute(() -> {
? ? ? ? ? ? Jedis jedis1 = new Jedis("127.0.0.1", 6378);
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? jedis1.watch(redisKey);
? ? ? ? ? ? ? ? String redisValue = jedis1.get(redisKey);
? ? ? ? ? ? ? ? int valInteger = Integer.valueOf(redisValue);
? ? ? ? ? ? ? ? String userInfo = UUID.randomUUID().toString();
? ? ? ? ? ? ? ? // 沒有秒完
? ? ? ? ? ? ? ? if (valInteger < 20) {
? ? ? ? ? ? ? ? ? ? Transaction tx = jedis1.multi();
? ? ? ? ? ? ? ? ? ? tx.incr(redisKey);
? ? ? ? ? ? ? ? ? ? List list = tx.exec();
? ? ? ? ? ? ? ? ? ? // 秒成功 失敗返回空list而不是空
? ? ? ? ? ? ? ? ? ? if (list != null && list.size() > 0) {
? ? ? ? ? ? ? ? ? ? ? ? System.out.println("用戶:" + userInfo + ",秒殺成功!當前成功人數:" + (valInteger + 1));
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? // 版本變化,被別人搶了。
? ? ? ? ? ? ? ? ? ? else {
? ? ? ? ? ? ? ? ? ? ? ? System.out.println("用戶:" + userInfo + ",秒殺失敗");
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? // 秒完了
? ? ? ? ? ? ? ? else {
? ? ? ? ? ? ? ? ? ? System.out.println("已經有20人秒殺成功,秒殺結束");
? ? ? ? ? ? ? ? }
? ? ? ? ? ? } catch (Exception e) {
? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? } finally {
? ? ? ? ? ? ? ? jedis1.close();
? ? ? ? ? ? }
? ? ? ? });
? ? }
? ? executorService.shutdown();
}