SpringBoot整合redis緩存使用

        一開始寫增刪改查基本都是直接對庫操作,接口調用量少的時候,性能問題幾乎不存在,但是數據量級上升之後,這些增刪改查接口的壓力也會大大上升,甚至會出現慢查詢的情況,出現較大的延遲。這時候機智的小夥伴會使用索引,沒錯,索引可以解決一部分查詢造成的性能問題。那麼如何才能進一步提升查詢的性能呢?對於讀多寫少的表可以使用緩存,那麼將大大減少讀取數據庫的次數。

 

 

一.Redis(緩存)常見的問題

1.緩存穿透

        緩存穿透指查詢一個一定不存在的數據,由於緩存不命中就會從數據庫查詢,查不到數據則不寫入緩存,這將導致這個不存在的數據都要到數據庫查詢,造成緩存穿透。這種情況其實就是因爲數據庫不存在的數據無法寫入緩存,解決這個問題的方法,就是把這種數據庫查不到值的情況也考慮進去。

解決方案:

  • 緩存空值,將數據庫查詢不到的則作爲空值存入,那麼下次可以從緩存中獲取key值是空值可以判斷出數據庫不存在這個值。
  • 使用布隆過濾器,布隆過濾器能判斷一個 key 一定不存在(不保證一定存在,因爲布隆過濾器結構原因,不能刪除,但是舊值可能被新值替換,而將舊值刪除後它可能依舊判斷其可能存在),在緩存的基礎上,構建布隆過濾器數據結構,在布隆過濾器中存儲對應的 key,如果存在,則說明 key 對應的值爲空。

2.緩存擊穿

       緩存擊穿針對某些高頻訪問的key,當這個key失效的瞬間,大量的請求擊穿了緩存,直接請求數據庫。

解決方案:

  • 設置二級緩存
  • 設置高頻key緩存永不過期
  • 使用互斥鎖,在執行過程中,如果緩存過期,那麼先獲取分佈式鎖,在執行從數據庫中加載數據,如果找到數據就加入緩存,沒有就繼續該有的動作,在這個過程中能保證只有一個線程操作數據庫,避免了對數據庫的大量請求。

3.緩存雪崩

        緩存雪崩指緩存服務器重啓、或者大量緩存集中在某一個時間段失效,這樣失效的時候,會給後端系統帶來很大壓力,造成數據庫的故障。

解決方法:

  • 緩存高可用設計,Redis sentinel和Redis Cluster等
  • 請求限流與服務熔斷降級機制,限制服務請求次數,當服務不可用時快速熔斷降級。
  • 設置緩存過期時間一定的隨機分佈,避免集中在同一時間緩存失效,可以在設計時將時間定爲一個固定值+隨機值。
  • 定時更新緩存策略,對於實時性要求不高的數據,定時進行更新。

4.分佈式鎖

    我們知道目前常見的分佈式鎖有基於zookeeper和基於redis的,基於zookeeper是一個持久節點下的臨時順序節點的搶佔,是一個隊列機制。而基於redis則是對某個redis緩存key的搶佔。兩者優缺點如下:

  優點 缺點
zookeeper

1.有封裝好的框架,容易實現

2.有等待鎖的隊列,大大提升搶鎖效率。

添加和刪除節點性能較低
redis Set和Del指令性能較高

1.實現複雜,需要考慮超時,原子性,誤刪等情形。

2.沒有等待鎖的隊列,只能在客戶端自旋來等待,效率低下。

    可以看出,redis實現分佈式鎖需要設置超時時間,如果不設置超時時間會出現什麼問題呢?如果獲取鎖之後在解鎖過程中出現宕機,則會導致死鎖現象。因此需要設置超時時間來避免死鎖現象。在redis2.8版本之前獲取鎖及設置超時時間分爲setnx和expire兩個步驟完成,如果這兩個步驟之間出現宕機現象,依然會存在死鎖現象。因此,redis2.8版本做了修改,將setnx和expire兩個作爲一個方法實現,避免了宕機引起的死鎖現象。

 

二.緩存的使用(springboot整合redis)

1.引入依賴:

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>

2.配置

application.yml文件

spring:
   redis:
    ## Redis數據庫索引(默認爲0)
    database: 0
    ## Redis服務器地址
    host: 127.0.0.1
    ## Redis服務器連接端口
    port: 6379
    ## Redis服務器連接密碼(默認爲空)
    password: root
    jedis:
      pool:
        ## 連接池最大連接數(使用負值表示沒有限制)
        #spring.redis.pool.max-active=8
        max-active: 8
        ## 連接池最大阻塞等待時間(使用負值表示沒有限制)
        #spring.redis.pool.max-wait=-1
        max-wait: -1
        ## 連接池中的最大空閒連接
        #spring.redis.pool.max-idle=8
        max-idle: 8
        ## 連接池中的最小空閒連接
        #spring.redis.pool.min-idle=0
        min-idle: 0
    ## 連接超時時間(毫秒)
    timeout: 1200

Application類----@EnableCaching啓動緩存註解

@SpringBootApplication
@EnableTransactionManagement
@MapperScan("com.carson.cachedemo.mapper")
@EnableScheduling
@EnableCaching
public class CachedemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(CachedemoApplication.class, args);
    }

}

提供使用緩存的接口類,該類對user對象做了緩存,即user表中的每一條記錄做了緩存,緩存key值是userid,緩存value值爲user對象。其中常用的緩存註解

其中獲取記錄需要添加註解:@Cache(value="xxx",key="xxxx",unless="#result==null")

刪除則用@CacheEvict(value="xxx",key="xxxxx")

更新則用 @CachePut(value="xxx",key="xxxx")

@Cacheable 主要針對方法配置,能夠根據方法的請求參數對其結果進行緩存
@CacheEvict 清空緩存
@CachePut 保證方法被調用,又希望結果被緩存。
@Service
public class UserManageServiceImpl implements UserManageService{

    @Autowired
    UserManageMapper userManageMapper;


    /**
     * 根據用戶id獲取用戶信息
     * @param userid
     * @return
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED,readOnly = false)
    @Cacheable(value = "user",key = "#userid",unless = "#result==null")
    public User findUser(int userid) {
        return userManageMapper.findByUserid(userid);
    }

    /**
     * 刪除的時候需要把好友關係表中的關係也刪除。
     * @param userid
     * @return
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED,readOnly = false)
    @CacheEvict(value = "user",key = "#userid")
    public int deleteUser(int userid) {

        int res2 = userManageMapper.deleteFriendByUserid(userid);
        int res3= userManageMapper.deleteFriendByFriendid(userid);
        int res1= userManageMapper.deleteByUserid(userid);
        return res1;
    }

    /**
     *  查詢所有好友
     * @return
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED,readOnly = false)
    public List<User> findAllUser() {
        return userManageMapper.findAllUser();
    }

    /**
     * 保存用戶
     * @param user
     * @return
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED,readOnly = false)
    public int saveUser(User user) {
        return userManageMapper.insertUser(user);
    }

    /**
     * 更新用戶
     * @param user
     * @return
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED,readOnly = false)
    @CachePut(value = "user",key = "#userid")
    public int updateUser(User user) {
        return userManageMapper.updateUser(user);
    }

    /**
     * 獲取所有好友信息
     * @return
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED,readOnly = false)
    public List<Friends> getFriends() {
        return userManageMapper.getFriends();
    }

    /**
     * 根據userid獲取好友信息
     * @param userid
     * @return
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED,readOnly = false)
    public Friends getFriendsByUserid(int userid) {
        return userManageMapper.getFriendsByUserid(userid);
    }

    /**
     * 先對friendid進行查詢,如果用戶表中沒有friendid,以防安全,禁止添加,成對添加
     *
     * @return
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED,readOnly = false)
    public int addFriend(Relation relation) {

       Relation search=userManageMapper.getRelation(relation);
       if(search!=null){
           throw new OperationException(Result.FRIENDS_EXIST);
       }
       Relation newRelation = new Relation();
       newRelation.setUserid(relation.getFriendid());
       newRelation.setFriendid(relation.getUserid());
       int res1 = userManageMapper.insertFriend(relation);
       int res2 = userManageMapper.insertFriend(newRelation);
       return res1*res2;
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED,readOnly = false)
    public User findByFriendid(int friendid) {
        return userManageMapper.findByFriendid(friendid);
    }

    /**
     * 刪除好友關係,邏輯是成對刪除
     * @param relation
     * @return
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED,readOnly = false)
    public int deleteFriend(Relation relation) {
        Relation search=userManageMapper.getRelation(relation);
        if(search==null){
            throw new OperationException(Result.FRIENDS_NOT_EXIST);
        }
        Relation newRelation = new Relation();
        newRelation.setUserid(relation.getFriendid());
        newRelation.setFriendid(relation.getUserid());
        int res1 = userManageMapper.deleteFriend(relation);
        int res2 = userManageMapper.deleteFriend(newRelation);
        return res1*res2;

    }

    /**
     * 用於查詢是否存在該用戶
     * @param login
     * @return
     */
    @Override
    public User search(Login login) {
        return userManageMapper.searchForLogin(login);
    }


}

1.數據庫中記錄如下:

此時緩存的記錄如下:

然後調用http://localhost:8081/user/get/1016,得到如下結果。

此時查看緩存記錄:

則發現緩存出現。

2.調用http://localhost:8081/user/delete/1016 刪除數據庫記錄

此時數據庫記錄

緩存記錄如下,說明刪除操作會清空對應的緩存。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章