You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

README.md 13 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. # spring-boot-demo-zookeeper
  2. > 此 demo 主要演示了如何使用 Spring Boot 集成 Zookeeper 结合AOP实现分布式锁。
  3. ## pom.xml
  4. ```xml
  5. <?xml version="1.0" encoding="UTF-8"?>
  6. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  7. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  8. <modelVersion>4.0.0</modelVersion>
  9. <artifactId>spring-boot-demo-zookeeper</artifactId>
  10. <version>1.0.0-SNAPSHOT</version>
  11. <packaging>jar</packaging>
  12. <name>spring-boot-demo-zookeeper</name>
  13. <description>Demo project for Spring Boot</description>
  14. <parent>
  15. <groupId>com.xkcoding</groupId>
  16. <artifactId>spring-boot-demo</artifactId>
  17. <version>1.0.0-SNAPSHOT</version>
  18. </parent>
  19. <properties>
  20. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  21. <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  22. <java.version>1.8</java.version>
  23. </properties>
  24. <dependencies>
  25. <dependency>
  26. <groupId>org.springframework.boot</groupId>
  27. <artifactId>spring-boot-starter-web</artifactId>
  28. </dependency>
  29. <dependency>
  30. <groupId>org.springframework.boot</groupId>
  31. <artifactId>spring-boot-configuration-processor</artifactId>
  32. <optional>true</optional>
  33. </dependency>
  34. <dependency>
  35. <groupId>org.springframework.boot</groupId>
  36. <artifactId>spring-boot-starter-aop</artifactId>
  37. </dependency>
  38. <!-- curator 版本4.1.0 对应 zookeeper 版本 3.5.x -->
  39. <!-- curator 与 zookeeper 版本对应关系:https://curator.apache.org/zk-compatibility.html -->
  40. <dependency>
  41. <groupId>org.apache.curator</groupId>
  42. <artifactId>curator-recipes</artifactId>
  43. <version>4.1.0</version>
  44. </dependency>
  45. <dependency>
  46. <groupId>cn.hutool</groupId>
  47. <artifactId>hutool-all</artifactId>
  48. </dependency>
  49. <dependency>
  50. <groupId>org.springframework.boot</groupId>
  51. <artifactId>spring-boot-starter-test</artifactId>
  52. <scope>test</scope>
  53. </dependency>
  54. <dependency>
  55. <groupId>org.projectlombok</groupId>
  56. <artifactId>lombok</artifactId>
  57. <optional>true</optional>
  58. </dependency>
  59. </dependencies>
  60. <build>
  61. <finalName>spring-boot-demo-zookeeper</finalName>
  62. <plugins>
  63. <plugin>
  64. <groupId>org.springframework.boot</groupId>
  65. <artifactId>spring-boot-maven-plugin</artifactId>
  66. </plugin>
  67. </plugins>
  68. </build>
  69. </project>
  70. ```
  71. ## ZkProps.java
  72. ```java
  73. /**
  74. * <p>
  75. * Zookeeper 配置项
  76. * </p>
  77. *
  78. * @package: com.xkcoding.zookeeper.config.props
  79. * @description: Zookeeper 配置项
  80. * @author: yangkai.shen
  81. * @date: Created in 2018-12-27 14:47
  82. * @copyright: Copyright (c) 2018
  83. * @version: V1.0
  84. * @modified: yangkai.shen
  85. */
  86. @Data
  87. @ConfigurationProperties(prefix = "zk")
  88. public class ZkProps {
  89. /**
  90. * 连接地址
  91. */
  92. private String url;
  93. /**
  94. * 超时时间(毫秒),默认1000
  95. */
  96. private int timeout = 1000;
  97. /**
  98. * 重试次数,默认3
  99. */
  100. private int retry = 3;
  101. }
  102. ```
  103. ## application.yml
  104. ```yaml
  105. server:
  106. port: 8080
  107. servlet:
  108. context-path: /demo
  109. zk:
  110. url: 127.0.0.1:2181
  111. timeout: 1000
  112. retry: 3
  113. ```
  114. ## ZkConfig.java
  115. ```java
  116. /**
  117. * <p>
  118. * Zookeeper配置类
  119. * </p>
  120. *
  121. * @package: com.xkcoding.zookeeper.config
  122. * @description: Zookeeper配置类
  123. * @author: yangkai.shen
  124. * @date: Created in 2018-12-27 14:45
  125. * @copyright: Copyright (c) 2018
  126. * @version: V1.0
  127. * @modified: yangkai.shen
  128. */
  129. @Configuration
  130. @EnableConfigurationProperties(ZkProps.class)
  131. public class ZkConfig {
  132. private final ZkProps zkProps;
  133. @Autowired
  134. public ZkConfig(ZkProps zkProps) {
  135. this.zkProps = zkProps;
  136. }
  137. @Bean
  138. public CuratorFramework curatorFramework() {
  139. RetryPolicy retryPolicy = new ExponentialBackoffRetry(zkProps.getTimeout(), zkProps.getRetry());
  140. CuratorFramework client = CuratorFrameworkFactory.newClient(zkProps.getUrl(), retryPolicy);
  141. client.start();
  142. return client;
  143. }
  144. }
  145. ```
  146. ## ZooLock.java
  147. > 分布式锁的关键注解
  148. ```java
  149. /**
  150. * <p>
  151. * 基于Zookeeper的分布式锁注解
  152. * 在需要加锁的方法上打上该注解后,AOP会帮助你统一管理这个方法的锁
  153. * </p>
  154. *
  155. * @package: com.xkcoding.zookeeper.annotation
  156. * @description: 基于Zookeeper的分布式锁注解,在需要加锁的方法上打上该注解后,AOP会帮助你统一管理这个方法的锁
  157. * @author: yangkai.shen
  158. * @date: Created in 2018-12-27 14:11
  159. * @copyright: Copyright (c) 2018
  160. * @version: V1.0
  161. * @modified: yangkai.shen
  162. */
  163. @Target({ElementType.METHOD})
  164. @Retention(RetentionPolicy.RUNTIME)
  165. @Documented
  166. @Inherited
  167. public @interface ZooLock {
  168. /**
  169. * 分布式锁的键
  170. */
  171. String key();
  172. /**
  173. * 锁释放时间,默认五秒
  174. */
  175. long timeout() default 5 * 1000;
  176. /**
  177. * 时间格式,默认:毫秒
  178. */
  179. TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
  180. }
  181. ```
  182. ## LockKeyParam.java
  183. > 分布式锁动态key的关键注解
  184. ```java
  185. /**
  186. * <p>
  187. * 分布式锁动态key注解,配置之后key的值会动态获取参数内容
  188. * </p>
  189. *
  190. * @package: com.xkcoding.zookeeper.annotation
  191. * @description: 分布式锁动态key注解,配置之后key的值会动态获取参数内容
  192. * @author: yangkai.shen
  193. * @date: Created in 2018-12-27 14:17
  194. * @copyright: Copyright (c) 2018
  195. * @version: V1.0
  196. * @modified: yangkai.shen
  197. */
  198. @Target({ElementType.PARAMETER})
  199. @Retention(RetentionPolicy.RUNTIME)
  200. @Documented
  201. @Inherited
  202. public @interface LockKeyParam {
  203. /**
  204. * 如果动态key在user对象中,那么就需要设置fields的值为user对象中的属性名可以为多个,基本类型则不需要设置该值
  205. * <p>例1:public void count(@LockKeyParam({"id"}) User user)
  206. * <p>例2:public void count(@LockKeyParam({"id","userName"}) User user)
  207. * <p>例3:public void count(@LockKeyParam String userId)
  208. */
  209. String[] fields() default {};
  210. }
  211. ```
  212. ## ZooLockAspect.java
  213. > 分布式锁的关键部分
  214. ```java
  215. /**
  216. * <p>
  217. * 使用 aop 切面记录请求日志信息
  218. * </p>
  219. *
  220. * @package: com.xkcoding.log.aop.aspectj
  221. * @description: 使用 aop 切面记录请求日志信息
  222. * @author: yangkai.shen
  223. * @date: Created in 2018/10/1 10:05 PM
  224. * @copyright: Copyright (c) 2018
  225. * @version: V1.0
  226. * @modified: yangkai.shen
  227. */
  228. @Aspect
  229. @Component
  230. @Slf4j
  231. public class ZooLockAspect {
  232. private final CuratorFramework zkClient;
  233. private static final String KEY_PREFIX = "DISTRIBUTED_LOCK_";
  234. private static final String KEY_SEPARATOR = "/";
  235. @Autowired
  236. public ZooLockAspect(CuratorFramework zkClient) {
  237. this.zkClient = zkClient;
  238. }
  239. /**
  240. * 切入点
  241. */
  242. @Pointcut("@annotation(com.xkcoding.zookeeper.annotation.ZooLock)")
  243. public void doLock() {
  244. }
  245. /**
  246. * 环绕操作
  247. *
  248. * @param point 切入点
  249. * @return 原方法返回值
  250. * @throws Throwable 异常信息
  251. */
  252. @Around("doLock()")
  253. public Object around(ProceedingJoinPoint point) throws Throwable {
  254. MethodSignature signature = (MethodSignature) point.getSignature();
  255. Method method = signature.getMethod();
  256. Object[] args = point.getArgs();
  257. ZooLock zooLock = method.getAnnotation(ZooLock.class);
  258. if (StrUtil.isBlank(zooLock.key())) {
  259. throw new RuntimeException("分布式锁键不能为空");
  260. }
  261. String lockKey = buildLockKey(zooLock, method, args);
  262. InterProcessMutex lock = new InterProcessMutex(zkClient, lockKey);
  263. try {
  264. // 假设上锁成功,以后拿到的都是 false
  265. if (lock.acquire(zooLock.timeout(), zooLock.timeUnit())) {
  266. return point.proceed();
  267. } else {
  268. throw new RuntimeException("请勿重复提交");
  269. }
  270. } finally {
  271. lock.release();
  272. }
  273. }
  274. /**
  275. * 构造分布式锁的键
  276. *
  277. * @param lock 注解
  278. * @param method 注解标记的方法
  279. * @param args 方法上的参数
  280. * @return
  281. * @throws NoSuchFieldException
  282. * @throws IllegalAccessException
  283. */
  284. private String buildLockKey(ZooLock lock, Method method, Object[] args) throws NoSuchFieldException, IllegalAccessException {
  285. StringBuilder key = new StringBuilder(KEY_SEPARATOR + KEY_PREFIX + lock.key());
  286. // 迭代全部参数的注解,根据使用LockKeyParam的注解的参数所在的下标,来获取args中对应下标的参数值拼接到前半部分key上
  287. Annotation[][] parameterAnnotations = method.getParameterAnnotations();
  288. for (int i = 0; i < parameterAnnotations.length; i++) {
  289. // 循环该参数全部注解
  290. for (Annotation annotation : parameterAnnotations[i]) {
  291. // 注解不是 @LockKeyParam
  292. if (!annotation.annotationType().isInstance(LockKeyParam.class)) {
  293. continue;
  294. }
  295. // 获取所有fields
  296. String[] fields = ((LockKeyParam) annotation).fields();
  297. if (ArrayUtil.isEmpty(fields)) {
  298. // 普通数据类型直接拼接
  299. if (ObjectUtil.isNull(args[i])) {
  300. throw new RuntimeException("动态参数不能为null");
  301. }
  302. key.append(KEY_SEPARATOR).append(args[i]);
  303. } else {
  304. // @LockKeyParam的fields值不为null,所以当前参数应该是对象类型
  305. for (String field : fields) {
  306. Class<?> clazz = args[i].getClass();
  307. Field declaredField = clazz.getDeclaredField(field);
  308. declaredField.setAccessible(true);
  309. Object value = declaredField.get(clazz);
  310. key.append(KEY_SEPARATOR).append(value);
  311. }
  312. }
  313. }
  314. }
  315. return key.toString();
  316. }
  317. }
  318. ```
  319. ## SpringBootDemoZookeeperApplicationTests.java
  320. > 测试分布式锁
  321. ```java
  322. @RunWith(SpringRunner.class)
  323. @SpringBootTest
  324. @Slf4j
  325. public class SpringBootDemoZookeeperApplicationTests {
  326. public Integer getCount() {
  327. return count;
  328. }
  329. private Integer count = 10000;
  330. private ExecutorService executorService = Executors.newFixedThreadPool(1000);
  331. @Autowired
  332. private CuratorFramework zkClient;
  333. /**
  334. * 不使用分布式锁,程序结束查看count的值是否为0
  335. */
  336. @Test
  337. public void test() throws InterruptedException {
  338. IntStream.range(0, 10000).forEach(i -> executorService.execute(this::doBuy));
  339. TimeUnit.MINUTES.sleep(1);
  340. log.error("count值为{}", count);
  341. }
  342. /**
  343. * 测试AOP分布式锁
  344. */
  345. @Test
  346. public void testAopLock() throws InterruptedException {
  347. // 测试类中使用AOP需要手动代理
  348. SpringBootDemoZookeeperApplicationTests target = new SpringBootDemoZookeeperApplicationTests();
  349. AspectJProxyFactory factory = new AspectJProxyFactory(target);
  350. ZooLockAspect aspect = new ZooLockAspect(zkClient);
  351. factory.addAspect(aspect);
  352. SpringBootDemoZookeeperApplicationTests proxy = factory.getProxy();
  353. IntStream.range(0, 10000).forEach(i -> executorService.execute(() -> proxy.aopBuy(i)));
  354. TimeUnit.MINUTES.sleep(1);
  355. log.error("count值为{}", proxy.getCount());
  356. }
  357. /**
  358. * 测试手动加锁
  359. */
  360. @Test
  361. public void testManualLock() throws InterruptedException {
  362. IntStream.range(0, 10000).forEach(i -> executorService.execute(this::manualBuy));
  363. TimeUnit.MINUTES.sleep(1);
  364. log.error("count值为{}", count);
  365. }
  366. @ZooLock(key = "buy", timeout = 1, timeUnit = TimeUnit.MINUTES)
  367. public void aopBuy(int userId) {
  368. log.info("{} 正在出库。。。", userId);
  369. doBuy();
  370. log.info("{} 扣库存成功。。。", userId);
  371. }
  372. public void manualBuy() {
  373. String lockPath = "/buy";
  374. log.info("try to buy sth.");
  375. try {
  376. InterProcessMutex lock = new InterProcessMutex(zkClient, lockPath);
  377. try {
  378. if (lock.acquire(1, TimeUnit.MINUTES)) {
  379. doBuy();
  380. log.info("buy successfully!");
  381. }
  382. } finally {
  383. lock.release();
  384. }
  385. } catch (Exception e) {
  386. log.error("zk error");
  387. }
  388. }
  389. public void doBuy() {
  390. count--;
  391. log.info("count值为{}", count);
  392. }
  393. }
  394. ```
  395. ## 参考
  396. 1. [如何在测试类中使用 AOP](https://stackoverflow.com/questions/11436600/unit-testing-spring-around-aop-methods)
  397. 2. zookeeper 实现分布式锁:《Spring Boot 2精髓 从构建小系统到架构分布式大系统》李家智 - 第16章 - Spring Boot 和 Zoo Keeper - 16.3 实现分布式锁

一个用来深度学习并实战 spring boot 的项目,目前总共包含 66 个集成demo,已经完成 55 个。

Contributors (1)