Redis(10)スモールケースの実際の戦闘(2回目のキル~~~~)
非分散環境でのスパイク、分散された高い同時実行性の場合に問題が発生します
<packaging>war</packaging>
<properties>
<!--配置运行环境-->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<java.version>1.11</java.version>
<maven.compiler.source>1.11</maven.compiler.source>
<maven.compiler.target>1.11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<!--实现分布式锁的工具类-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.6.1</version>
</dependency>
<!--spring操作redis的工具类-->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>2.3.2.RELEASE</version>
</dependency>
<!--redis客户端-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.1.0</version>
</dependency>
<!--json解析工具-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>
</dependencies>
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/spring.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd">
<!--包注解扫描-->
<context:component-scan base-package="com.szx.controller"/>
<!--spring为连接Redis提供的模板工具类-->
<bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
<property name="connectionFactory" ref="connectionFactory"></property>
</bean>
<!--Redis连接工厂-->
<bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="hostName" value="106.75.245.83"></property>
<property name="port" value="6379"/>
</bean>
</beans>
@Controller
public class TestKill {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@RequestMapping("/kill")
@ResponseBody
//synchronized:此处锁,只能解决一个Tomcat的并发问题
//synchronized:锁的是一个进程下的线程并发,如果分布式环境,多个线程并发,这种方案就失效了
public synchronized String kill(){
//在Redis中获取手机的库存量
int phoneCount = Integer.valueOf(stringRedisTemplate.opsForValue().get("phone"));
//判断手机数量是否足够秒杀
if (phoneCount > 0){
//减少库存
phoneCount--;
//将减少后的值放回Redis
stringRedisTemplate.opsForValue().set("phone",phoneCount + ""); //此处为了将int转换为String(phoneCount + "")
System.out.println("库存 -1,剩余库存:" + phoneCount);
} else {
System.out.println("库存不足!!!");
}
return "over!";
}
}
分散ロックを実装するためのアイデア
-
redisはシングルスレッドであるため、コマンドもアトミックです。setnxコマンドを使用して、ロックを実現し、kvを保存します。
- kが存在しない場合は、保存(現在のスレッドロック)し、実行が完了したら、kを削除してロックを解除します
- kがすでに存在する場合は、スレッドの実行をブロックし、ロックがあることを示します
-
ロックが成功すると、ビジネスコードの実行中に例外が発生し、kが削除されず(ロックの解放に失敗)、デッドロックが発生します(後続のすべてのスレッドを実行できません)。
- 有効期限を設定します。たとえば、10秒後、redisは自動的に削除します
-
同時実行性が高い場合、期間などの要因によりサーバーのプレッシャーが大きすぎたり小さすぎたりし、各スレッドの実行時間が異なります。
- 最初のスレッドの実行には13秒かかります。実行が10秒に達すると、redisは自動的に期限切れになりますk(ロックの解除)
- 2番目のスレッドは実行に7秒かかり、ロックして3番目の秒を実行します(ロックが解放されたのは、なぜ、最初のスレッドのfinally deleteKeyによって解放されたのですか)[最初のスレッドが遅すぎて解放されたのはロックです2番目のスレッドの]
- 連鎖反応、現在のスレッドによって追加されたばかりのロックが他のスレッドによって解放され、サイクルが繰り返されて、永続的なロック障害が発生します
-
一意の識別子UUIDを各スレッドに追加してランダムに生成し、リリースされたときにそれが現在の識別子であるかどうかを判断します
-
問題は、有効期限が設定されているかどうかです。
- 10秒が短すぎる場合はどうなりますか?
- 60秒に設定すると、長すぎて時間が無駄になります
- タイマースレッドを開始できます。有効期限が合計有効期限の1/3未満の場合は、合計有効期限を増やします(エリクサーを食べて人生を続けてください!)
分散ロックを自分で実装するのは難しすぎます!
Redisson
Redisは最も人気のあるNoSQLデータベースソリューションの1つであり、Javaは世界で最も人気のある(「最高の」とは言わなかった)プログラミング言語の1つです。
この2つは自然に「連携」しているように見えますが、Redisは実際にはJavaのネイティブサポートを提供していないことを知っておく必要があります。
それどころか、Java開発者として、Redisをプログラムに統合する場合は、Redisサードパーティライブラリを使用する必要があります。
また、RedissonはJavaプログラムでRedisを操作するためのライブラリであり、プログラムでRedisを簡単に使用できます。
Redissonは、java.utilの共通インターフェースに基づく分散特性を備えた一連のツールクラスを提供します。
@Controller
public class TestKill {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private Redisson redisson;
@RequestMapping("/kill")
@ResponseBody
//synchronized:此处锁,只能解决一个Tomcat的并发问题
//synchronized:锁的是一个进程下的多线程并发,如果分布式环境,多个线程并发,这种方案就失效了
public synchronized String kill(){
//实现分布式锁
//定义商品ID
String prouductKey = "HUAWEI-P40";
//通过Redisson获取锁
RLock lock = redisson.getLock(prouductKey); //底层源码就是集成了setnx过期时间等操作
//加锁
//过期时间为 30 秒
lock.lock(30, TimeUnit.SECONDS);
try {
//在Redis中获取手机的库存量
int phoneCount = Integer.valueOf(stringRedisTemplate.opsForValue().get("phone"));
//判断手机数量是否足够秒杀
if (phoneCount > 0){
//减少库存
phoneCount--;
//将减少后的值放回Redis
stringRedisTemplate.opsForValue().set("phone",phoneCount + ""); //此处为了将int转换为String(phoneCount + "")
System.out.println("库存 -1,剩余库存:" + phoneCount);
} else {
System.out.println("库存不足!!!");
}
} catch (NumberFormatException e) {
e.printStackTrace();
} finally {
//释放锁
lock.unlock();
}
return "over!";
}
@Bean
public Redisson redisson(){
Config config = new Config();
//使用单例服务器
config.useSingleServer().setAddress("redis://106.75.245.83:6379").setDatabase(0);
//使用集群服务器
//setScanInterval:心跳时间
//addNodeAddress:节点地址
//config.useClusterServers().setScanInterval(2000).addNodeAddress("","","");
return (Redisson) Redisson.create(config);
}
}
分散ロックを実装するためのソリューションは実際にはたくさんあります。以前使用した動物園の飼育係は信頼性が高いという特徴があり、現在使用しているRedisは高性能が特徴です。
現在、最も広く使用されている分散ロックは「Redis」です。