微服務(wù)緩存&Redis

1. 緩存管理器和配置

Spring在使用緩存注解前耳鸯,需要配置緩存管理器,緩存管理器將提供一些重要的信息膀曾,如緩存配型县爬,超時(shí)時(shí)間等。Spring可以支持多種緩存的使用添谊,因此它存在多種緩存處理器财喳,并提供了緩存處理器的接口CacheManage和相關(guān)類:

CacheManager

這是緩存相關(guān)的全部配置

spring:
  cache:
    # 緩存相關(guān)配置
    cache-names: test_cache
    # 如果有底層的緩存配置管理器支持創(chuàng)建
    caffeine:
      # caffeine 緩存
      spec: x
        # 配置細(xì)節(jié)
    couchbase:
      # couchbase 緩存
      expiration: 0ms
      # 超時(shí)時(shí)間,默認(rèn)永不超時(shí)
    ehcache:
      # ehcache 緩存
      config: x
      # 配置ehcache緩存初始化文件路徑
    infinispan:
      # infinispan 緩存
      config: x
      # 配置infinispan緩存配置文件
    jcache:
      # jcache 緩存
      config: x
      # jcache緩存配置文件
      provider: "" 
      # jcache緩存提供者配置
    redis:
      # redis緩存
      cache-null-values: true
      # 是否允許緩存空值
      key-prefix: x
      # redis的鍵的前綴
      time-to-live: 0ms
      # 緩存超時(shí)時(shí)間,配置為0則表示永不超時(shí)
      use-key-prefix: true
      # 是否啟用Redis的鍵前綴
    type: redis
    # 緩存類型斩狱,默認(rèn)情況下耳高,Spring會(huì)自動(dòng)根據(jù)上下文探測

Spring支持的緩存類型有這些

image-20200806193300054

完整的配置如下:

spring:
  # redis 服務(wù)器配置
  redis:
    host: 10.0.228.117
    port: 6379
    password: ""
    timeout: 60000
    database: 0
    # 連接池屬性配置
    lettuce:
      pool:
        # 最小空閑連接數(shù)
        min-idle: 5
        # 最大空閑連接數(shù)
        max-idle: 10
        # 最大活動(dòng)的連接數(shù)
        max-active: 10
        # 連接最大等待數(shù)
        max-wait: 3000
  cache:
    # 緩存相關(guān)配置
    cache-names: test_cache
#    # 如果有底層的緩存配置管理器支持創(chuàng)建
#    caffeine:
#      # caffeine 緩存
#      spec: x
#        # 配置細(xì)節(jié)
#    couchbase:
#      # couchbase 緩存
#      expiration: 0ms
#      # 超時(shí)時(shí)間,默認(rèn)永不超時(shí)
#    ehcache:
#      # ehcache 緩存
#      config: x
#      # 配置ehcache緩存初始化文件路徑
#    infinispan:
#      # infinispan 緩存
#      config: x
#      # 配置infinispan緩存配置文件
#    jcache:
#      # jcache 緩存
#      config: x
#      # jcache緩存配置文件
#      provider: ""
      # jcache緩存提供者配置
    redis:
      # redis緩存
      cache-null-values: true
      # 是否允許緩存空值
      key-prefix: x
      # redis的鍵的前綴
      time-to-live: 0ms
      # 緩存超時(shí)時(shí)間,配置為0則表示永不超時(shí)
      use-key-prefix: true
      # 是否啟用Redis的鍵前綴
    type: redis
    # 緩存類型所踊,默認(rèn)情況下泌枪,Spring會(huì)自動(dòng)根據(jù)上下文探測

除了在配置文件中配置,還需要啟用緩存機(jī)制

image-20200806193536400

2. 實(shí)例程序框架搭建

實(shí)例集成了oracle數(shù)據(jù)庫秕岛,MyBatis,Redis,緩存和日志碌燕。

增加依賴:

image-20200806194331192

3. 配置

配置:

spring:
  # redis 服務(wù)器配置
  redis:
    host: 10.0.228.117
    port: 6379
    password: ""
    timeout: 60000
    database: 0
    # 連接池屬性配置
    lettuce:
      pool:
        # 最小空閑連接數(shù)
        min-idle: 5
        # 最大空閑連接數(shù)
        max-idle: 10
        # 最大活動(dòng)的連接數(shù)
        max-active: 10
        # 連接最大等待數(shù)
        max-wait: 3000
  cache:
    # 緩存相關(guān)配置
    cache-names: redisCache
    redis:
      # redis緩存
      cache-null-values: true
      # 是否允許緩存空值
      key-prefix: x
      # redis的鍵的前綴
      time-to-live: 0ms
      # 緩存超時(shí)時(shí)間,配置為0則表示永不超時(shí)
      use-key-prefix: true
      # 是否啟用Redis的鍵前綴
    type: redis
    # 緩存類型继薛,默認(rèn)情況下修壕,Spring會(huì)自動(dòng)根據(jù)上下文探測
  datasource:
    # 配置數(shù)據(jù)庫
    driver-class-name: oracle.jdbc.OracleDriver
    # 數(shù)據(jù)庫驅(qū)動(dòng)
    url: jdbc:oracle:thin:@//10.0.250.19:1521/starbass
    # 數(shù)據(jù)庫連接
    username: system
    # 用戶名
    password: system
    # 密碼
    tomcat:
      # 數(shù)據(jù)庫相關(guān)配置
      max-idle: 10
      # 最大閑置的連接數(shù)量
      max-active: 10
      # 最大活躍連接數(shù)量
      min-idle: 5
      # 最小閑置數(shù)量
      max-wait: 2000
      # 最大等待時(shí)間
mybatis:
  # mybatis配置
  mapper-locations: classpath:com/study/redishello/mapper/*.xml
  # mybatis的文件目錄
  type-aliases-package: com.study.redishello.pojo
  # mybatis實(shí)體的包路徑
logging:
  # 日志配置
  level:
    root: debug
  # 日志級(jí)別:調(diào)試
    org:
      springframework: debug
      org:
        mybatis: debug

4. 創(chuàng)建實(shí)體

@Alias("user")
@Data
public class User implements Serializable {

    private Long id;

    private String userName;

    private String note;

}

@Data是lombok的注解,表示自動(dòng)生成getter,setter,構(gòu)造,toString,equals方法遏考。在調(diào)用toString和equals方法時(shí)慈鸠,會(huì)調(diào)用父類的方法。

@Alias定義了別名诈皿,這個(gè)別名在mybatis的xml文件中用到林束。

同時(shí)像棘,User類實(shí)現(xiàn)了Serializable接口稽亏,表明User類可以被序列化(redis緩存需要用到,redis存儲(chǔ)對(duì)象缕题,需要借助序列化和反序列化)

5. oracle

建表

CREATE TABLE t_user(
ID NUMBER NOT NULL,
user_name VARCHAR2(50) NOT NULL,
note VARCHAR2(200));

ALTER

主鍵

ALTER TABLE t_user ADD CONSTRAINT t_user_pk
PRIMARY KEY (ID);

序列

CREATE SEQUENCE seq_t_user
MINVALUE 1
MAXVALUE 99999
START WITH 1
INCREMENT BY 1
CACHE 20;

6. 創(chuàng)建dao

首先創(chuàng)建dao接口

@Repository
public interface UserDao {

    User getUser(Long id);

    int insertUser(User user);

    int updateUser(User user);

    List<User> findUsers(@Param("userName") String userName, @Param("note") String note);

    int deleteUser(Long id);

}

==使用了@Repository注解截歉,標(biāo)識(shí)這是個(gè)Dao。==

==在mybatis體系中烟零,這里應(yīng)該是@Mapper瘪松,但是使用@Mapper,后續(xù)在自動(dòng)注入dao時(shí)锨阿,IDE會(huì)提示宵睦,實(shí)際運(yùn)行正確。==

==我們?cè)赼pplication中使用了@MapperScan來指明我們的Mapper上面使用的是什么注解墅诡。==

image-20200807193725333

接著創(chuàng)建mybatis的xml文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.study.redishello.dao.UserDao">
    <select id="getUser" parameterType="long" resultType="user">
        select id,user_name as userName, note from t_user
        where id = #{id}
    </select>
    <insert id="insertUser" parameterType="user">
        <selectKey resultType="long" keyProperty="id" order="BEFORE">
            select seq_t_user.nextVal from dual
        </selectKey>
        insert into t_user(id,user_name,note) values(#{id},#{userName},#{note})
    </insert>
    <update id="updateUser">
        update t_user
        <set>
            <if test="userName != null">
                user_name = #{userName},
            </if>
            <if test="note != null">
                note=#{note},
            </if>
        </set>
        where id=#{id}
    </update>
    <select id="findUsers" resultType="user">
        select identity,user_name as userName,note from t_user
        <where>
            <if test="userName != null">
                and user_name=#{userName}
            </if>
            <if test="note != null">
                and note=#{note}
            </if>
        </where>
    </select>
    <delete id="deleteUser" parameterType="long">
        delete from t_user where id = #{id}
    </delete>
</mapper>

在insert中壳嚎,我們需要先查詢序列,然后將序列的值設(shè)置到user的id中,然后在執(zhí)行插入操作烟馅。order指明了在insert前執(zhí)行说庭。

xml的namespace就是指明了dao接口的路徑。

6. 創(chuàng)建Service

接口

public interface UserService {

    User getUser(Long id);

    User insertUser(User user);

    User updateUserName(Long id,String userName);

    List<User> findUsers(String userName, String note);

    int deleteUser(Long id);

}

實(shí)現(xiàn)

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao;

    @Override
    @Transactional
    @Cacheable(value = "redisCache", key = "'redis_user_'+#id")
    public User getUser(Long id) {
        return userDao.getUser(id);
    }

    @Override
    @Transactional
    @CachePut(value = "redisCache", key = "'redis_user_'+#result.id")
    public User insertUser(User user) {
        userDao.insertUser(user);
        return user;
    }

    @Override
    @Transactional
    @CachePut(value = "redisCache", condition = "#result != 'null' ", key = "'redis_user_'+#result.id")
    public User updateUserName(Long id, String userName) {
        User user = getUser(id);
        if (user == null) {
            return null;
        }
        user.setUserName(userName);
        userDao.updateUser(user);
        return user;
    }

    @Override
    @Transactional
    public List<User> findUsers(String userName, String note) {
        return userDao.findUsers(userName, note);
    }

    @Override
    @Transactional
    @CacheEvict(value = "redisCache", key = "'redis_user_'+#id", beforeInvocation = false)
    public int deleteUser(Long id) {
        return userDao.deleteUser(id);
    }
}

6.1 緩存注解

  • @CachePut : 將方法結(jié)果返回放到緩存中郑趁。
  • @Cacheable: 先從緩存中通過定義的鍵查詢刊驴,如果可以查詢到數(shù)據(jù),則返回寡润,否則執(zhí)行該方法捆憎,返回?cái)?shù)據(jù),并且將返回結(jié)果保存到緩存中悦穿。
  • @CacheEvict:通過定能夠以的鍵移除緩存攻礼,它有一個(gè)Boolean類型的配置項(xiàng):beforeInvocation,表示在方法之前或者方法之后移除緩存。默認(rèn)false,默認(rèn)在方法之后移除緩存

6.2 緩存使用參數(shù)

上述緩存注解中都配置了value="redisCache",因?yàn)槲覀冊(cè)谂渲梦募欣跗猓渲玫膔edis的名字就是redisCache:

image-20200807194650274

這樣就能夠引用到對(duì)應(yīng)的緩存了礁扮,而鍵配置規(guī)則是一個(gè)Spring EL,很多時(shí)候可以看到配置為'redis_user_'+#id其中#id代表參數(shù)瞬沦,通過名稱匹配太伊。所以參數(shù)中必須存在一個(gè)參數(shù)的名字是id。除此之外逛钻,還可以使用序號(hào)引用參數(shù)僚焦,比如#a[0]或者#p[1]表示第一個(gè)或者第二個(gè)。 ==在一次配置中曙痘,名字應(yīng)該一致芳悲。==

6.3 緩存返回值

當(dāng)希望使用返回結(jié)果的一些屬性緩存數(shù)據(jù),比如insertUser方法边坤。在插入數(shù)據(jù)庫前名扛,此時(shí)user還沒有id。而這個(gè)id將會(huì)在寫入數(shù)據(jù)庫時(shí)茧痒,由selectKey標(biāo)簽寫入肮韧。所以,需要使用返回結(jié)果的user的id旺订,這樣使用#result就代表了返回的user對(duì)象弄企。因?yàn)閕d是user的一個(gè)屬性,所以使用#result.id取出id区拳。

image.png

6.4 緩存條件

在updateUserName方法中拘领,可能存在返回null的情況。如果返回null樱调,則不需要進(jìn)行緩存约素。所以在注解@CachePut中加入了condition條件洽瞬,它也是一個(gè)Spring EL表達(dá)式。表達(dá)式要求返回Boolean類型的值业汰,如果尾true伙窃,則使用緩存操作;否則就不使用样漆。==同樣的@Cacheable和@CacheEvict也可以使用为障。==

image.png

6.5 緩存不可靠

在updateUserName的方法中,我們首先調(diào)用了getUser從數(shù)據(jù)庫中查詢user放祟,然后更新user鳍怨。因?yàn)榫彺嬷锌赡艽嬖谶^時(shí)的數(shù)據(jù),也就是臟數(shù)據(jù)跪妥⌒客戶端從緩存中獲取到臟數(shù)據(jù),然后在臟數(shù)據(jù)的基礎(chǔ)上進(jìn)行修改眉撵,最后在進(jìn)行更新侦香。此時(shí)就可能存在因緩存過時(shí)問題,造成==使用不可靠的緩存數(shù)據(jù)去更新數(shù)據(jù)庫數(shù)據(jù)纽疟。==

這是不可取的罐韩,非常危險(xiǎn)的操作。

不過污朽,在updateUserName的方法中散吵,調(diào)用getUser方法,每次都會(huì)從數(shù)據(jù)庫中讀取蟆肆。這里的==緩存失效==了矾睦。

image.png

6.6 緩存命中率低

對(duì)于方法findUsers,因?yàn)槊看蝹魅氲臈l件都不相同炎功,所以枚冗,就會(huì)導(dǎo)致緩存的命中率非常低,那么我們不使用緩存亡问。使用了緩存官紫,性能反而變慢.

image-20200807200743088

7. 驗(yàn)證

7.1 insert

@SpringBootTest
public class UserTests {

    @Autowired
    private UserService userService;

    @Test
    public void testAddUser(){
        User user = new User();
        user.setUserName(UUID.randomUUID().toString());
        user.setNote(UUID.randomUUID().toString());
        System.out.println(user);
        userService.insertUser(user);
    }

}
image-20200807201004971
image-20200807201010458

7.2 query

image-20200807201857302

可以發(fā)現(xiàn)肛宋,userService中的dao是空的州藕。

7.3 update

image-20200807202654098
image-20200807202722863
image-20200807202748590

7.4 delete

image-20200807202851349
image-20200807202903160

8. 緩存失效

在前面我們就提到了緩存失效,那么酝陈,為什么呢床玻?

因?yàn)镾pring的緩存機(jī)制也是基于Spring Aop的原理,而在Spring中AOP是通過動(dòng)態(tài)代理技術(shù)實(shí)現(xiàn)的沉帮,updateUserName調(diào)用getUser方法是類內(nèi)部的自調(diào)用锈死,并不存在代理對(duì)象的調(diào)用贫堰,這樣便不會(huì)出現(xiàn)AOP,也就不會(huì)使用到標(biāo)注在getUser上的緩存注解去獲取緩存的值了待牵。

如何避免緩存失效其屏?

答案是每次調(diào)用都產(chǎn)生一個(gè)新事務(wù),這樣就可以克服自調(diào)用緩存缨该、事務(wù)失效的問題了偎行。

image-20200807203342588

9. 緩存數(shù)據(jù)不可靠--臟數(shù)據(jù)

因?yàn)槲覀兊臄?shù)據(jù)是唯一的,但是使用的用途卻不唯一贰拿。每一種使用的用途都有可能需要做緩存蛤袒。

如果多個(gè)鍵保存了同一個(gè)數(shù)據(jù),那么在修改的時(shí)候膨更,只有修改的調(diào)用者知道妙真,這個(gè)緩存不是最新的,需要將數(shù)據(jù)庫中的數(shù)據(jù)更新到緩存荚守。而其余的調(diào)用者珍德,是不知道緩存的數(shù)據(jù)已經(jīng)被更新了,還在使用舊的緩存數(shù)據(jù)矗漾,就可能存在問題菱阵。這里就是使用了臟數(shù)據(jù)。

臟數(shù)據(jù)無法避免缩功,但是可以減小臟數(shù)據(jù)被使用的可能性晴及,比如給緩存加過期時(shí)間,或者每隔指定的時(shí)間嫡锌,統(tǒng)一隨機(jī)刷新緩存等等虑稼。

==所以,用到了緩存势木,在寫入數(shù)據(jù)的時(shí)候需要非常的小心蛛倦。==

10. 自定義緩存管理器

我們前面使用的緩存管理器,是通過配置啦桌,定制實(shí)現(xiàn)的緩存管理器溯壶,過期時(shí)間是0,表示永不過期甫男,自動(dòng)生產(chǎn)的鍵有x前綴

image-20200807204022231

如果通過配置定制的緩存管理器且改,無法滿足需求,還可以自定義實(shí)現(xiàn)緩存管理器

image-20200807204227791

此時(shí)需要去除緩存管理器的配置板驳,然后使用java 代碼又跛,自定義緩存管理器

image-20200807204913848

然后自定義緩存管理器

    @Bean
    public RedisCacheManager initRedisCacheManager(){
        // Redis加鎖寫入器
        RedisCacheWriter writer = RedisCacheWriter.lockingRedisCacheWriter(redisTemplate.getConnectionFactory());
        // 啟動(dòng)Redis緩存的默認(rèn)設(shè)置
        RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
        // 設(shè)置jdk序列化器
        .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new JdkSerializationRedisSerializer()))
        // 禁用前綴
        .disableKeyPrefix()
        // 設(shè)置超時(shí)時(shí)間 1分鐘
        .entryTtl(Duration.ofMinutes(1));
        // 創(chuàng)建Redis緩存管理器
        RedisCacheManager redisCacheManager = new RedisCacheManager(writer, cacheConfiguration);
        return redisCacheManager;
    }
image.png

驗(yàn)證

image-20200807205042980
image-20200807205104024
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市若治,隨后出現(xiàn)的幾起案子慨蓝,更是在濱河造成了極大的恐慌感混,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件礼烈,死亡現(xiàn)場離奇詭異弧满,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)此熬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門谱秽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人摹迷,你說我怎么就攤上這事疟赊。” “怎么了峡碉?”我有些...
    開封第一講書人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵近哟,是天一觀的道長。 經(jīng)常有香客問我鲫寄,道長吉执,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任地来,我火速辦了婚禮戳玫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘未斑。我一直安慰自己咕宿,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開白布蜡秽。 她就那樣靜靜地躺著府阀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪芽突。 梳的紋絲不亂的頭發(fā)上试浙,一...
    開封第一講書人閱讀 51,482評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音寞蚌,去河邊找鬼田巴。 笑死,一個(gè)胖子當(dāng)著我的面吹牛挟秤,可吹牛的內(nèi)容都是我干的壹哺。 我是一名探鬼主播,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼煞聪,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼斗躏!你這毒婦竟也來了逝慧?” 一聲冷哼從身側(cè)響起昔脯,我...
    開封第一講書人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤啄糙,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后云稚,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體隧饼,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年静陈,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了燕雁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡鲸拥,死狀恐怖拐格,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情刑赶,我是刑警寧澤捏浊,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站撞叨,受9級(jí)特大地震影響金踪,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜牵敷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一胡岔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧枷餐,春花似錦靶瘸、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至村生,卻和暖如春惊暴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背趁桃。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來泰國打工辽话, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人卫病。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓油啤,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蟀苛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子益咬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354