JAVA學習筆記_Redis進階

文章目錄

  • 初識redis
    • redis簡介
    • windows啟動redis服務器
    • linux啟動redis服務器
    • 圖形用戶界面客戶端RDM
  • redis命令
    • 常用數據類型
    • 特殊類型
    • 字符串操作命令
    • Key的層級格式
    • 哈希操作命令
    • 列表操作命令
    • 集合操作命令
    • 有序集合操作命令
    • 通用命令
  • java客戶端
    • Jedis
    • jedis連接池
    • SpringDataRedis
    • 序列化
    • 手動序列化
    • redisTemplate的方法習慣
  • 實戰篇
    • 短信登錄
      • 發送驗證碼
      • 短信驗證碼登錄和注冊
      • 登錄校驗攔截器
      • 隱藏用戶敏感信息
      • session共享問題
      • 基于redis代替session登錄流程
      • 基于redis實現短信登陸
      • 解決狀態登錄刷新的問題
    • 商戶查詢緩存
      • 添加商戶緩存
      • 緩存更新策略
      • 緩存穿透
      • 緩存雪崩
      • 緩存擊穿
      • 緩存工具封裝
    • 分布式鎖
      • 基于redis的分布式鎖
      • 分布式鎖誤刪問題
      • 多條redis命令原子性操作
      • java調用lua腳本
      • 基于redis的分布式鎖實現思路
    • redisson
      • 入門
      • 可重入鎖
      • 重試和超時續約
      • 主從一致性

初識redis

redis簡介

Redis(遠程詞典服務器),是一個基于內存的鍵值型NoSQL數據庫
Redis是一個基于內存的key-value結構數據庫
官網,www.redis.net.cn

  • 基于內存存儲,讀寫性能高
  • 適合存儲熱點數據(熱點商品、資訊、新聞)
  • 企業應用廣泛

windows啟動redis服務器

redis在windows上啟動服務端
redis屬于綠色軟件(文件夾),解壓即可使用
啟動redis服務
cmd中啟動redis-server.exe
客戶端(這里的客戶指的是開發人員)使用服務
cmd中啟動redis-cli.exe
命令示例,redis-cli.exe -h localhost -p 6379 -a 123456
-h 地址 -p 端口號 -a 密碼
密碼需要在redis.windows-service.conf手動設置
雖然可以在命令行使用redis,但一般還是圖形界面的redis更好用更主流(還是要手動啟動redis服務的)

linux啟動redis服務器

windows版本的redis都是微軟自己重寫的,redis官方并沒有windows版本,只有linux版本

linux版本
下載安裝略過
三種啟動方式
- 默認啟動,redis-server
- 指定配置啟動,需要修改redis.conf文件中的一些配置,主要是設置哪些ip可訪問redis,設置為守護進程(運行狀態不顯示在前端),用戶密碼,日志文件等,啟動命令,redis-server redis.conf
- 開機自啟動,需要配置vi /etc/systemd/system/redis.service
systemctl enable redis//redis開機自啟
systemctl daemon-reload//重載系統服務
# 啟動
systemctl start redis
# 停止
systemctl stop redis
# 重啟
systemctl restart redis
# 查看狀態
systemctl status redis

圖形用戶界面客戶端RDM

GitHub上的大神編寫了Redis的圖形化桌面客戶端,地址:https://github.com/uglide/RedisDesktopManager(收費)
在下面這個倉庫可以找到安裝包:https://github.com/lework/RedisDesktopManager-Windows/releases(免費)

redis命令

常用數據類型

各種命令可通過官網查看’https://www.redis.net.cn/’
key為字符串類型,value有5種常用的基本數據類型

  • 字符串string
  • 哈希hash ,類似HashMap
  • 列表list ,類似LinkedList
  • 集合set ,類似HashSet
  • 有序集合sorted set/zset 集合中每個元素關聯一個分數score,根據分數升序

特殊類型

  • GEO
  • BitMap
  • HyperLog

字符串操作命令

  • SET key value ,設置指定key的值
  • GET key ,獲取指定key的值
  • MSET key value,批量添加或修改
  • MGET key value,批量獲取
  • SETEX key seconds value ,設置指定key值,并將key的過期時間設置為sencond秒
    復合命令,等同于SET key value ex seconds
  • SETNX key value ,只有在key不存在時,設置key的值
    復合命令,等同于SET key value nx
  • INCR key,自增1
  • INCRBY key increment,自增指定步長
  • INCRBYFLOAT key increment,浮點數自增并指定步長
    string類型的三種格式,字符串,int,float,雖然都是string類型但是存儲規則不同,都是怎么節省空間怎么存儲

Key的層級格式

哈希操作命令

  • HSET key field value,設置值
  • HGET key field ,獲取值
  • HMSET key field value field value,批量設置
  • HGET key field1 field2,批量獲取
  • HGETALL key,獲取全部field字段和value
  • HKEYS key,獲取表中所有字段
  • HVALS key,獲取表中所有值
  • HINCRBY key field increment,設置一個key中的字段自增
  • HSETNX,添加一個Hash類型的key的field值,前提是這個field不存在,否則不執行
  • HDEL key field,刪除值

列表操作命令

  • LPUSH key element,將一個或多個值插入頭部(后插入的那端為頭部)
  • LPUSH key element,將一個或多個值插入尾部
  • LPOP key,移除并返回列表左側第一個元素,沒有則返回nil
  • RPOP key,移除并獲取列表最后一個元素,也就是第一個被插入的
  • LRANGE key start stop,獲取列表指定范圍的元素
  • BLPOP和BRPOP,與lpop和rpop類似,只不過在沒有元素時,等待指定時間,而不是直接返回nil
  • LLEN KEY,獲取列表長度

集合操作命令

  • SADD key mamber1 [member2],向集合添加一個或多個成員
  • SMEMBERS key,返回集合中的所有成員
  • SISMEMBER s1 a,判斷a是否在集合中
  • SCARD key,獲取集合的成員數
  • SINTER key1 [key2],返回給定集合的交集
  • SUNION key1 [key2],返回給定集合的并集
  • SREM key member1 [member2],刪除集合中的一個或多個成員

有序集合操作命令

每個元素關聯一個double類型的分數

  • ZADD key score1 member1 [score2 member2] 向有序集合添加一個或多個成員
  • ZSCORE KEY member,獲取sorted set中的指定元素的score值
  • ZRANK KEY member,獲取sorted set中的指定元素的排名
  • ZCARD key,獲取元素個數
  • ZCOUNT KEY min max,統計score值在指定范圍內的所有元素的個數
  • ZRANGE key start stop [WITHSCORS],通過索引區間返回指定區間的成員
  • ZINCRBY key increment member,添加上增量increment
  • ZRANGEBYSCORE key min max,按照score排序后,獲取指定score范圍內的元素
  • ZREM key member [member…],移除集合中的一個或多個成員
  • ZDIFF\ZINTER\ZUNION,求差集\交集\并集
    所有的排名默認都是升序的,如果要降序則在命令的Z后面添加REV即可

通用命令

help [command]查看一個命令的信息

  • KEYS pattern ,查找所有符合給定模式的key
  • EXISTS key, 檢查給定key是否存在
  • TYPE key , 返回key所存儲的值的類型
  • DEL key ,該命令用于在key存在時,刪除key
  • EXPIRE key age,設置key的有效期
  • TTL,查看一個KEY的剩余有效期

java客戶端

Jedis和Lettuce和Redisson

Jedis

引入依賴

<dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>5.2.0</version>
</dependency>
public class JedisTest {private Jedis jedis;@BeforeEachvoid setUp(){jedis = new Jedis("192.168.88.130", 6379);jedis.auth("123321");jedis.select(0);}@Testvoid testString(){String set = jedis.set("name", "虎哥");System.out.println(set);String name = jedis.get("name");System.out.println(name);}@AfterEachvoid tearDown(){if(jedis != null){jedis.close();}}
}

jedis連接池

配置連接池

public class JedisConnectionFactory {private static JedisPool jedisPool;static {//配置連接池,JedisPoolConfig poolConfig = new JedisPoolConfig();//最大連接poolConfig.setMaxIdle(8);//臨時連接poolConfig.setMaxIdle(8);//超過等待時間清零連接poolConfig.setMinIdle(0);//最大等待時間poolConfig.setMaxWaitMillis(1000);//創建連接池jedisPool = new JedisPool(poolConfig,"192.168.88.130",6379,1000,"123321");}public static Jedis getJedis(){return jedisPool.getResource();}
}

修改為從連接池中獲取jedis資源

jedis = JedisConnectionFactory.getJedis();

SpringDataRedis

提供了redisTemplate工具類,其中封裝了各種對redis的操作,并且將不同數據類型的操作API封裝到了不同的類型中
SpringDataRedis默認使用Lettuce
引入依賴

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

spring配置

spring:redis:host: 192.168.88.130port: 6379lettuce:pool:max-active: 8max-idle: 8min-idle: 0max-wait: 100mspassword: 123321

注入RedisTemplate

@Autowired
private RedisTemplate redisTemplate;
@Test
void testString(){redisTemplate.opsForValue().set("name","虎哥");Object name = redisTemplate.opsForValue().get("name");System.out.println(name);
}

序列化

redisTemplate可以接收到任意object作為值寫入redis,只不過寫入之前會把object序列化為字節形式,默認是采用JDK序列化(可讀性差,內存占用較大)
所以更改key和value的默認序列化

@Configuration
public class RedisConfig {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory){//創建redisteplate對象RedisTemplate<String, Object> template = new RedisTemplate<>();//設置連接工廠template.setConnectionFactory(connectionFactory);//創建json序列化工具GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();//設置key的序列化為string序列化template.setValueSerializer(RedisSerializer.string());template.setHashValueSerializer(RedisSerializer.string());//設置value的序列化為json序列化template.setValueSerializer(genericJackson2JsonRedisSerializer);template.setHashValueSerializer(genericJackson2JsonRedisSerializer);return template;};
}
@Test
void testSaveUser(){//寫入數據redisTemplate.opsForValue().set("user:100",new User("虎哥",100));//獲取數據Object o = redisTemplate.opsForValue().get("user:100");System.out.println(o);}

但是通常會定義一個類去與redis傳輸,redis中要存儲這個類的信息,也比較耗內存

手動序列化

為了節省內存空間,我們并不會使用json序列化器來處理value,而是統一使用string序列化器,要求只能存儲string類型的key和value,當需要存儲java對象時,手動完成對象的序列化和反序列化
spring默認提供了一個StringRedisTemplate類,它的key和value的序列化方式默認就似乎string方式,省去我們自定義RedisTemplate的過程

@Test
void testSaveUser() throws JsonProcessingException {//創建對象User user = new User("虎哥", 21);//手動序列化String s = objectMapper.writeValueAsString(user);//寫入數據stringRedisTemplate.opsForValue().set("user:200",s);//獲得數據String jsonUser = stringRedisTemplate.opsForValue().get("user:200");//手動反序列化User user1 = objectMapper.readValue(jsonUser, User.class);System.out.println(user1);}

redisTemplate的方法習慣

redisTemplate的方法命名習慣更貼近java的習慣比如redis的Hash的使用更貼近集合中的Hashmap的方法命名而不是HSET或HGET這樣的方法名

@Test
void testHash(){stringRedisTemplate.opsForHash().put("user:400","name","虎哥");stringRedisTemplate.opsForHash().put("user:400","age","20");Map<Object, Object> entries = stringRedisTemplate.opsForHash().entries("user:400");System.out.println(entries);
}

實戰篇

  • 短信登錄
  • 用戶查詢緩存
  • 達人探店
  • 優惠券秒殺
  • 好友關注
  • 附近商戶
  • 用戶簽到
  • UV統計

短信登錄

發送驗證碼

@Override
public Result sendCode(String phone, HttpSession session) {//校驗手機號if(RegexUtils.isPhoneInvalid(phone)){//不符合返回失敗return Result.fail("手機號格式錯誤");}//符合生成驗證碼String code = RandomUtil.randomNumbers(6);//將驗證碼放入sessionsession.setAttribute("code",code);//模擬發送驗證碼log.debug("發送驗證碼: {}"+ code);//返回okreturn Result.ok();
}

短信驗證碼登錄和注冊

登錄校驗攔截器

隱藏用戶敏感信息

session共享問題

session共享問題,多臺Tomcat并不共享session存儲空間,當請求切換到不同tomcat服務時導致數據丟失的問題.
多臺Tomcat可以互相傳輸session信息,但是問題是數據重復,內存浪費,而且傳輸也需要一定的延遲
所以需要替代方案滿足:數據共享,內存存儲,key\value結構(redis)

基于redis代替session登錄流程

基于redis實現短信登陸

解決狀態登錄刷新的問題

  • service層
  • 攔截器
  • 常量類
    service層
    @Overridepublic Result sendCode(String phone, HttpSession session) {//校驗手機號if(RegexUtils.isPhoneInvalid(phone)){//不符合返回失敗return Result.fail("手機號格式錯誤");}//符合生成驗證碼String code = RandomUtil.randomNumbers(6);//將驗證碼放入redisstringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY+phone,code,LOGIN_CODE_TTL, TimeUnit.MINUTES);//模擬發送驗證碼log.debug("發送驗證碼: {}", code);//返回okreturn Result.ok();}@Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {// 1.校驗手機號String phone = loginForm.getPhone();if (RegexUtils.isPhoneInvalid(phone)) {// 2.如果不符合,返回錯誤信息return Result.fail("手機號格式錯誤!");}// 3.從redis獲取驗證碼并校驗String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);String code = loginForm.getCode();if (cacheCode == null || !cacheCode.equals(code)) {// 不一致,報錯return Result.fail("驗證碼錯誤");}// 4.一致,根據手機號查詢用戶 select * from tb_user where phone = ?User user = query().eq("phone", phone).one();//判斷用戶是否存在if (user == null) {user = createUserWithPhone(phone);}//隨機生成tokenString token = UUID.randomUUID().toString(true);//token和用戶信息存入redisUserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),CopyOptions.create().setIgnoreNullValue(true).setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));//設置token的有效期String tokenKey = LOGIN_USER_KEY +token;stringRedisTemplate.opsForHash().putAll(tokenKey,userMap);stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);//返回token給前端return Result.ok(token);}private User createUserWithPhone(String phone){//創建用戶User user = new User();user.setPhone(phone);user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));//保存用戶save(user);return user;}

第一級攔截器攔截所有

public class RefreshTokenInterceptor implements HandlerInterceptor {private StringRedisTemplate stringRedisTemplate;public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//獲取請求頭中的tokenString token = request.getHeader("authorzation");//還沒token放行if(StrUtil.isBlank(token)) {return true;}//基于token獲取redis中的用戶String key = LOGIN_USER_KEY + token;Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);//判斷用戶是否存在if(userMap.isEmpty()){return true;}//將查詢到的hash數據轉為UserDTOUserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);//存在,保存用戶信息到threalocalUserHolder.saveUser(userDTO);//刷新token有效期stringRedisTemplate.expire(key,LOGIN_USER_TTL, TimeUnit.MINUTES);//放行return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {// 移除用戶UserHolder.removeUser();}
}

第二級校驗是否為登錄用戶,不是登錄用戶不用處理請求了(熱點訪問,登錄,發送驗證碼要排除)

public class LoginInterceptor implements HandlerInterceptor {public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//判斷是否需要攔截(threalocal中是否有用戶,看看是不是有人瞎jb點)//之前那個攔截器已經用token獲取threadlocal中的用戶信息了if(UserHolder.getUser()==null){//沒有,需要攔截,設置狀態碼response.setStatus(401);//攔截return false;}return true;}
}

為了代碼的簡潔性優雅性和開閉原則,需要封裝常量

public class RedisConstants {public static final String LOGIN_CODE_KEY = "login:code:";public static final Long LOGIN_CODE_TTL = 2L;public static final String LOGIN_USER_KEY = "login:token:";public static final Long LOGIN_USER_TTL = 36000L;public static final Long CACHE_NULL_TTL = 2L;public static final Long CACHE_SHOP_TTL = 30L;public static final String CACHE_SHOP_KEY = "cache:shop:";public static final String LOCK_SHOP_KEY = "lock:shop:";public static final Long LOCK_SHOP_TTL = 10L;public static final String SECKILL_STOCK_KEY = "seckill:stock:";public static final String BLOG_LIKED_KEY = "blog:liked:";public static final String FEED_KEY = "feed:";public static final String SHOP_GEO_KEY = "shop:geo:";public static final String USER_SIGN_KEY = "sign:";
}

商戶查詢緩存

緩存是數據交換的緩沖區(cache),是存貯數據的臨時地方,一般讀寫性能較高
CPU的緩存就在cpu內部,比磁盤和內存更快,一般1MB-64MB
redis緩存還是在CPU中

添加商戶緩存

先到redis中查商戶信息,查不到再到mysql中查,查出來放入redis中

緩存更新策略

  • 內存淘汰,reids自動實現,但一致性差
  • 超時剔除,可以給數據添加TTL,一致性一般
  • 主動更新,編寫邏輯,主動實現更新,一致性好
    主動更新是最好的方案,當然也可以結合其他方案使用
    主動更新策略
  • cache aside pattern,調用者主動更新,
  • read/write through pattern,緩存與數據庫整合為一個服務,但是找一個現成的這樣的業務很難
  • write behind caching pattern,只改緩存
    cache aside pattern是最好的方案
    先寫數據庫,再刪緩存要比先刪緩存再寫數據庫的出錯率低
高一致性需求,主動更新,并以超時剔除作為兜底方案
讀操作:
緩存命中則直接返回
緩存未命中查詢數據庫,并寫入緩存,設定超時時間
寫操作:
先寫數據庫,然后再刪除緩存
要確保數據庫與緩存操作的原子性

緩存穿透

緩存這一系列問題就是為了減少對sql數據庫的查詢,因為對數據庫的操作相當一次網絡請求,耗時長,對計算資源的消耗也高
緩存穿透是指客戶端請求的數據在緩存中和數據庫中都不存在,這樣緩存永遠不會生效,這些請求都會打到數據庫,給數據庫帶來巨大壓力

  • 緩存空對象
    優點:實現簡單,維護方便
    缺點:額外的內存消耗,可能造成短期的不一致
  • 布隆過濾
    優點:內存占用較少,沒有多余key
    缺點:實現復雜,存在誤判可能
    緩存穿透的解決方案:
  • 緩存null值
  • 布隆過濾
  • 增強id的復雜度,避免被猜測id規律
  • 做好數據的基礎格式校驗
  • 加強用戶權限校驗
  • 做好熱點參數的限流

緩存雪崩

緩存雪崩是指在同一時段大量的緩存key同時失效或者redis服務宕機,導致大量請求到達數據庫,帶來巨大壓力
解決方案:

  • 給不同的key的TTL添加隨機值
  • 利用redis集群提高服務的可用性
  • 給緩存業務添加降級限流策略
  • 給業務添加多級緩存

緩存擊穿

緩存擊穿問題也叫熱點key問題,就是一個被高并發訪問并且緩存重建業務較復雜的key突然失效,無數的請求訪問會在瞬間給數據庫帶來巨大的沖擊
常見的解決方案:

  • 互斥鎖
    優點:沒有額外的內存消耗,保證取數據一致性,實現簡單
    缺點:線程需要等待,性能受影響,可能有死鎖風險
  • 邏輯過期
    優點:線程無需等待,性能較好
    缺點:不保證一致性,有額外內存消耗,實現復雜
    Apache JMeter,高并發壓力測試工具
    可以顯示線程處理的最大\最小\平均值,異常值,吞吐量等
    基于互斥鎖解決緩存擊穿
public <R, ID> R queryWithMutex(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {String key = keyPrefix + id;// 1.從redis查詢商鋪緩存String shopJson = stringRedisTemplate.opsForValue().get(key);// 2.判斷是否存在if (StrUtil.isNotBlank(shopJson)) {// 3.存在,直接返回return JSONUtil.toBean(shopJson, type);}// 判斷命中的是否是空值if (shopJson != null) {// 返回一個錯誤信息return null;}// 4.實現緩存重建// 4.1.獲取互斥鎖String lockKey = LOCK_SHOP_KEY + id;R r = null;try {boolean isLock = tryLock(lockKey);// 4.2.判斷是否獲取成功if (!isLock) {// 4.3.獲取鎖失敗,休眠并重試Thread.sleep(50);return queryWithMutex(keyPrefix, id, type, dbFallback, time, unit);}// 4.4.獲取鎖成功,根據id查詢數據庫r = dbFallback.apply(id);// 5.不存在,返回錯誤if (r == null) {// 將空值寫入redisstringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);// 返回錯誤信息return null;}// 6.存在,寫入redisthis.set(key, r, time, unit);} catch (InterruptedException e) {throw new RuntimeException(e);}finally {// 7.釋放鎖unlock(lockKey);}// 8.返回return r;
}private boolean tryLock(String key) {Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);return BooleanUtil.isTrue(flag);
}private void unlock(String key) {stringRedisTemplate.delete(key);
}

基于邏輯過期解決緩存擊穿

    public <R, ID> R queryWithLogicalExpire(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {String key = keyPrefix + id;// 1.從redis查詢商鋪緩存String json = stringRedisTemplate.opsForValue().get(key);// 2.判斷是否存在if (StrUtil.isBlank(json)) {// 3.存在,直接返回return null;}// 4.命中,需要先把json反序列化為對象RedisData redisData = JSONUtil.toBean(json, RedisData.class);R r = JSONUtil.toBean((JSONObject) redisData.getData(), type);LocalDateTime expireTime = redisData.getExpireTime();// 5.判斷是否過期if(expireTime.isAfter(LocalDateTime.now())) {// 5.1.未過期,直接返回店鋪信息return r;}// 5.2.已過期,需要緩存重建// 6.緩存重建// 6.1.獲取互斥鎖String lockKey = LOCK_SHOP_KEY + id;boolean isLock = tryLock(lockKey);// 6.2.判斷是否獲取鎖成功if (isLock){// 6.3.成功,開啟獨立線程,實現緩存重建CACHE_REBUILD_EXECUTOR.submit(() -> {try {// 查詢數據庫R newR = dbFallback.apply(id);// 重建緩存this.setWithLogicalExpire(key, newR, time, unit);} catch (Exception e) {throw new RuntimeException(e);}finally {// 釋放鎖unlock(lockKey);}});}// 6.4.返回過期的商鋪信息return r;}

緩存工具封裝

為了緩存工具開發維護成本,需要將緩存常用代碼封裝成工具類

分布式鎖

sychronized只能對同一個jvm內的線程進行鎖操作
分布式系統:多個 Java 程序可能在不同的物理或虛擬機器上運行,每個程序啟動一個 JVM。
分布式鎖,滿足分布式系統或集群模式下多進程可見并且互斥的鎖(對多個jvm的所有線程鎖操作)
分布式鎖的實現
Mysql:利用mysql本身的互斥鎖機制
redis:利用setnx這樣的互斥命令
zookeeper:利用節點的唯一性和有序性實現互斥

基于redis的分布式鎖

需要實現獲取鎖(設置一個redis鍵值對)和釋放鎖,確保只有一個線程獲取鎖,也要保證獲取鎖和釋放鎖操作設置的原子性,否則某個線程獲取了鎖,但進程突然宕機,就無法釋放鎖
set lock thread1 nx ex 10//是最好的選擇,nx保證互斥,ex 10在一定時間后釋放鎖

分布式鎖誤刪問題

需要判斷是不是自己的鎖再刪除

  • 在獲取鎖時存入線程標識(可以用UUID表示,因為不同jvm里可能有不同的線程有同一線程id)
  • 在釋放鎖時先獲取鎖中的線程標識,判斷是否與當前線程標識一致,如果一致再釋放鎖

多條redis命令原子性操作

需要保證判斷鎖和釋放鎖的原子性操作,需要用到lua腳本,否則在極端情況會出現線程亂套的問題(比如線程A釋放線程B的鎖)
lua腳本,redis提供了lua腳本功能,在一個腳本中編寫多條redis命令,確保原子性
官網:https://www.runoob.com/lua/lua-tutorial.html

java調用lua腳本

redisTemplate提供了調用lua腳本的API

基于redis的分布式鎖實現思路

  • 利用set nx ex獲取鎖,并設置過期時間,保存線程標識(重點1)
  • 釋放鎖時,先判斷線程標識是否與自己一致,一致則刪除鎖(重點2)
    特性:
  • 利用set nx滿足互斥性
  • 利用set ex保證故障時鎖依然能釋放,避免死鎖.提高安全性
  • 利用lua腳本保證redis命令原子性操作
  • 利用redis集群保證高可用性和高并發性

redisson

redisson是一個在redis的基礎上實現的java駐內存數據網格(in-memory data grid),不僅提供了一系列的分布式的java常用對象,還提供了許多分布式服務,其中就包含了各種分布式鎖的實現
官網:https://redisson.org/
reidsson中的API方案不僅集成了上面的優化策略,還有解決以下問題的策略:

  • 不可重入:同一個線程無法多次獲取同一把鎖
  • 不可重試:獲取鎖只嘗試一次就返回false,沒有重試機制
  • 超時釋放:鎖超時釋放雖然可以避免死鎖,但如果是業務執行耗時較長,也會導致鎖釋放,存在安全隱患
  • 主從一致性:如果redis提供了主從集群,主從同步存在延遲,當主宕機時,如果從并沒有同步主中的鎖數據,則會出現鎖實現

入門

引入依賴

<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.13.6</version>
</dependency>

配置redisson

@Configuration
public class RedissonConfig {@Beanpublic RedissonClient redissonClient(){// 配置Config config = new Config();config.useSingleServer().setAddress("redis://192.168.88.130:6379").setPassword("123321");// 創建RedissonClient對象return Redisson.create(config);}
}

使用redisson

@Transactional
public Result createVoucherOrder(Long voucherId) {// 5.一人一單Long userId = UserHolder.getUser().getId();// 創建鎖對象RLock redisLock = redissonClient.getLock("lock:order:" + userId);// 嘗試獲取鎖boolean isLock = redisLock.tryLock();// 判斷if(!isLock){// 獲取鎖失敗,直接返回失敗或者重試return Result.fail("不允許重復下單!");}try {.....} finally {// 釋放鎖redisLock.unlock();}}

可重入鎖

利用hash結構記錄線程id和重入次數

既要記錄線程id和重入次數還是hash比string要方便

重試和超時續約

可重試:利用信號量和PubSub功能實現等待,喚醒,獲取鎖失敗的重試機制
超時續約:利用watchDog,每隔一段時間(releaseTime/3),重置超時時間

主從一致性

redisson的multiLock:

  • 原理: 多個獨立的redis節點(redis集群,相當于多個mysql數據庫備份),必須在所有節點都獲取重入鎖,才算獲取鎖成功
  • 缺陷:運維成本高,實現復雜

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/64622.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/64622.shtml
英文地址,請注明出處:http://en.pswp.cn/web/64622.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

1月第一講:WxPython跨平臺開發框架之前后端結合實現附件信息的上傳及管理

1、功能描述和界面 前端&#xff08;wxPython GUI&#xff09;&#xff1a; 提供文件選擇、顯示文件列表的界面。支持上傳、刪除和下載附件。展示上傳狀態和附件信息&#xff08;如文件名、大小、上傳時間&#xff09;。后端&#xff08;REST API 服務&#xff09;&#xff1a…

面試經典150題——滑動窗口

文章目錄 1、長度最小的子數組1.1 題目鏈接1.2 題目描述1.3 解題代碼1.4 解題思路 2、無重復字符的最長子串2.1 題目鏈接2.2 題目描述2.3 解題代碼2.4 解題思路 3、串聯所有單詞的子串3.1 題目鏈接3.2 題目描述3.3 解題代碼3.4 解題思路 4、最小覆蓋子串4.1 題目鏈接4.2 題目描…

12.29~12.31[net][review]need to recite[part 2]

網絡層 IP 首部的前一部分是固定長度&#xff0c;共 20 字節&#xff0c;是所有 IP 數據報必須具有的 路由器 路由選擇協議屬于網絡層控制層面的內容 l 路由器 的 主要工作&#xff1a; 轉發分組。 l 路由 信息協議 RIP (Routing Information Protocol ) 是 一種 分布式的…

免費下載 | 2024網絡安全產業發展核心洞察與趨勢預測

《2024網絡安全產業發展核心洞察與趨勢預測》報告的核心內容概要&#xff1a; 網絡安全產業概況&#xff1a; 2023年中國網絡安全產業市場規模約992億元&#xff0c;同比增長7%。 預計2024年市場規模將增長至1091億元&#xff0c;2025年達到1244億元。 網絡安全企業數量超過4…

Django項目部署到服務器

文章目錄 django項目部署到服務器在服務器上安裝Django和依賴&#xff1a;項目代碼上傳配置數據庫收集靜態文件配置Web服務器配置Gunicorn&#xff08;WSGI服務器&#xff09;啟動/停止/重載systemd服務。 django項目部署到服務器 在服務器上安裝Django和依賴&#xff1a; su…

記憶旅游系統|Java|SSM|VUE| 前后端分離

【技術棧】 1??&#xff1a;架構: B/S、MVC 2??&#xff1a;系統環境&#xff1a;Windowsh/Mac 3??&#xff1a;開發環境&#xff1a;IDEA、JDK1.8、Maven、Mysql5.7 4??&#xff1a;技術棧&#xff1a;Java、Mysql、SSM、Mybatis-Plus、VUE、jquery,html 5??數據庫可…

微信小程序:定義頁面標題,動態設置頁面標題,json

1、常規設置頁面標題 正常微信小程序中&#xff0c;設置頁面標題再json頁面中進行設置&#xff0c;例如 {"usingComponents": {},"navigationBarTitleText": "標題","navigationBarBackgroundColor": "#78b7f7","navi…

基于通用優化軟件GAMS的數學建模和優化分析;GAMS安裝和介紹、GAMS程序編寫、GAMS程序調試、實際應用算例演示與經驗分享

GAMS&#xff08;General Algebraic Modeling System&#xff09;是一款高級建模系統&#xff0c;主要用于解決線性規劃、非線性規劃、動態規劃、混合整數規劃等優化問題。它以其簡單清晰的用戶接口和強健穩定的數值分析能力而著稱&#xff0c;適用于大型、復雜的優化問題。GAM…

理解生成協同促進?華為諾亞提出ILLUME,15M數據實現多模態理解生成一體化

多模態理解與生成一體化模型&#xff0c;致力于將視覺理解與生成能力融入同一框架&#xff0c;不僅推動了任務協同與泛化能力的突破&#xff0c;更重要的是&#xff0c;它代表著對類人智能&#xff08;AGI&#xff09;的一種深層探索。通過在單一模型中統一理解與生成&#xff…

學習vue3的筆記

一、vue和react的對比 1、基礎介紹 vue&#xff1a;https://cn.vuejs.org/ vue3是2020年創建的 react&#xff1a;https://react.dev/ react是一個2013年開源的JavaScript庫&#xff0c;嚴格意義上來說不是一個框架 2、diff算法 兩個框架采用的都是同級對比策略 兩節點對…

SQLiteDataBase數據庫

XML界面設計 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"xmlns:tools"http://schemas.android.com/tools"android:layout_width"match_paren…

k8s部署nginx+sshd實現文件上傳下載

要通過 nginx 和 sshd 實現文件的上傳和下載&#xff0c;通常的做法是結合 SSH 協議和 HTTP 協議&#xff0c;使用 nginx 提供 Web 服務器功能&#xff0c;同時使用 sshd&#xff08;即 SSH 服務&#xff09;來處理通過 SSH 協議進行的文件傳輸。 SSH 實現文件的上傳和下載&…

Golang 中 Goroutine 的調度

Golang 中 Goroutine 的調度 Golang 中的 Goroutine 是一種輕量級的線程&#xff0c;由 Go 運行時&#xff08;runtime&#xff09;自動管理。Goroutine 的調度基于 M:N 模型&#xff0c;即多個 Goroutine 可以映射到多個操作系統線程上執行。以下是詳細的調度過程和策略&…

clickhouse-backup配置及使用(Linux)

一、下載地址 Releases Altinity/clickhouse-backup GitHub 二、上傳到服務器解壓安裝 自行上傳至服務器&#xff0c;解壓命令&#xff1a; tar xvf clickhouse-backup-linux-amd64.tar.gz 三、創建軟連接 sudo ln -sv build/linux/amd64/clickhouse-backup /usr/local/bin/…

如何在群暉NAS上安裝并配置MySQL與phpMyAdmin遠程管理數據庫

文章目錄 前言1. 安裝MySQL2. 安裝phpMyAdmin3. 修改User表4. 本地測試連接MySQL5. 安裝cpolar內網穿透6. 配置MySQL公網訪問地址7. 配置MySQL固定公網地址8. 配置phpMyAdmin公網地址9. 配置phpmyadmin固定公網地址 前言 大家是不是經常遇到需要隨時隨地訪問自己數據的情況&am…

《向量數據庫指南》——Milvus Cloud 2.5:Sparse-BM25引領全文檢索新時代

Milvus Cloud BM25:重塑全文檢索的未來 在最新的Milvus Cloud 2.5版本中,我們自豪地引入了“全新”的全文檢索能力,這一創新不僅鞏固了Milvus Cloud在向量數據庫領域的領先地位,更為用戶提供了前所未有的靈活性和效率。作為大禹智庫的向量數據庫高級研究員,以及《向量數據…

SQL 總結

SQL 總結 引言 SQL(Structured Query Language,結構化查詢語言)是一種用于管理關系數據庫管理系統(RDBMS)的標準編程語言。自1974年首次提出以來,SQL已成為數據庫領域中不可或缺的一部分。它允許用戶執行各種操作,如查詢、更新、插入和刪除數據庫中的數據。本文旨在提…

ESP32-CAM開發板入門 (下載示例程序)

ESP32-CAM開發板例程使用 1、準備工作1.1、硬件準備1.2、軟件準備 2、選擇示例程序并錄入第一步 1、準備工作 1.1、硬件準備 1.2、軟件準備 Arduino IDE &#xff1a; 編程與寫入&#xff08;下載地址 https://www.arduino.cc/en/software&#xff09; 安裝好后將軟件設置到…

企業賦能是什么意思-國際數字影像產業園解讀

在當今競爭激烈的商業環境中&#xff0c;企業賦能已成為推動企業發展、提升競爭力的關鍵策略。國際數字影像產業園作為數字影像產業的重要集聚地&#xff0c;通過一系列創新舉措為入駐園區的我眾多企業賦能。那么&#xff0c;企業賦能究竟是什么意思呢&#xff1f; 企業賦能是…

混合并行訓練框架性能對比

混合并行訓練框架性能對比 1. 框架類型 DeepSpeed、Megatron - LM、Colossal - AI、SageMaker、Merak、FasterMoE、Tutel、Whale、Alpa、DAPPLE、Mesh - TensorFlow 2. 可用并行性(Available parallelisms) DNN framework(深度神經網絡框架)DP(數據并行,Data Parallelis…