Redis分布式锁的使用和实现原理详解

2023-10-31

这篇文章主要给大家介绍了关于Redis分布式锁的使用和实现原理的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
 

模拟一个电商里面下单减库存的场景。

第一版代码:存在超卖

1.首先在redis里加入商品库存数量。

2.新建一个Spring Boot项目,在pom里面引入相关的依赖。

1
2
3
4
5
6
7
8
9
<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>

3.接下来,在application.yml配置redis属性和指定应用的端口号:

1
2
3
4
5
6
7
server:
 port: 8090
 
spring:
 redis:
 host: 192.168.0.60
 port: 6379

4.新建一个Controller类,扣减库存第一版代码:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
import javax.annotation.Resource;
import java.util.Objects;
 
@RestController
public class StockController {
 
 private static final Logger logger = LoggerFactory.getLogger(StockController.class);
 
 @Resource
 private StringRedisTemplate stringRedisTemplate;
 
 @RequestMapping("/reduceStock")
 public String reduceStock() {
 // 从redis中获取库存数量
 int stock = Integer.parseInt(Objects.requireNonNull(stringRedisTemplate.opsForValue().get("stockCount")));
 if (stock > 0) {
  // 减库存
  int restStock = stock - 1;
  // 剩余库存再重新设置到redis中
  stringRedisTemplate.opsForValue().set("stockCount", String.valueOf(restStock));
  logger.info("扣减成功,剩余库存:{}", restStock);
 } else {
  logger.info("库存不足,扣减失败。");
 }
 
 return "success";
 }
}
上面第一版的代码存在什么问题:超卖。假如多个线程同时调用获取库存数量的代码,那么每个线程拿到的都是100,判断库存都大于0,都可以执行减库存的操作。假如两个线程都做减库存更新缓存,那么缓存的库存变成99,但实际上,应该是减掉2个库存。

那么很多人的第一个想法是加synchronized同步代码块,因为获取数量和减库存不是原子性操作,有多个线程来执行代码的时候,只允许一个线程执行代码块里的代码。

第二版:加synchronized同步代码块

@RequestMapping("/reduceStock")
public String reduceStock() {
synchronized (this) {
 // 从redis中获取库存数量
 int stock = Integer.parseInt(Objects.requireNonNull(stringRedisTemplate.opsForValue().get("stockCount")));
 if (stock > 0) {
 // 减库存
 int restStock = stock - 1;
 // 剩余库存再重新设置到redis中
 stringRedisTemplate.opsForValue().set("stockCount", String.valueOf(restStock));
 logger.info("扣减成功,剩余库存:{}", restStock);
 } else {
 logger.info("库存不足,扣减失败。");
 }
}
 
return "success";
}
但使用synchronize存在的问题,就是只能保证单机环境运行时没有问题的。但现在的软件公司里,基本上都是集群架构,是多实例,前面使用Nginx做负载均衡,大概架构如下:

Nginx分发请求,把请求发送到不同的Tomcat容器,而synchronize只能保证一个应用是没有问题的。

第三版:引入redis分布式锁

具体代码如下:

1. 基础:只设置了锁

@RequestMapping("/reduceStock")public String reduceStock() {
String lockKey = "stockKey";
try {
    boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "1"); 
    if (!result) {
         return "errorCode"; 
    }
     // 从redis中获取库存数量
    int stock =    Integer.parseInt(Objects.requireNonNull(stringRedisTemplate.opsForValue().get("stockCount"))); 
    if (stock > 0) { // 减库存 
         int restStock = stock - 1; // 剩余库存再重新设置到redis
         RedisTemplate.opsForValue().set("stockCount", String.valueOf(restStock)); 
         logger.info("扣减成功,剩余库存:{}", restStock); 
    } 
    else { 
        logger.info("库存不足,扣减失败。"); 
      }
} 
finally { 
    stringRedisTemplate.delete(lockKey)
}
return "success";
}
如果有一个线程拿到锁,那么其他的线程就会等待。一定要记得在finally里面把使用完的锁要删除掉。否则一旦抛出异常,只有一个线程会一直持有锁,其他线程没有机会获取。

2. 进阶:为防止死锁,加过期时间 

但如果在执行if (stock > 0) {代码块里的代码,因为宕机或重启没有执行完,也会一直持有锁,所以,这里需要把锁加一个超时时间:

boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "1");
stringRedisTemplate.expire(lockKey, 10, TimeUnit.SECONDS);
 

但如果上面两行代码在中间执行出问题了,设置超时时间的代码还没执行,也会出现锁不能释放的问题。好在有对应的方法:就是把上面两行代码设置成一个原子操作:

// 这里默认设置超时时间为10秒
boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
到此为止,如果并发量不是很大的话,基本上是没有问题的。

3. 进阶: 并发量很大,多用户锁互删、覆盖问题

为每个请求生成唯一的id,保证自己只能删除自己的锁

但是,如果请求的并发量很大,就会出现新的问题:有种比较特殊的情况,第一个线程执行了15秒,但是执行到10秒钟的时候,锁已经失效释放了,那么在高并发场景下,第二个线程发现锁已经失效,那么它就可以拿到这把锁进行加锁,
假设第二个线程执行需要8秒,它执行到5秒钟后,此时第一个线程已经执行完了,执行完那一刻,进行了删除key的操作,但是此时的锁是第二个线程加的,这样第一个线程把第二个线程加的锁删掉了。

那意味着第三个线程又可以拿到锁,第三个线程执行了3秒钟,此时第二个线程执行完毕,那么第二个线程把第三个线程的锁又删除了。导致锁失效。

那么解决的思路就是,我自己加的锁,不要被别人删掉。那么可以为每个进来的请求生成一个唯一的id,作为分布式锁的值,然后在释放时,判断一下当前线程的id,是不是和缓存里的id是否相等。

@RequestMapping("/reduceStock")
public String reduceStock() {
String lockKey = "stockKey";
String id = UUID.randomUUID().toString();
try {
 // 这里默认设置超时时间为30秒
 boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, id, 30, TimeUnit.SECONDS);
 if (!result) {
 return "errorCode";
 }
 // 从redis中获取库存数量
 int stock = Integer.parseInt(Objects.requireNonNull(stringRedisTemplate.opsForValue().get("stockCount")));
 if (stock > 0) {
 // 减库存
 int restStock = stock - 1;
 // 剩余库存再重新设置到redis中
 stringRedisTemplate.opsForValue().set("stockCount", String.valueOf(restStock));
 logger.info("扣减成功,剩余库存:{}", restStock);
 } else {
 logger.info("库存不足,扣减失败。");
 }
} finally {
 if (id.contentEquals(Objects.requireNonNull(stringRedisTemplate.opsForValue().get(lockKey)))) {
 stringRedisTemplate.delete(lockKey);
 }
}
return "success";
}
 

到此为止,一个比较完善的锁就实现了,可以应付大部分场景。
 

4. 进阶进阶:锁续命

当然,上面的代码还有一个问题,就是一个线程执行时间超过了过期时间,后面的代码还没有执行完,锁就已经删除了,还是会有些bug存在。解决的方法是给锁续命的操作。
在当前主线程获取到锁以后,可以fork出一个线程,执行Timer定时器操作,假如默认超时时间为30秒,那么定时器每隔10秒去看下这把锁还是否存在,存在就说明这个锁里的逻辑还没有执行完,那么就可以把当前主线程的超时时间重新设置为30秒;如果不存在,就直接结束掉。

用Redisson来实现分布式锁

但是上面的逻辑,在高并发场景下,实现比较完善还是比较困难的。好在现在已经有比较成熟的框架,那就是Redisson。官方地址https://redisson.org。

引入依赖包:

1
2
3
4
5
<dependency>
 <groupId>org.redisson</groupId>
 <artifactId>redisson</artifactId>
 <version>3.6.5</version>
</dependency>

配置类:

1
2
3
4
5
6
7
8
9
10
@Configuration
public class RedissonConfig {
 @Bean
 public Redisson redisson() {
  // 单机模式
  Config config = new Config();
  config.useSingleServer().setAddress("redis://192.168.0.60:6379").setDatabase(0);
  return (Redisson) Redisson.create(config);
 }
}

用redisson重写上面的减库存操作:

 

@Resource
private Redisson redisson;
 
@RequestMapping("/reduceStock")
public String reduceStock() {
 String lockKey = "stockKey";
 RLock redissonLock = redisson.getLock(lockKey);
 try {
  // 加锁,锁续命
  redissonLock.lock();
  // 从redis中获取库存数量
  int stock = Integer.parseInt(Objects.requireNonNull(stringRedisTemplate.opsForValue().get("stockCount")));
  if (stock > 0) {
   // 减库存
   int restStock = stock - 1;
   // 剩余库存再重新设置到redis中
   stringRedisTemplate.opsForValue().set("stockCount", String.valueOf(restStock));
   logger.info("扣减成功,剩余库存:{}", restStock);
  } else {
   logger.info("库存不足,扣减失败。");
  }
 } finally {
  redissonLock.unlock();
 }
 return "success";
}
其实就是三个步骤:获取锁,加锁,释放锁。

Redisson的实现原理:

这里先说一下Redis很多操作使用Lua脚本来实现原子性操作,关于Lua语法,可以去网上找下相关教程。
使用Lua脚本的好处有:

1.减少网络开销,多个命令可以使用一次请求完成;

2.实现了原子性操作,Redis会把Lua脚本作为一个整体去执行;

3.实现事务,Redis自带的事务功能有限,而Lua脚本实现了事务的常规操作,而且还支持回滚。

但是Lua实际上不会使用很多,如果Lua脚本执行时间过长,因为Redis是单线程,因此会导致堵塞。

Redisson分布式锁的代码实现分析

找到上面的redissonLock.lock();
lock方法点进去,一直点到RedissonLock类里面的lockInterruptibly方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Override
 public void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException {
  // 获取线程id
  long threadId = Thread.currentThread().getId();
  Long ttl = tryAcquire(leaseTime, unit, threadId);
  // lock acquired
  if (ttl == null) {
   return;
  }
 
  RFuture<RedissonLockEntry> future = subscribe(threadId);
  commandExecutor.syncSubscription(future);
 
  try {
   while (true) {
    ttl = tryAcquire(leaseTime, unit, threadId);
    // lock acquired
    if (ttl == null) {
     break;
    }
 
    // waiting for message
    if (ttl >= 0) {
     getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
    } else {
     getEntry(threadId).getLatch().acquire();
    }
   }
  } finally {
   unsubscribe(future, threadId);
  }
//  get(lockAsync(leaseTime, unit));
 }

重点看下tryAcquire方法,把线程id作为一个参数传递进来,在这个方法里面,找到tryLockInnerAsync方法点进去,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
 internalLockLeaseTime = unit.toMillis(leaseTime);
 
 return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
    "if (redis.call('exists', KEYS[1]) == 0) then " +
     "redis.call('hset', KEYS[1], ARGV[2], 1); " +
     "redis.call('pexpire', KEYS[1], ARGV[1]); " +
     "return nil; " +
    "end; " +
    "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
     "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
     "redis.call('pexpire', KEYS[1], ARGV[1]); " +
     "return nil; " +
    "end; " +
    "return redis.call('pttl', KEYS[1]);",
    Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
}

这里就是一堆Lua脚本,先看第一个if命令,先去判断 KEYS[1](就是对应的锁key的名字),如果不存在,在hashmap里,设置一个属性为线程id,值为1,再把map的过期时间设置为internalLockLeaseTime,这个值默认是30秒,

上面的操作对应的命令是:

1
2
hset keyname id:thread 1
pexpire keyname 30

然后返回nil,相当于null,那程序return了。

另外,Redisson还支持重入锁,那第二个if就是执行重入锁的操作,会判断锁是否存在,并且传入的线程id是否是当前线程的id,若果是,支持重复加锁进行自增操作;

如果是其他线程调用lock方法,上面两个if判断不会走,会返回锁剩余过期时间。

接着返回到tryAcquireAsync方法里面往下看:

实际上是加了一个监听器,在监听器里面有个很重要的方法scheduleExpirationRenewal,一看这个名字就能大概猜出是什么功能,

里面有个定时任务的轮询,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
private void scheduleExpirationRenewal(final long threadId) {
  if (expirationRenewalMap.containsKey(getEntryName())) {
   return;
  }
 
  Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
   @Override
   public void run(Timeout timeout) throws Exception {
    // 判断传递进来的线程id是否是我们之前主线程设置的id,如果是,则增加续命,增加30秒。
    RFuture<Boolean> future = commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
      "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
       "redis.call('pexpire', KEYS[1], ARGV[1]); " +
       "return 1; " +
      "end; " +
      "return 0;",
       Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
     
    future.addListener(new FutureListener<Boolean>() {
     @Override
     public void operationComplete(Future<Boolean> future) throws Exception {
      expirationRenewalMap.remove(getEntryName());
      if (!future.isSuccess()) {
       log.error("Can't update lock " + getName() + " expiration", future.cause());
       return;
      }
       
      if (future.getNow()) {
       // reschedule itself
       scheduleExpirationRenewal(threadId);
      }
     }
    });
   }
  }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
 
  if (expirationRenewalMap.putIfAbsent(getEntryName(), task) != null) {
   task.cancel();
  }
 }

接着推迟10秒钟(internalLockLeaseTime / 3),再执行续命操作逻辑。

到最后,再回到lockInterruptibly方法,如果ttl 为null,说明加锁成功了,就返回null,那如果其他线程的话,就会返回剩余过期时间,那么就会进入到while死循环里,一直尝试加锁,调用tryAcquire方法,在琐失效以后,再会尝试获取加锁。

到此为止,分析完毕。

总结

到此这篇关于Redis分布式锁的使用和实现原理的文章就介绍到这了,更多相关Redis分布式锁的使用和原理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

 
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Redis分布式锁的使用和实现原理详解 的相关文章

  • 如何在实时添加对象时从 Redis 中弹出对象?

    我想让 Node js 进程运行 因为它正在检查 Redis 服务器是否有任何新的弹出内容 另一个进程将偶尔进行推送 而 Node 进程将尝试弹出任何进来的内容 Node 进程将保持运行 有人能给我指出一个好的方向吗 我正在尝试找出如何监听
  • Redis多插入问题

    我尝试多次插入 但它给了我错误 http pastie org 7337421 http pastie org 7337421 cat mass insert txt 3 r n 3 r nSET r n 3 r nkey r n 5 r
  • Node.js 上通过套接字连接 Redis

    由于共享托管 目标主机上的我的 redis 服务器不在端口上运行 而是在非常特定的套接字上运行 可以通过套接字文件连接到该套接字 只有我的用户可以访问 但是 我还没有找到如何通过套接字指定连接node redis and connect r
  • Redis 块推送直到列表有空位

    我正在寻找类似的东西BLPUSH该命令将阻塞 直到列表的长度低于指定值max size 目的是防止生产者运行速度快于消费者时列表无限增长 功能与 python 非常相似Queue put https docs python org 3 li
  • python 3.5 中的 json.loads 和 Redis

    我使用 json dumps 创建了一个 JSON 对象 并在 Redis 列表中将其 RPUSH ed 当使用 LRANGE redis lrange 返回 JSON 时 我收到一个二进制字符串 b si 00 ff 所以 json lo
  • 如何在多个Lua State(多线程)之间传递数据?

    我在中启动Redis连接池redis lua 通过从 C 调用 我得到了redis lua state 此 Lua 状态全局启动一次 仅在其他线程中启动get从中 当有一个 HTTP 请求 工作线程 时 我需要从redis lua stat
  • 如何使用Spring Cache处理redis异常?

    我目前正在开发一个包含 Spring Data Redis 和 Spring Cache 的项目 在spring data redis中 我使用redis模板调用redis 我在 try catch 块中处理 redis 模板抛出的所有异常
  • WSL Redis 遇到系统尚未使用 systemd 作为 init 系统(PID 1)启动。无法操作[已关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我正在尝试遵循本文中讨论的 Redis 安装过程article https www digitalocean com community
  • Docker-compose Predis 不通过 PHP 连接

    我正在尝试使用 docker compose 将 PHP 与 redis 连接 docker compose yml version 2 services redis image redis 3 2 2 php image company
  • Redis INCRBY 有限制

    我想知道是否有一种方法可以通过我的应用程序的单次往返在 Redis 中执行此操作 对于给定的键K 其可能值V是范围内的任意整数 A B 基本上 它有上限和下限 When an INCRBY or DECRBY发出命令 例如INCRBY ke
  • redis 阻塞直到 key 存在

    我是 Redis 新手 想知道是否有办法能够await get通过它的键来获取值 直到该键存在 最小代码 async def handler data await self fetch key async def fetch key ret
  • socket.io 广播功能 & Redis pub/sub 架构

    如果有人能帮助我解决一个小疑问 我将不胜感激 使用socket io广播功能和在Redis上使用pub sub设计架构有什么区别 例如 在另一个示例中 node js 服务器正在侦听 socket io 针对 键 模型 todo 和值 数据
  • Redis Cluster 与 Pub/Sub 中的 ZeroMQ,用于水平扩展的分布式系统

    如果我要设计一个巨大的分布式系统 其吞吐量应随系统中的订阅者数量和通道数量线性扩展 哪个会更好 1 Redis集群 仅适用于Redis 3 0 alpha 如果是集群模式 您可以在一个节点上发布并在另一个完全不同的节点上订阅 消息将传播并到
  • Redis、会话过期和反向查找

    我目前正在构建一个网络应用程序 并想使用 Redis 来存储会话 登录时 会话会使用相应的用户 ID 插入到 Redis 中 并且过期时间设置为 15 分钟 我现在想实现会话的反向查找 获取具有特定用户 ID 的会话 这里的问题是 由于我无
  • Spring Data JPA Redis:无法编写基于自定义方法的查询

    我已经使用 Redis 配置了 Spring Data JPA 并使用RedisRepositorieswith 提供了类似的方法find findAll 所有这些方法似乎都工作得很好 但我无法编写我的自定义方法 RedisEntity f
  • 有没有办法让特定的key在集群模式下定位到特定的redis实例上?

    我想让我的多锁位于不同的redis实例上 我发现redission可以指定一个实例来执行命令 但是如果该命令与key相关 则指定的实例会将命令传输到另一个实例 你能给我一些建议吗 你可以 但这并不是微不足道的 首先 Redis 在键中使用大
  • Redis是如何实现高吞吐量和高性能的?

    我知道这是一个非常普遍的问题 但是 我想了解允许 Redis 或 MemCached Cassandra 等缓存 以惊人的性能极限工作的主要架构决策是什么 如何维持连接 连接是 TCP 还是 HTTP 我知道它完全是用C写的 内存是如何管理
  • StackExchange.Redis的正确使用方法

    这个想法是使用更少的连接和更好的性能 连接会随时过期吗 对于另一个问题 redis GetDatabase 打开新连接 private static ConnectionMultiplexer redis private static ID
  • redis dump.rdb / 保存小文件

    Context 我正在使用redis 数据库小于 100 MB 但是 我想进行每日备份 我也在 Ubuntu Server 12 04 上运行 当输入 redis cli save 我不知道 dump rdb 保存到哪里 因为 redis
  • redis 2.8.7 Linux Sentinel环境配置问题,如何使其自启动,应该订阅什么?

    现在我们尝试使用 redis 2 8 7 作为缓存存储 来自使用 booksleeve 客户端的 NET Web 应用程序 目前看来这是一个非常有趣和令人兴奋的任务 redis 文档非常好 但由于缺乏真正的实践经验 我确实有几个关于如何正确

随机推荐

  • TrainingTesseract3

    Introduction http code google com p tesseract ocr wiki TrainingTesseract3 Tesseract 3 0x is fully trainable This page de
  • 基于大数据平台的毕业设计01:基于Docker的HDP集群搭建

    前言 好多人问我 这种基于大数据平台的xxxx的毕业设计要怎么做 这个可以参考之前写得关于我大数据毕业设计的文章基于大数据平台的毕业设计 这篇文章是将对之前的毕设进行优化 个人觉得可以分为两个部分 第一个部分就是基础的平台搭建 例如Hado
  • 写1篇关于运动负荷量化研究的学术论文

    我可以提供一篇关于运动负荷量化研究的学术论文 其中包括对运动负荷量化模型的系统研究 以及该模型如何用于优化运动训练计划的应用研究 文中提出了一种新的动态运动负荷量化方法 以最大限度地减少运动负荷 并可以按照个人的运动训练计划进行定制
  • 【C语言刷题】将一个十进制数字转化为二进制数字

    题目描述 将一个十进制的数字转化为二进制的数字 测试用例 输入 10 输出 1010 输入 9 输出 1001 思路 可以发现二进制位是满2进1 则可以通过 2来判断是否需要进位 依次作为循环终止条件 通过 2可以判断二进制的每一位对应的数
  • python 实现微秒级等待(windows)

    windows限制 python 的 time sleep 方法 在windows操作系统下 最低只能实现到0 001秒 即最少等待1毫秒 时间单位 秒 second 时间单位 s 毫秒 millisecond 时间单位 ms 微秒 mic
  • Python趣味代码整合之提升学生编程兴趣

    这篇文章主要是整合一些趣味代码 一方面自己对这些内容比较感兴趣 另一方面希望这些代码能提升学生的编程兴趣 其主旨是代码能在我的电脑上运行并有些趣味 参考资料 知乎 可以用 Python 编程语言做哪些神奇好玩的事情 知乎 学习Python的
  • Windows Server 2012 R2超级详细安装教程(附下载链接)

    一 百度网盘 链接 点我下载 注意 包内有安装教程和镜像 以下是主要步骤 1 打开安装好的虚拟机 2 两种类型随意选择 第一种 典型 比较简单 快速建立虚拟机 安装比自定义的步骤较少 3 选择虚拟机硬件兼容性 我们选择当前硬件兼容性 Wor
  • 服务器连接异常系统无法登录,win10系统无法登录LOL提示“服务器连接异常”的解决方法...

    很多小伙伴都遇到过win10系统无法登录LOL提示 服务器连接异常 的困惑吧 一些朋友看过网上零散的win10系统无法登录LOL提示 服务器连接异常 的处理方法 并没有完完全全明白win10系统无法登录LOL提示 服务器连接异常 是如何解决
  • 【C++11智能指针】unique_ptr概述、初始化、常用操作、返回unique_ptr、指定删除器、尺寸

    文章目录 1 unique ptr概述 2 unique ptr的初始化 2 1 直接初始化 2 2 make unique函数 3 unique ptr不支持拷贝构造和拷贝赋值 4 unique ptr支持移动构造和移动赋值 5 uniq
  • thanos配置promethes高可用

    参考文档 https www kubernetes org cn 7217 html prometheus高可用方案 prometheus官方的高可用有几种方案 HA 即两套 prometheus 采集完全一样的数据 外边挂负载均衡 HA
  • 从技术布局看,苹果离元宇宙还有多远?

    扎克伯格的Meta元宇宙令人津津乐道 从2021年10月28日更名之始 元宇宙 Metaverse 便开始在各大媒体及圈内人士间被不断谈论 但在此之外 元宇宙的另一个 实力选手 依旧赫然在列 扎克伯格一直把它看作是竞争对手 在一次内部的谈话
  • HarmonyOS开发:解决DevEco Studio低版本导入高版本项目运行失败问题

    前言 基于DevEco Studio 4 0 Beta2 hvigorVersion为3 0 2 开发了一个项目 上传到了远程仓库 当同事下载后 却始终无法运行 频繁报错 由于API都是使用的9 第一感觉就是开发环境不同 于是 让其发来了他
  • go 进阶 gin底层原理相关: 四. gin中间件底层原理

    目录 一 gin 中间件基础 二 中间件初始化流程 1 初始化中间件保存到RouterGroup的HandlersChain数组中 HandlersChain是什么 2 整合中间件函数与业务相关的mainHandler构建前缀树 三 中间件
  • Matlab:自定义绘图颜色

    Matlab 自定义绘图颜色 在 Matlab 中绘制图形时 我们可能需要使用自己指定的颜色来填充线条 散点或者其他图案 这可以让我们的图像更加美观和易读 下面介绍两种常见的设置自定义颜色的方法 使用 RGB 颜色值 RGB 颜色值是一种由
  • idea如何导入一个spring boot 项目

    1 菜单 gt File gt New gt Project From Existing Sources 2 选中项目中的pom xml 3 点击OK 然后后面就一路 Next 直到 finish就行了 需注意你的idea工具中项目jdk和
  • 面试必问 - AES 加密 和 RSA 加密是什么?它们有什么区别

    1 什么是 AES 加密 和 RSA 加密 AES Advanced Encryption Standard 高级加密标准 AES 是一种对称加密算法 即加密和解密使用相同的密钥 AES 的密钥长度可以选择 128 位 192 位或 256
  • Vue中通过localStorage存储信息并获取显示到页面中

    这两天写了一个日程表功能 包括日程表内容的增加 删除功能 解决办法一 可以在后端写接口 把日程表的内容写到数据库中 再通过接口从数据库中获取 通过后端的接口来对数据进行增删改查 解决办法二 这次我没想着做后端接口 因为是写在浏览器首页面中
  • 怎样优化Pentium系列处理器的代码 From:http://www.codingnow.com/2000/download/pentopt.htm#26_14

    How to optimize for the Pentium family of microprocessors Copyright 1996 2000 by Agner Fog Last modified 2000 07 03 Cont
  • Redis集群实现分布式Session共享

    Cookie 保存在客户端浏览器中 而 Session 保存在服务器上 客户端浏览器访问服务器的时候 服务器把客户端信息以某种形式记录在服务器上 这就是 Session 客户端浏览器再次访问时只需要从该 Session 中查找该客户的状态就
  • Redis分布式锁的使用和实现原理详解

    这篇文章主要给大家介绍了关于Redis分布式锁的使用和实现原理的相关资料 文中通过示例代码介绍的非常详细 对大家的学习或者工作具有一定的参考学习价值 需要的朋友们下面随着小编来一起学习学习吧 模拟一个电商里面下单减库存的场景 第一版代码 存