一、主配置类和Redis配置类

(1)主配置类

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class BootRedis01Application {public static void main(String[] args) {SpringApplication.run(BootRedis01Application.class);}
}

(2)Redis配置类

@Configuration
public class RedisConfig {@Beanpublic RedisTemplate<String, Serializable> redisTemplate(LettuceConnectionFactory connectionFactory){// 新建 RedisTemplate 对象,key 为 String 对象,value 为 Serializable(可序列化的)对象RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<>();// key 值使用字符串序列化器redisTemplate.setKeySerializer(new StringRedisSerializer());// value 值使用 json 序列化器redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());// 传入连接工厂redisTemplate.setConnectionFactory(connectionFactory);// 返回 redisTemplate 对象return redisTemplate;}
}

二、controller业务类递进过程

1. 单机版

1.1 单机1.0版

@RestController
public class GoodController {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${server.port}")private String serverPort;@GetMapping("/buy_goods")public String buy_Goods() {// 从 redis 中获取商品的剩余数量String result = stringRedisTemplate.opsForValue().get("goods:001");int goodsNumber = result == null ? 0 : Integer.parseInt(result);String retStr = null;// 商品数量大于零才能出售if (goodsNumber > 0) {int realNumber = goodsNumber - 1;stringRedisTemplate.opsForValue().set("goods:001", realNumber + "");retStr = "成功秒杀商品,此时还剩余:" + realNumber + "件" + "\t 服务器端口: " + serverPort;} else {retStr = "商品已经售罄/活动结束/调用超时" + "\t 服务器端口: " + serverPort;}System.out.println(retStr);return retStr;}
}

(1)问题

单机版没有加锁,并发下会出现超卖问题

(2)解决

加锁!但是,加到锁是Synchronized还是ReentrantLock呢?
(a)Synchronized:不见不散,其他线程等不到锁就会死等,会造成积压
(b)ReentrantLock:过时不候,需手动获取锁,为避免死锁 ,在finally释放锁
①trylock()
②trylock(long time,TimeUnit unit):可设置抢锁时间,规定时间内抢不到锁就放弃

1.2 单机2.0版

@RestController
public class GoodController {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${server.port}")private String serverPort;@GetMapping("/buy_goods")public String buy_Goods() {synchronized (this) {// 从 redis 中获取商品的剩余数量String result = stringRedisTemplate.opsForValue().get("goods:001");int goodsNumber = result == null ? 0 : Integer.parseInt(result);String retStr = null;// 商品数量大于零才能出售if (goodsNumber > 0) {int realNumber = goodsNumber - 1;stringRedisTemplate.opsForValue().set("goods:001", realNumber + "");retStr = "成功秒杀商品,此时还剩余:" + realNumber + "件" + "\t 服务器端口: " + serverPort;} else {retStr = "商品已经售罄/活动结束/调用超时" + "\t 服务器端口: " + serverPort;}System.out.println(retStr);return retStr;}}
}

(1)问题

在分布式情况下,竞争的线程可能不在同一节点上,所以需要一个所有进程都可以访问到的节点来进行加锁,如Redis或Zookeeper。

2 分布式

2.1 Redis分布式锁3.0版

value=当前请求的UUID+线程名称。

@RestController
public class GoodController {private static final String REDIS_LOCK_KEY = "lockOneby";@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${server.port}")private String serverPort;@GetMapping("/buy_goods")public String buy_Goods() {// 当前请求的 UUID + 线程名String value = UUID.randomUUID().toString()+Thread.currentThread().getName();// setIfAbsent() 就相当于 setnx,如果不存在就新建锁Boolean lockFlag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK_KEY, value);// 抢锁失败if(lockFlag == false){return "抢锁失败";}// 从 redis 中获取商品的剩余数量String result = stringRedisTemplate.opsForValue().get("goods:001");int goodsNumber = result == null ? 0 : Integer.parseInt(result);String retStr = null;// 商品数量大于零才能出售if (goodsNumber > 0) {int realNumber = goodsNumber - 1;stringRedisTemplate.opsForValue().set("goods:001", realNumber + "");retStr = "成功秒杀商品,此时还剩余:" + realNumber + "件" + "\t 服务器端口: " + serverPort;} else {retStr = "商品已经售罄/活动结束/调用超时" + "\t 服务器端口: " + serverPort;}System.out.println(retStr);stringRedisTemplate.delete(REDIS_LOCK_KEY); // 释放分布式锁return retStr;}
}

(1)问题

上述代码在执行时,可能会出现无法释放锁的情况。

2.2 保证锁释放4.0版

@RestController
public class GoodController {private static final String REDIS_LOCK_KEY = "lockOneby";@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${server.port}")private String serverPort;@GetMapping("/buy_goods")public String buy_Goods() {// 当前请求的 UUID + 线程名String value = UUID.randomUUID().toString() + Thread.currentThread().getName();try {// setIfAbsent() 就相当于 setnx,如果不存在就新建锁Boolean lockFlag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK_KEY, value);// 抢锁失败if (lockFlag == false) {return "抢锁失败";}// 从 redis 中获取商品的剩余数量String result = stringRedisTemplate.opsForValue().get("goods:001");int goodsNumber = result == null ? 0 : Integer.parseInt(result);String retStr = null;// 商品数量大于零才能出售if (goodsNumber > 0) {int realNumber = goodsNumber - 1;stringRedisTemplate.opsForValue().set("goods:001", realNumber + "");retStr = "成功秒杀商品,此时还剩余:" + realNumber + "件" + "\t 服务器端口: " + serverPort;} else {retStr = "商品已经售罄/活动结束/调用超时" + "\t 服务器端口: " + serverPort;}System.out.println(retStr);return retStr;} finally {stringRedisTemplate.delete(REDIS_LOCK_KEY); // 释放分布式锁}}
}

(1)问题

假设部署了微服务jar包的服务器挂了,代码层面没有走到finally部分,也就无法释放锁,导致锁的key删除不了,这样其他微服务无法抢到锁。

(2)解决

设置一个过期时间

2.3 设置锁的过期时间5.0版

// 设置过期时间为 10s
stringRedisTemplate.expire(REDIS_LOCK_KEY, 10L, TimeUnit.SECONDS);

完整代码如下:

@RestController
public class GoodController {private static final String REDIS_LOCK_KEY = "lockOneby";@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${server.port}")private String serverPort;@GetMapping("/buy_goods")public String buy_Goods() {// 当前请求的 UUID + 线程名String value = UUID.randomUUID().toString() + Thread.currentThread().getName();try {// setIfAbsent() 就相当于 setnx,如果不存在就新建锁Boolean lockFlag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK_KEY, value);// 设置过期时间为 10sstringRedisTemplate.expire(REDIS_LOCK_KEY, 10L, TimeUnit.SECONDS);// 抢锁失败if (lockFlag == false) {return "抢锁失败";}// 从 redis 中获取商品的剩余数量String result = stringRedisTemplate.opsForValue().get("goods:001");int goodsNumber = result == null ? 0 : Integer.parseInt(result);String retStr = null;// 商品数量大于零才能出售if (goodsNumber > 0) {int realNumber = goodsNumber - 1;stringRedisTemplate.opsForValue().set("goods:001", realNumber + "");retStr = "成功秒杀商品,此时还剩余:" + realNumber + "件" + "\t 服务器端口: " + serverPort;} else {retStr = "商品已经售罄/活动结束/调用超时" + "\t 服务器端口: " + serverPort;}System.out.println(retStr);return retStr;} finally {stringRedisTemplate.delete(REDIS_LOCK_KEY); // 释放分布式锁}}
}

(1)问题

加锁操作与设置过期时间为两行代码,如果服务器刚执行加锁操作就宕机了,那么锁也可能释放不了。

2.4 保证加锁和设置过期时间为原子操作6.0版

//该方法在加锁的同时设置过期时间,保证了原子性
stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK_KEY, value, 10L, TimeUnit.SECONDS)

(1)问题

张冠李戴,删除了别人的锁。
因为无法保证一个业务的执行时间,如果当前业务还在执行,但是锁过期了,那么久有可能会出现超卖的情况,并且可能会导致其他业务进来执行,没有执行完,而锁被释放的情况。如下图:

2.5 只允许删自己的锁7.0版

//释放锁之前,判断是否为自己的锁
value.equalsIgnoreCase(stringRedisTemplate.opsForValue().get(REDIS_LOCK_KEY))

完整代码:

@RestController
public class GoodController {private static final String REDIS_LOCK_KEY = "lockOneby";@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${server.port}")private String serverPort;@GetMapping("/buy_goods")public String buy_Goods() {// 当前请求的 UUID + 线程名String value = UUID.randomUUID().toString() + Thread.currentThread().getName();try {// setIfAbsent() 就相当于 setnx,如果不存在就新建锁,同时加上过期时间保证原子性Boolean lockFlag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK_KEY, value, 10L, TimeUnit.SECONDS);// 抢锁失败if (lockFlag == false) {return "抢锁失败";}// 从 redis 中获取商品的剩余数量String result = stringRedisTemplate.opsForValue().get("goods:001");int goodsNumber = result == null ? 0 : Integer.parseInt(result);String retStr = null;// 商品数量大于零才能出售if (goodsNumber > 0) {int realNumber = goodsNumber - 1;stringRedisTemplate.opsForValue().set("goods:001", realNumber + "");retStr = "成功秒杀商品,此时还剩余:" + realNumber + "件" + "\t 服务器端口: " + serverPort;} else {retStr = "商品已经售罄/活动结束/调用超时" + "\t 服务器端口: " + serverPort;}System.out.println(retStr);return retStr;} finally {// 判断是否是自己加的锁if(value.equalsIgnoreCase(stringRedisTemplate.opsForValue().get(REDIS_LOCK_KEY))){stringRedisTemplate.delete(REDIS_LOCK_KEY); // 释放分布式锁}}}
}

(1)问题

在finally代码块中的判断与删除并不是原子操作,假设判断if时,还是当前业务获得的锁,但是可能在执行完if之后,这把锁就被其他执行操作给释放了,出现了误删锁的情况。

2.6 用redis自身事务保证原子性操作8.1版

(1)Redis事务命令

  • MULTI :用于标记事务块的开始,Redis会将后续的命令逐个放入队列中,然后使用EXEC命令原子化地执行这个命令序列。
  • EXEC:在一个事务中执行所有先前放入队列的命令,然后恢复正常的连接状态。
  • DISCARD:清除所有先前在一个事务中放入队列的命令,然后恢复正常的连接状态。
  • WATCH:当某个事务需要按条件执行时,就要使用这个命令将给定的键设置为受监控的状态。WATCH key[key……],该命令可以实现redis的乐观锁。
  • UNWATCH:清除所有先前为一个事务监控的键。

(2)完整代码

开启事务,监视REDIS_LOCK_KEY,如果被修改过,就重新执行删除操作,否则解除监视。

@RestController
public class GoodController {private static final String REDIS_LOCK_KEY = "lockOneby";@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${server.port}")private String serverPort;@GetMapping("/buy_goods")public String buy_Goods() {// 当前请求的 UUID + 线程名String value = UUID.randomUUID().toString() + Thread.currentThread().getName();try {// setIfAbsent() 就相当于 setnx,如果不存在就新建锁,同时加上过期时间保证原子性Boolean lockFlag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK_KEY, value, 10L, TimeUnit.SECONDS);// 抢锁失败if (lockFlag == false) {return "抢锁失败";}// 从 redis 中获取商品的剩余数量String result = stringRedisTemplate.opsForValue().get("goods:001");int goodsNumber = result == null ? 0 : Integer.parseInt(result);String retStr = null;// 商品数量大于零才能出售if (goodsNumber > 0) {int realNumber = goodsNumber - 1;stringRedisTemplate.opsForValue().set("goods:001", realNumber + "");retStr = "成功秒杀商品,此时还剩余:" + realNumber + "件" + "\t 服务器端口: " + serverPort;} else {retStr = "商品已经售罄/活动结束/调用超时" + "\t 服务器端口: " + serverPort;}System.out.println(retStr);return retStr;} finally {while (true) {//加事务,乐观锁stringRedisTemplate.watch(REDIS_LOCK_KEY);// 判断是否是自己加的锁if (value.equalsIgnoreCase(stringRedisTemplate.opsForValue().get(REDIS_LOCK_KEY))) {// 开启事务stringRedisTemplate.setEnableTransactionSupport(true);stringRedisTemplate.multi();stringRedisTemplate.delete(REDIS_LOCK_KEY);// 判断事务是否执行成功,如果等于 null,就是没有删掉,删除失败,再回去 while 循环那再重新执行删除List<Object> list = stringRedisTemplate.exec();if (list == null) {continue;}}//如果删除成功,释放监控器,并且 break 跳出当前循环stringRedisTemplate.unwatch();break;}}}
}

2.7 使用Lua脚本保证原子性操作8.2版

lua脚本
Redis可以通过eval命令保证执行的原子性。

(1)RedisUtils工具类

public class RedisUtils {private static JedisPool jedisPool;private static String hostAddr = "192.168.152.233";static {JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();jedisPoolConfig.setMaxTotal(20);jedisPoolConfig.setMaxIdle(10);jedisPool = new JedisPool(jedisPoolConfig, hostAddr, 6379, 100000);}public static Jedis getJedis() throws Exception {if (null != jedisPool) {return jedisPool.getResource();}throw new Exception("Jedispool is not ok");}
}

(2)完整代码

@RestController
public class GoodController {private static final String REDIS_LOCK_KEY = "lockOneby";@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${server.port}")private String serverPort;@GetMapping("/buy_goods")public String buy_Goods() throws Exception {// 当前请求的 UUID + 线程名String value = UUID.randomUUID().toString() + Thread.currentThread().getName();try {// setIfAbsent() 就相当于 setnx,如果不存在就新建锁,同时加上过期时间保证原子性Boolean lockFlag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK_KEY, value, 10L, TimeUnit.SECONDS);// 抢锁失败if (lockFlag == false) {return "抢锁失败";}// 从 redis 中获取商品的剩余数量String result = stringRedisTemplate.opsForValue().get("goods:001");int goodsNumber = result == null ? 0 : Integer.parseInt(result);String retStr = null;// 商品数量大于零才能出售if (goodsNumber > 0) {int realNumber = goodsNumber - 1;stringRedisTemplate.opsForValue().set("goods:001", realNumber + "");retStr = "成功秒杀商品,此时还剩余:" + realNumber + "件" + "\t 服务器端口: " + serverPort;} else {retStr = "商品已经售罄/活动结束/调用超时" + "\t 服务器端口: " + serverPort;}System.out.println(retStr);return retStr;} finally {// 获取连接对象Jedis jedis = RedisUtils.getJedis();// lua 脚本,摘自官网String script = "if redis.call('get', KEYS[1]) == ARGV[1]" + "then "+ "return redis.call('del', KEYS[1])" + "else " + "  return 0 " + "end";try {// 执行 lua 脚本Object result = jedis.eval(script, Collections.singletonList(REDIS_LOCK_KEY), Collections.singletonList(value));// 获取 lua 脚本的执行结果if ("1".equals(result.toString())) {System.out.println("------del REDIS_LOCK_KEY success");} else {System.out.println("------del REDIS_LOCK_KEY error");}} finally {// 关闭链接if (null != jedis) {jedis.close();}}}}
}

(3)问题

无法保证业务执行时间,如果业务还在执行,但是锁过期了,就会出现误删其他锁或超卖的问题。

2.8 自动续期9.0版

(1)知识补充

  • Redis主从复制时采用异步复制,即执行完立即返回给用户,同时主从同步。但如果主节点没来得及将set进的数据同步给从节点就宕机了,就会导致主从不一致。那么,用Redisson来解决。
  • zookeeper保持强一致性,先主从同步,再将信息传回。不会导致主从不一致,但是性能也相应下降。

(2)使用Redisson实现自动续期

补充:redisson看门狗机制
官网解释
Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。默认情况下,看门狗的检查锁的超时时间是30秒钟,也可以通过修改Config.lockWatchdogTimeout来另行指定。

看门狗开启条件
我们可以看到,leaseTime != -1时,只执行tryLockInnerAsync方法,其它情况会执行下面的代码,而leaseTime 就是我们调用lock(10, TimeUnit.SECONDS);方法传入的时间参数。

由此可知:redisson如果只是用lock.lock();不传过期时间的话,会启动看门狗机制,传过期时间的话,就不会启动看门狗机制。

(a)注入Redisson对象

在Redis配置类中注入Redisson对象。

@Configuration
public class RedisConfig {@Value("${spring.redis.host}")private String redisHost;@Beanpublic RedisTemplate<String, Serializable> redisTemplate(LettuceConnectionFactory connectionFactory) {// 新建 RedisTemplate 对象,key 为 String 对象,value 为 Serializable(可序列化的)对象RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<>();// key 值使用字符串序列化器redisTemplate.setKeySerializer(new StringRedisSerializer());// value 值使用 json 序列化器redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());// 传入连接工厂redisTemplate.setConnectionFactory(connectionFactory);// 返回 redisTemplate 对象return redisTemplate;}@Beanpublic Redisson redisson() {Config config = new Config();config.useSingleServer().setAddress("redis://" + redisHost + ":6379").setDatabase(0);return (Redisson) Redisson.create(config);}
}
(b)使用redissonLock.lock()和redissonLock.unlock()
@RestController
public class GoodController {private static final String REDIS_LOCK_KEY = "lockOneby";@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${server.port}")private String serverPort;@Autowiredprivate Redisson redisson;@GetMapping("/buy_goods")public String buy_Goods() throws Exception {// 当前请求的 UUID + 线程名String value = UUID.randomUUID().toString() + Thread.currentThread().getName();// 获取锁RLock redissonLock = redisson.getLock(REDIS_LOCK_KEY);// 上锁redissonLock.lock();try {// 从 redis 中获取商品的剩余数量String result = stringRedisTemplate.opsForValue().get("goods:001");int goodsNumber = result == null ? 0 : Integer.parseInt(result);String retStr = null;// 商品数量大于零才能出售if (goodsNumber > 0) {int realNumber = goodsNumber - 1;stringRedisTemplate.opsForValue().set("goods:001", realNumber + "");retStr = "成功秒杀商品,此时还剩余:" + realNumber + "件" + "\t 服务器端口: " + serverPort;} else {retStr = "商品已经售罄/活动结束/调用超时" + "\t 服务器端口: " + serverPort;}System.out.println(retStr);return retStr;} finally {// 还在持有锁的状态,并且是当前线程持有的锁再解锁if (redissonLock.isLocked() && redissonLock.isHeldByCurrentThread()){redissonLock.unlock();}}}
}

Redis锁解决超卖问题相关推荐

  1. redis mysql 解决超卖_Redis 分布式锁解决超卖问题

    Redis 分布式锁解决超卖问题 1,Redis 事物介绍 1. Redis 事物是可以一次执行多个命令, 本质是一组命令的集合. 2. 一个事务中的所有命令都会序列化, 按顺序串行化的执行而不会被其 ...

  2. 07: redis分布式锁解决超卖问题

    07: redis分布式锁解决超卖问题 参考文章: (1)07: redis分布式锁解决超卖问题 (2)https://www.cnblogs.com/xiaonq/p/12328934.html 备 ...

  3. 使用Redis分布式锁处理并发,解决超卖问题

    使用Redis分布式锁处理并发,解决超卖问题 参考文章: (1)使用Redis分布式锁处理并发,解决超卖问题 (2)https://www.cnblogs.com/VitoYi/p/8726070.h ...

  4. mysql 超卖_mysql 解决超卖问题的锁分析

    解决超卖问题,常见的方式,利用redis 的原子性去递减:利用队列,队列入队计数.或者直接打到mysql 层.由mysql 保证不超卖,有几个玩法.利用属性不一样,挺有意思,记录下. 首先,mysql ...

  5. 秒杀系统优化以及解决超卖问题

    问题描述 在众多抢购活动中,在有限的商品数量的限制下如何保证抢购到商品的用户数不能大于商品数量,也就是不能出现超卖的问题:还有就是抢购时会出现大量用户的访问,如何提高用户体验效果也是一个问题,也就是要 ...

  6. Redis锁解决高并发问题

    Redis锁解决高并发问题 redis真的是一个很好的技术,它可以很好的在一定程度上解决网站一瞬间的并发量,例如商品抢购秒杀等活动. redis之所以能解决高并发的原因是它可以直接访问内存,而以往我们 ...

  7. 秒杀系统并发情况下解决超卖问题

    非分布式秒杀系统 并发情况下解决超卖问题 乐观锁防止超卖 / 令牌桶限流/ redis缓存 /接口限流/接口加盐/单用户限制访问频率/消息队列异步处理订单 #数据库表drop table if exi ...

  8. 淘宝如何解决超卖问题

    这篇文章是我从某文库爬下来的,放在这里供大家学习. 淘宝超卖现象的产生及解决方案 一.什么是超卖现象?  超卖即"超卖缺货",当宝贝库存接近0时,如果多个买家同时付款购买此宝贝,将 ...

  9. 秒杀如何解决超卖和重复购买问题

    举一个简单的例子,一个商品限购10人 使用jmeter测试 发现是乱的....当然了 我们可以通过 synchronized 来解决 的确是可以的,但是这样真的太慢了.不建议这么去弄. ======= ...

  10. MyBatis-Plus 乐观锁 防止超卖、逻辑删除、自动填充、Id自增

    MyBatis-Plus 乐观锁 防止超卖.逻辑删除.自动填充 Day3 前面的简单的讲了一下mybatis-plus的使用 当然有很多不足 我写博客就是想促进大家一起学习 也想让这些内容更简单一些. ...

最新文章

  1. WeChat判断是否为微信浏览器访问方法:HTTP_USER_AGENT:MicroMessenger
  2. 第五节:EF Core中的三类事务(SaveChanges、DbContextTransaction、TransactionScope)
  3. HowToDoInJava Java 教程·翻译完成
  4. Apache Ranger——Hadoop ACL控制工具
  5. Codeforces Round #661-C Boats Competition
  6. 微信messageutil.java_java微信公众平台开发回复文本消息
  7. windows下Apache 启动出错的解决
  8. Java实时读取日志文件
  9. 地表温度数据、LST温度数据、地表反照率、NDVI数据、NPP数据、植被覆盖度、土地利用数据
  10. Android Studio酷炫插件(一)——自动化快速实现Parcelable接口序列化
  11. 2022亚太杯数学建模比赛准备
  12. 十天学会单片机和c语言编程,十天学会单片机和C语言编程
  13. YUV格式视频流文件下载
  14. ESP8266开发之旅 网络篇⑯ 无线更新——OTA固件更新
  15. 禁用 SQL 游标,告诉你外面听不到的原因【内含福利】
  16. 计算机中职作文,中职作文题目
  17. matlab 直接馈通,s函数直接馈通
  18. 让杂牌蓝牙适配器也安装上windows 自带的驱动程序
  19. 首月流水3亿的暑期档黑马,被外挂所困扰
  20. java ebcdic_java EBCDIC | 学步园

热门文章

  1. c语言中windows.h是什么意思,c语言中memory.h有什么作用
  2. 计算机WIN7动态硬盘分区,如何创建Win7虚拟磁盘分区
  3. Java8中计算时间的四种方式及区别Period、Duration、ChronoUnit、Until 时间区间Duration的简单使用
  4. Java Duration类和Period类
  5. 云服务器系统么开始bios,云服务器怎么进入bios
  6. 计算机应用高级教程,大学计算机应用高级教程(第3版)简介,目录书摘
  7. pythonarray去除inf_python 中numpy快速去除nan, inf的方法
  8. 一步一步排查真实拍图片不能上传的问题
  9. echarts中如何设置geo3D地图背景图片,以及geo3D中如何使用effectScatter属性
  10. Pyhton零基础投喂(综合练习:2:论⽂作者统计)