├── .gitattributes ├── .gitignore ├── .idea ├── compiler.xml ├── dataSources.local.xml ├── dataSources.xml ├── dataSources │ └── a2fed71b-6822-4e41-a514-8ab1d04ff389.xml ├── encodings.xml ├── inspectionProfiles │ └── Project_Default.xml ├── libraries │ ├── Maven__ch_qos_logback_logback_classic_1_2_3.xml │ ├── Maven__ch_qos_logback_logback_core_1_1_11.xml │ ├── Maven__com_alibaba_druid_1_1_0.xml │ ├── Maven__com_alibaba_druid_spring_boot_starter_1_1_0.xml │ ├── Maven__com_alibaba_fastjson_1_2_31.xml │ ├── Maven__com_fasterxml_classmate_1_3_4.xml │ ├── Maven__com_fasterxml_jackson_core_jackson_annotations_2_8_0.xml │ ├── Maven__com_fasterxml_jackson_core_jackson_core_2_8_10.xml │ ├── Maven__com_fasterxml_jackson_core_jackson_databind_2_8_10.xml │ ├── Maven__com_google_code_gson_gson_2_8_4.xml │ ├── Maven__com_jayway_jsonpath_json_path_2_2_0.xml │ ├── Maven__com_vaadin_external_google_android_json_0_0_20131108_vaadin1.xml │ ├── Maven__javax_validation_validation_api_1_1_0_Final.xml │ ├── Maven__junit_junit_4_12.xml │ ├── Maven__log4j_log4j_1_2_17.xml │ ├── Maven__mysql_mysql_connector_java_5_1_44.xml │ ├── Maven__net_jpountz_lz4_lz4_1_3_0.xml │ ├── Maven__net_minidev_accessors_smart_1_1.xml │ ├── Maven__net_minidev_json_smart_2_2_1.xml │ ├── Maven__org_apache_commons_commons_pool2_2_4_2.xml │ ├── Maven__org_apache_kafka_kafka_clients_0_10_1_1.xml │ ├── Maven__org_apache_tomcat_embed_tomcat_embed_core_8_5_20.xml │ ├── Maven__org_apache_tomcat_embed_tomcat_embed_el_8_5_20.xml │ ├── Maven__org_apache_tomcat_embed_tomcat_embed_websocket_8_5_20.xml │ ├── Maven__org_apache_tomcat_tomcat_jdbc_8_5_20.xml │ ├── Maven__org_apache_tomcat_tomcat_juli_8_5_20.xml │ ├── Maven__org_assertj_assertj_core_2_6_0.xml │ ├── Maven__org_hamcrest_hamcrest_core_1_3.xml │ ├── Maven__org_hamcrest_hamcrest_library_1_3.xml │ ├── Maven__org_hibernate_hibernate_validator_5_3_5_Final.xml │ ├── Maven__org_jboss_logging_jboss_logging_3_3_1_Final.xml │ ├── Maven__org_mockito_mockito_core_1_10_19.xml │ ├── Maven__org_mybatis_mybatis_3_4_0.xml │ ├── Maven__org_mybatis_mybatis_spring_1_3_0.xml │ ├── Maven__org_mybatis_spring_boot_mybatis_spring_boot_autoconfigure_1_1_1.xml │ ├── Maven__org_mybatis_spring_boot_mybatis_spring_boot_starter_1_1_1.xml │ ├── Maven__org_objenesis_objenesis_2_1.xml │ ├── Maven__org_ow2_asm_asm_5_0_3.xml │ ├── Maven__org_projectlombok_lombok_1_16_18.xml │ ├── Maven__org_skyscreamer_jsonassert_1_4_0.xml │ ├── Maven__org_slf4j_jcl_over_slf4j_1_7_25.xml │ ├── Maven__org_slf4j_jul_to_slf4j_1_7_25.xml │ ├── Maven__org_slf4j_log4j_over_slf4j_1_7_25.xml │ ├── Maven__org_slf4j_slf4j_api_1_7_25.xml │ ├── Maven__org_springframework_boot_spring_boot_1_5_7_RELEASE.xml │ ├── Maven__org_springframework_boot_spring_boot_autoconfigure_1_5_7_RELEASE.xml │ ├── Maven__org_springframework_boot_spring_boot_starter_1_5_7_RELEASE.xml │ ├── Maven__org_springframework_boot_spring_boot_starter_jdbc_1_5_7_RELEASE.xml │ ├── Maven__org_springframework_boot_spring_boot_starter_logging_1_5_7_RELEASE.xml │ ├── Maven__org_springframework_boot_spring_boot_starter_test_1_5_7_RELEASE.xml │ ├── Maven__org_springframework_boot_spring_boot_starter_tomcat_1_5_7_RELEASE.xml │ ├── Maven__org_springframework_boot_spring_boot_starter_web_1_5_7_RELEASE.xml │ ├── Maven__org_springframework_boot_spring_boot_test_1_5_7_RELEASE.xml │ ├── Maven__org_springframework_boot_spring_boot_test_autoconfigure_1_5_7_RELEASE.xml │ ├── Maven__org_springframework_data_spring_data_commons_1_13_7_RELEASE.xml │ ├── Maven__org_springframework_data_spring_data_keyvalue_1_2_7_RELEASE.xml │ ├── Maven__org_springframework_data_spring_data_redis_1_8_7_RELEASE.xml │ ├── Maven__org_springframework_kafka_spring_kafka_1_1_6_RELEASE.xml │ ├── Maven__org_springframework_retry_spring_retry_1_2_1_RELEASE.xml │ ├── Maven__org_springframework_spring_aop_4_3_11_RELEASE.xml │ ├── Maven__org_springframework_spring_beans_4_3_11_RELEASE.xml │ ├── Maven__org_springframework_spring_context_4_3_11_RELEASE.xml │ ├── Maven__org_springframework_spring_context_support_4_3_11_RELEASE.xml │ ├── Maven__org_springframework_spring_core_4_3_11_RELEASE.xml │ ├── Maven__org_springframework_spring_expression_4_3_11_RELEASE.xml │ ├── Maven__org_springframework_spring_jdbc_4_3_11_RELEASE.xml │ ├── Maven__org_springframework_spring_messaging_4_3_11_RELEASE.xml │ ├── Maven__org_springframework_spring_oxm_4_3_11_RELEASE.xml │ ├── Maven__org_springframework_spring_test_4_3_11_RELEASE.xml │ ├── Maven__org_springframework_spring_tx_4_3_11_RELEASE.xml │ ├── Maven__org_springframework_spring_web_4_3_11_RELEASE.xml │ ├── Maven__org_springframework_spring_webmvc_4_3_11_RELEASE.xml │ ├── Maven__org_xerial_snappy_snappy_java_1_1_2_6.xml │ ├── Maven__org_yaml_snakeyaml_1_17.xml │ └── Maven__redis_clients_jedis_2_9_0.xml ├── misc.xml ├── modules.xml ├── smartfox_info.xml ├── uiDesigner.xml ├── vcs.xml └── workspace.xml ├── .mvn └── wrapper │ ├── MavenWrapperDownloader.java │ └── maven-wrapper.properties ├── LICENSE ├── README.md ├── mvnw ├── mvnw.cmd ├── pom.xml ├── secondkill.log.2019-06-10.0.gz ├── seconds-kill.iml └── src ├── main ├── java │ └── com │ │ └── daydreamdev │ │ └── secondskill │ │ ├── Kafka │ │ └── ConsumerListen.java │ │ ├── SecondsKillApplication.java │ │ ├── common │ │ ├── Limit │ │ │ └── RedisLimit.java │ │ ├── RedisPreheatRunner.java │ │ ├── StockWithRedis │ │ │ └── StockWithRedis.java │ │ ├── stockWithRedis │ │ │ └── RedisKeysConstant.java │ │ └── utils │ │ │ ├── RedisPool.java │ │ │ ├── RedisPoolUtil.java │ │ │ └── ScriptUtil.java │ │ ├── controller │ │ └── IndexController.java │ │ ├── dao │ │ ├── StockMapper.java │ │ └── StockOrderMapper.java │ │ ├── pojo │ │ ├── Stock.java │ │ └── StockOrder.java │ │ └── service │ │ ├── api │ │ ├── OrderService.java │ │ └── StockService.java │ │ └── impl │ │ ├── OrderServiceImpl.java │ │ └── StockServiceImpl.java └── resources │ ├── application.properties │ └── limit.lua └── test └── java └── com └── daydreamdev └── secondskill └── SecondsKillApplicationTests.java /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files and Maven 2 | target/ 3 | pom.xml.tag 4 | pom.xml.releaseBackup 5 | pom.xml.versionsBackup 6 | pom.xml.next 7 | release.properties 8 | dependency-reduced-pom.xml 9 | buildNumber.properties 10 | .mvn/timing.properties 11 | 12 | # Compiled class file 13 | *.class 14 | 15 | # Log file 16 | *.log 17 | 18 | # BlueJ files 19 | *.ctxt 20 | 21 | # Mobile Tools for Java (J2ME) 22 | .mtj.tmp/ 23 | 24 | # Package Files # 25 | *.jar 26 | *.war 27 | *.nar 28 | *.ear 29 | *.zip 30 | *.tar.gz 31 | *.rar 32 | 33 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 34 | hs_err_pid* 35 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.idea/dataSources.local.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #@ 7 | ` 8 | 9 | 10 | master_key 11 | root 12 | *:seconds_kill 13 | 14 | 15 | -------------------------------------------------------------------------------- /.idea/dataSources.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | mysql 6 | true 7 | com.mysql.jdbc.Driver 8 | jdbc:mysql://119.3.214.253:3306/seconds_kill 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/dataSources/a2fed71b-6822-4e41-a514-8ab1d04ff389.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | exact 6 | 7 | 8 | utf8_bin 9 | 10 | 11 | utf8_general_ci 12 | 13 | 14 | latin1_swedish_ci 15 | 16 | 17 | utf8_general_ci 18 | 19 | 20 | 1 21 | 1 22 | utf8_general_ci 23 | 24 | 25 | utf8_general_ci 26 | 27 | 28 | armscii8 29 | 0 30 | 31 | 32 | armscii8 33 | 1 34 | 35 | 36 | ascii 37 | 0 38 | 39 | 40 | ascii 41 | 1 42 | 43 | 44 | big5 45 | 0 46 | 47 | 48 | big5 49 | 1 50 | 51 | 52 | binary 53 | 1 54 | 55 | 56 | cp1250 57 | 0 58 | 59 | 60 | cp1250 61 | 0 62 | 63 | 64 | cp1250 65 | 0 66 | 67 | 68 | cp1250 69 | 1 70 | 71 | 72 | cp1250 73 | 0 74 | 75 | 76 | cp1251 77 | 0 78 | 79 | 80 | cp1251 81 | 0 82 | 83 | 84 | cp1251 85 | 1 86 | 87 | 88 | cp1251 89 | 0 90 | 91 | 92 | cp1251 93 | 0 94 | 95 | 96 | cp1256 97 | 0 98 | 99 | 100 | cp1256 101 | 1 102 | 103 | 104 | cp1257 105 | 0 106 | 107 | 108 | cp1257 109 | 1 110 | 111 | 112 | cp1257 113 | 0 114 | 115 | 116 | cp850 117 | 0 118 | 119 | 120 | cp850 121 | 1 122 | 123 | 124 | cp852 125 | 0 126 | 127 | 128 | cp852 129 | 1 130 | 131 | 132 | cp866 133 | 0 134 | 135 | 136 | cp866 137 | 1 138 | 139 | 140 | cp932 141 | 0 142 | 143 | 144 | cp932 145 | 1 146 | 147 | 148 | dec8 149 | 0 150 | 151 | 152 | dec8 153 | 1 154 | 155 | 156 | eucjpms 157 | 0 158 | 159 | 160 | eucjpms 161 | 1 162 | 163 | 164 | euckr 165 | 0 166 | 167 | 168 | euckr 169 | 1 170 | 171 | 172 | gb18030 173 | 0 174 | 175 | 176 | gb18030 177 | 1 178 | 179 | 180 | gb18030 181 | 0 182 | 183 | 184 | gb2312 185 | 0 186 | 187 | 188 | gb2312 189 | 1 190 | 191 | 192 | gbk 193 | 0 194 | 195 | 196 | gbk 197 | 1 198 | 199 | 200 | geostd8 201 | 0 202 | 203 | 204 | geostd8 205 | 1 206 | 207 | 208 | greek 209 | 0 210 | 211 | 212 | greek 213 | 1 214 | 215 | 216 | hebrew 217 | 0 218 | 219 | 220 | hebrew 221 | 1 222 | 223 | 224 | hp8 225 | 0 226 | 227 | 228 | hp8 229 | 1 230 | 231 | 232 | keybcs2 233 | 0 234 | 235 | 236 | keybcs2 237 | 1 238 | 239 | 240 | koi8r 241 | 0 242 | 243 | 244 | koi8r 245 | 1 246 | 247 | 248 | koi8u 249 | 0 250 | 251 | 252 | koi8u 253 | 1 254 | 255 | 256 | latin1 257 | 0 258 | 259 | 260 | latin1 261 | 0 262 | 263 | 264 | latin1 265 | 0 266 | 267 | 268 | latin1 269 | 0 270 | 271 | 272 | latin1 273 | 0 274 | 275 | 276 | latin1 277 | 0 278 | 279 | 280 | latin1 281 | 0 282 | 283 | 284 | latin1 285 | 1 286 | 287 | 288 | latin2 289 | 0 290 | 291 | 292 | latin2 293 | 0 294 | 295 | 296 | latin2 297 | 0 298 | 299 | 300 | latin2 301 | 1 302 | 303 | 304 | latin2 305 | 0 306 | 307 | 308 | latin5 309 | 0 310 | 311 | 312 | latin5 313 | 1 314 | 315 | 316 | latin7 317 | 0 318 | 319 | 320 | latin7 321 | 0 322 | 323 | 324 | latin7 325 | 1 326 | 327 | 328 | latin7 329 | 0 330 | 331 | 332 | macce 333 | 0 334 | 335 | 336 | macce 337 | 1 338 | 339 | 340 | macroman 341 | 0 342 | 343 | 344 | macroman 345 | 1 346 | 347 | 348 | sjis 349 | 0 350 | 351 | 352 | sjis 353 | 1 354 | 355 | 356 | swe7 357 | 0 358 | 359 | 360 | swe7 361 | 1 362 | 363 | 364 | tis620 365 | 0 366 | 367 | 368 | tis620 369 | 1 370 | 371 | 372 | ucs2 373 | 0 374 | 375 | 376 | ucs2 377 | 0 378 | 379 | 380 | ucs2 381 | 0 382 | 383 | 384 | ucs2 385 | 0 386 | 387 | 388 | ucs2 389 | 0 390 | 391 | 392 | ucs2 393 | 0 394 | 395 | 396 | ucs2 397 | 1 398 | 399 | 400 | ucs2 401 | 0 402 | 403 | 404 | ucs2 405 | 0 406 | 407 | 408 | ucs2 409 | 0 410 | 411 | 412 | ucs2 413 | 0 414 | 415 | 416 | ucs2 417 | 0 418 | 419 | 420 | ucs2 421 | 0 422 | 423 | 424 | ucs2 425 | 0 426 | 427 | 428 | ucs2 429 | 0 430 | 431 | 432 | ucs2 433 | 0 434 | 435 | 436 | ucs2 437 | 0 438 | 439 | 440 | ucs2 441 | 0 442 | 443 | 444 | ucs2 445 | 0 446 | 447 | 448 | ucs2 449 | 0 450 | 451 | 452 | ucs2 453 | 0 454 | 455 | 456 | ucs2 457 | 0 458 | 459 | 460 | ucs2 461 | 0 462 | 463 | 464 | ucs2 465 | 0 466 | 467 | 468 | ucs2 469 | 0 470 | 471 | 472 | ucs2 473 | 0 474 | 475 | 476 | ucs2 477 | 0 478 | 479 | 480 | ujis 481 | 0 482 | 483 | 484 | ujis 485 | 1 486 | 487 | 488 | utf16 489 | 0 490 | 491 | 492 | utf16 493 | 0 494 | 495 | 496 | utf16 497 | 0 498 | 499 | 500 | utf16 501 | 0 502 | 503 | 504 | utf16 505 | 0 506 | 507 | 508 | utf16 509 | 0 510 | 511 | 512 | utf16 513 | 1 514 | 515 | 516 | utf16 517 | 0 518 | 519 | 520 | utf16 521 | 0 522 | 523 | 524 | utf16 525 | 0 526 | 527 | 528 | utf16 529 | 0 530 | 531 | 532 | utf16 533 | 0 534 | 535 | 536 | utf16 537 | 0 538 | 539 | 540 | utf16 541 | 0 542 | 543 | 544 | utf16 545 | 0 546 | 547 | 548 | utf16 549 | 0 550 | 551 | 552 | utf16 553 | 0 554 | 555 | 556 | utf16 557 | 0 558 | 559 | 560 | utf16 561 | 0 562 | 563 | 564 | utf16 565 | 0 566 | 567 | 568 | utf16 569 | 0 570 | 571 | 572 | utf16 573 | 0 574 | 575 | 576 | utf16 577 | 0 578 | 579 | 580 | utf16 581 | 0 582 | 583 | 584 | utf16 585 | 0 586 | 587 | 588 | utf16 589 | 0 590 | 591 | 592 | utf16le 593 | 0 594 | 595 | 596 | utf16le 597 | 1 598 | 599 | 600 | utf32 601 | 0 602 | 603 | 604 | utf32 605 | 0 606 | 607 | 608 | utf32 609 | 0 610 | 611 | 612 | utf32 613 | 0 614 | 615 | 616 | utf32 617 | 0 618 | 619 | 620 | utf32 621 | 0 622 | 623 | 624 | utf32 625 | 1 626 | 627 | 628 | utf32 629 | 0 630 | 631 | 632 | utf32 633 | 0 634 | 635 | 636 | utf32 637 | 0 638 | 639 | 640 | utf32 641 | 0 642 | 643 | 644 | utf32 645 | 0 646 | 647 | 648 | utf32 649 | 0 650 | 651 | 652 | utf32 653 | 0 654 | 655 | 656 | utf32 657 | 0 658 | 659 | 660 | utf32 661 | 0 662 | 663 | 664 | utf32 665 | 0 666 | 667 | 668 | utf32 669 | 0 670 | 671 | 672 | utf32 673 | 0 674 | 675 | 676 | utf32 677 | 0 678 | 679 | 680 | utf32 681 | 0 682 | 683 | 684 | utf32 685 | 0 686 | 687 | 688 | utf32 689 | 0 690 | 691 | 692 | utf32 693 | 0 694 | 695 | 696 | utf32 697 | 0 698 | 699 | 700 | utf32 701 | 0 702 | 703 | 704 | utf8 705 | 0 706 | 707 | 708 | utf8 709 | 0 710 | 711 | 712 | utf8 713 | 0 714 | 715 | 716 | utf8 717 | 0 718 | 719 | 720 | utf8 721 | 0 722 | 723 | 724 | utf8 725 | 0 726 | 727 | 728 | utf8 729 | 1 730 | 731 | 732 | utf8 733 | 0 734 | 735 | 736 | utf8 737 | 0 738 | 739 | 740 | utf8 741 | 0 742 | 743 | 744 | utf8 745 | 0 746 | 747 | 748 | utf8 749 | 0 750 | 751 | 752 | utf8 753 | 0 754 | 755 | 756 | utf8 757 | 0 758 | 759 | 760 | utf8 761 | 0 762 | 763 | 764 | utf8 765 | 0 766 | 767 | 768 | utf8 769 | 0 770 | 771 | 772 | utf8 773 | 0 774 | 775 | 776 | utf8 777 | 0 778 | 779 | 780 | utf8 781 | 0 782 | 783 | 784 | utf8 785 | 0 786 | 787 | 788 | utf8 789 | 0 790 | 791 | 792 | utf8 793 | 0 794 | 795 | 796 | utf8 797 | 0 798 | 799 | 800 | utf8 801 | 0 802 | 803 | 804 | utf8 805 | 0 806 | 807 | 808 | utf8 809 | 0 810 | 811 | 812 | utf8mb4 813 | 0 814 | 815 | 816 | utf8mb4 817 | 0 818 | 819 | 820 | utf8mb4 821 | 0 822 | 823 | 824 | utf8mb4 825 | 0 826 | 827 | 828 | utf8mb4 829 | 0 830 | 831 | 832 | utf8mb4 833 | 0 834 | 835 | 836 | utf8mb4 837 | 1 838 | 839 | 840 | utf8mb4 841 | 0 842 | 843 | 844 | utf8mb4 845 | 0 846 | 847 | 848 | utf8mb4 849 | 0 850 | 851 | 852 | utf8mb4 853 | 0 854 | 855 | 856 | utf8mb4 857 | 0 858 | 859 | 860 | utf8mb4 861 | 0 862 | 863 | 864 | utf8mb4 865 | 0 866 | 867 | 868 | utf8mb4 869 | 0 870 | 871 | 872 | utf8mb4 873 | 0 874 | 875 | 876 | utf8mb4 877 | 0 878 | 879 | 880 | utf8mb4 881 | 0 882 | 883 | 884 | utf8mb4 885 | 0 886 | 887 | 888 | utf8mb4 889 | 0 890 | 891 | 892 | utf8mb4 893 | 0 894 | 895 | 896 | utf8mb4 897 | 0 898 | 899 | 900 | utf8mb4 901 | 0 902 | 903 | 904 | utf8mb4 905 | 0 906 | 907 | 908 | utf8mb4 909 | 0 910 | 911 | 912 | utf8mb4 913 | 0 914 | 915 | 916 | InnoDB 917 |
918 | 919 | InnoDB 920 |
921 | 922 | 1 923 | int(11) unsigned|0 924 | 1 925 | 1 926 | normal 927 | 928 | 929 | 2 930 | 名称 931 | varchar(50)|0 932 | 1 933 | '' 934 | normal 935 | 936 | 937 | 3 938 | 库存 939 | int(11)|0 940 | 1 941 | normal 942 | 943 | 944 | 4 945 | 已售 946 | int(11)|0 947 | 1 948 | normal 949 | 950 | 951 | 5 952 | 乐观锁,版本号 953 | int(11)|0 954 | 1 955 | normal 956 | 957 | 958 | 1 959 | id 960 | 1 961 | 962 | 963 | 1 964 | int(11) unsigned|0 965 | 1 966 | 1 967 | normal 968 | 969 | 970 | 2 971 | 库存ID 972 | int(11)|0 973 | 1 974 | normal 975 | 976 | 977 | 3 978 | 商品名称 979 | varchar(30)|0 980 | 1 981 | '' 982 | normal 983 | 984 | 985 | 4 986 | 创建时间 987 | timestamp|0 988 | 1 989 | CURRENT_TIMESTAMP 990 | normal 991 | CURRENT_TIMESTAMP 992 | 993 | 994 | 1 995 | id 996 | 1 997 | 998 |
999 |
-------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 95 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__ch_qos_logback_logback_classic_1_2_3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__ch_qos_logback_logback_core_1_1_11.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_alibaba_druid_1_1_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_alibaba_druid_spring_boot_starter_1_1_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_alibaba_fastjson_1_2_31.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_fasterxml_classmate_1_3_4.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_fasterxml_jackson_core_jackson_annotations_2_8_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_fasterxml_jackson_core_jackson_core_2_8_10.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_fasterxml_jackson_core_jackson_databind_2_8_10.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_google_code_gson_gson_2_8_4.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_jayway_jsonpath_json_path_2_2_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_vaadin_external_google_android_json_0_0_20131108_vaadin1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__javax_validation_validation_api_1_1_0_Final.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__junit_junit_4_12.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__log4j_log4j_1_2_17.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__mysql_mysql_connector_java_5_1_44.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__net_jpountz_lz4_lz4_1_3_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__net_minidev_accessors_smart_1_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__net_minidev_json_smart_2_2_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_apache_commons_commons_pool2_2_4_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_apache_kafka_kafka_clients_0_10_1_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_apache_tomcat_embed_tomcat_embed_core_8_5_20.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_apache_tomcat_embed_tomcat_embed_el_8_5_20.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_apache_tomcat_embed_tomcat_embed_websocket_8_5_20.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_apache_tomcat_tomcat_jdbc_8_5_20.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_apache_tomcat_tomcat_juli_8_5_20.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_assertj_assertj_core_2_6_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_hamcrest_hamcrest_core_1_3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_hamcrest_hamcrest_library_1_3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_hibernate_hibernate_validator_5_3_5_Final.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_jboss_logging_jboss_logging_3_3_1_Final.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_mockito_mockito_core_1_10_19.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_mybatis_mybatis_3_4_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_mybatis_mybatis_spring_1_3_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_mybatis_spring_boot_mybatis_spring_boot_autoconfigure_1_1_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_mybatis_spring_boot_mybatis_spring_boot_starter_1_1_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_objenesis_objenesis_2_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_ow2_asm_asm_5_0_3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_projectlombok_lombok_1_16_18.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_skyscreamer_jsonassert_1_4_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_slf4j_jcl_over_slf4j_1_7_25.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_slf4j_jul_to_slf4j_1_7_25.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_slf4j_log4j_over_slf4j_1_7_25.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_slf4j_slf4j_api_1_7_25.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_boot_spring_boot_1_5_7_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_boot_spring_boot_autoconfigure_1_5_7_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_boot_spring_boot_starter_1_5_7_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_boot_spring_boot_starter_jdbc_1_5_7_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_boot_spring_boot_starter_logging_1_5_7_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_boot_spring_boot_starter_test_1_5_7_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_boot_spring_boot_starter_tomcat_1_5_7_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_boot_spring_boot_starter_web_1_5_7_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_boot_spring_boot_test_1_5_7_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_boot_spring_boot_test_autoconfigure_1_5_7_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_data_spring_data_commons_1_13_7_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_data_spring_data_keyvalue_1_2_7_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_data_spring_data_redis_1_8_7_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_kafka_spring_kafka_1_1_6_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_retry_spring_retry_1_2_1_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_aop_4_3_11_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_beans_4_3_11_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_context_4_3_11_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_context_support_4_3_11_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_core_4_3_11_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_expression_4_3_11_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_jdbc_4_3_11_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_messaging_4_3_11_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_oxm_4_3_11_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_test_4_3_11_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_tx_4_3_11_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_web_4_3_11_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_webmvc_4_3_11_RELEASE.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_xerial_snappy_snappy_java_1_1_2_6.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_yaml_snakeyaml_1_17.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__redis_clients_jedis_2_9_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/smartfox_info.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/uiDesigner.xml: -------------------------------------------------------------------------------- 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 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | https://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | import java.io.File; 21 | import java.io.FileInputStream; 22 | import java.io.FileOutputStream; 23 | import java.io.IOException; 24 | import java.net.URL; 25 | import java.nio.channels.Channels; 26 | import java.nio.channels.ReadableByteChannel; 27 | import java.util.Properties; 28 | 29 | public class MavenWrapperDownloader { 30 | 31 | /** 32 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 33 | */ 34 | private static final String DEFAULT_DOWNLOAD_URL = 35 | "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"; 36 | 37 | /** 38 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to 39 | * use instead of the default one. 40 | */ 41 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 42 | ".mvn/wrapper/maven-wrapper.properties"; 43 | 44 | /** 45 | * Path where the maven-wrapper.jar will be saved to. 46 | */ 47 | private static final String MAVEN_WRAPPER_JAR_PATH = 48 | ".mvn/wrapper/maven-wrapper.jar"; 49 | 50 | /** 51 | * Name of the property which should be used to override the default download url for the wrapper. 52 | */ 53 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 54 | 55 | public static void main(String args[]) { 56 | System.out.println("- Downloader started"); 57 | File baseDirectory = new File(args[0]); 58 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 59 | 60 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 61 | // wrapperUrl parameter. 62 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 63 | String url = DEFAULT_DOWNLOAD_URL; 64 | if(mavenWrapperPropertyFile.exists()) { 65 | FileInputStream mavenWrapperPropertyFileInputStream = null; 66 | try { 67 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 68 | Properties mavenWrapperProperties = new Properties(); 69 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 70 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 71 | } catch (IOException e) { 72 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 73 | } finally { 74 | try { 75 | if(mavenWrapperPropertyFileInputStream != null) { 76 | mavenWrapperPropertyFileInputStream.close(); 77 | } 78 | } catch (IOException e) { 79 | // Ignore ... 80 | } 81 | } 82 | } 83 | System.out.println("- Downloading from: : " + url); 84 | 85 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 86 | if(!outputFile.getParentFile().exists()) { 87 | if(!outputFile.getParentFile().mkdirs()) { 88 | System.out.println( 89 | "- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 90 | } 91 | } 92 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 93 | try { 94 | downloadFileFromURL(url, outputFile); 95 | System.out.println("Done"); 96 | System.exit(0); 97 | } catch (Throwable e) { 98 | System.out.println("- Error downloading"); 99 | e.printStackTrace(); 100 | System.exit(1); 101 | } 102 | } 103 | 104 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 105 | URL website = new URL(urlString); 106 | ReadableByteChannel rbc; 107 | rbc = Channels.newChannel(website.openStream()); 108 | FileOutputStream fos = new FileOutputStream(destination); 109 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 110 | fos.close(); 111 | rbc.close(); 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 gongfukangEE 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 如何设计一个秒杀系统 2 | 3 | ## 系统的特点 4 | 5 | - 高性能:秒杀涉及大量的并发读和并发写,因此支持高并发访问这点非常关键 6 | - 一致性:秒杀商品减库存的实现方式同样关键,有限数量的商品在同一时刻被很多倍的请求同时来减库存,在大并发更新的过程中都要保证数据的准确性。 7 | - 高可用:秒杀时会在一瞬间涌入大量的流量,为了避免系统宕机,保证高可用,需要做好流量限制 8 | 9 | ## 优化思路 10 | 11 | - 后端优化:将请求尽量拦截在系统上游 12 | - 限流:屏蔽掉无用的流量,允许少部分流量走后端。假设现在库存为 10,有 1000 个购买请求,最终只有 10 个可以成功,99% 的请求都是无效请求 13 | - 削峰:秒杀请求在时间上高度集中于某一个时间点,瞬时流量容易压垮系统,因此需要对流量进行削峰处理,缓冲瞬时流量,尽量让服务器对资源进行平缓处理 14 | - 异步:将同步请求转换为异步请求,来提高并发量,本质也是削峰处理 15 | - 利用缓存:创建订单时,每次都需要先查询判断库存,只有少部分成功的请求才会创建订单,因此可以将商品信息放在缓存中,减少数据库查询 16 | - 负载均衡:利用 Nginx 等使用多个服务器并发处理请求,减少单个服务器压力 17 | - 前端优化: 18 | - 限流:前端答题或验证码,来分散用户的请求 19 | - 禁止重复提交:限定每个用户发起一次秒杀后,需等待才可以发起另一次请求,从而减少用户的重复请求 20 | - 本地标记:用户成功秒杀到商品后,将提交按钮置灰,禁止用户再次提交请求 21 | - 动静分离:将前端静态数据直接缓存到离用户最近的地方,比如用户浏览器、CDN 或者服务端的缓存中 22 | - 防作弊优化: 23 | - 隐藏秒杀接口:如果秒杀地址直接暴露,在秒杀开始前可能会被恶意用户来刷接口,因此需要在没到秒杀开始时间不能获取秒杀接口,只有秒杀开始了,才返回秒杀地址 url 和验证 MD5,用户拿到这两个数据才可以进行秒杀 24 | - 同一个账号多次发出请求:在前端优化的禁止重复提交可以进行优化;也可以使用 Redis 标志位,每个用户的所有请求都尝试在 Redis 中插入一个 `userId_secondsKill` 标志位,成功插入的才可以执行后续的秒杀逻辑,其他被过滤掉,执行完秒杀逻辑后,删除标志位 25 | - 多个账号一次性发出多个请求:一般这种请求都来自同一个 IP 地址,可以检测 IP 的请求频率,如果过于频繁则弹出一个验证码 26 | - 多个账号不同 IP 发起不同请求:这种一般都是僵尸账号,检测账号的活跃度或者等级等信息,来进行限制。比如微博抽奖,用 iphone 的年轻女性用户中奖几率更大。通过用户画像限制僵尸号无法参与秒杀或秒杀不能成功 27 | 28 | ## 代码优化 29 | 30 | 代码整体思路参考的 [@crossoverJie](),做了以下几点变动 31 | 32 | 1. 将 SSM 换成 SpringBoot,开箱即用,替换 Mapper XML 为注解,去掉 Dubbo 和 Zookeeper 33 | 2. 原项目中依赖了开发者自己的开源包 [distributed-redis-tool](),本项目将用到的限流部分直接集成到代码中 34 | 3. 加入缓存预热,在秒杀开始前,将库存信息读到缓存中,并暴露数据库和缓存重置方法便于服务器部署压测 35 | 4. 缓存更新逻辑中加入 Redis 事务,避免脏数据 36 | 5. 将 Kafka-client 替换为 spring-kafka,自动配置,通过 KafkaTemplate 和 Listen 进行消息的生产和消费,采用 Gson 进行 Kafka 消息序列化和反序列化,精简大量代码 37 | 38 | ### Jmeter 压测 39 | 40 | ![]() 41 | 42 | **测试流程如下:** 43 | 44 | 首先下载 JMeter 安装包 可以去官网下载:http://jmeter.apache.org 45 | 46 | windows 环境下载 zip 安装包,然后将下载的文件进行解压,进入 bin 目录运行 jmeter.bat 即可。 47 | 48 | 接下来是 Jmeter 测试计划设置: 49 | 50 | (1)在测试计划上右键新建一个线程组 51 | 52 | ![]() 53 | 54 | 线程组属性内可以修改线程数、Ramp-Up 时间和循环次数。 55 | 56 | ![]() 57 | 58 | (2)在线程组上右键添加 HTTP 请求 59 | 60 | ![]() 61 | 62 | 其属性包括 WEB 服务器的协议、服务器名称或 IP 和端口号,HTTP 请求的方法和路径。 63 | 64 | ![]() 65 | 66 | (3)在HTTP请求上右键添加一个监听器,可以根据自己的需求添加汇总报告、查看结果树等等。 67 | 68 | ![]() 69 | 70 | 如下图所示为汇总报告,可以查看异常比例和吞吐量,方便调优。 71 | 72 | ![]() 73 | 74 | 这样一个简单的 Jmeter 测试计划就算添加完了。一个 HTTP 请求对应一个接口,可以添加多个 HTTP 请求 以达到多个接口同时检测的需求。 75 | 76 | ### 0. 基本秒杀逻辑 77 | 78 | ```java 79 | @Override 80 | public int createWrongOrder(int sid) throws Exception { 81 | // 数据库校验库存 82 | Stock stock = checkStock(sid); 83 | // 扣库存(无锁) 84 | saleStock(stock); 85 | // 生成订单 86 | int res = createOrder(stock); 87 | return res; 88 | } 89 | private Stock checkStock(int sid) throws Exception { 90 | Stock stock = stockService.getStockById(sid); 91 | if (stock.getCount() < 1) { 92 | throw new RuntimeException("库存不足"); 93 | } 94 | return stock; 95 | } 96 | private int saleStock(Stock stock) { 97 | stock.setSale(stock.getSale() + 1); 98 | stock.setCount(stock.getCount() - 1); 99 | return stockService.updateStockById(stock); 100 | } 101 | private int createOrder(Stock stock) throws Exception { 102 | StockOrder order = new StockOrder(); 103 | order.setSid(stock.getId()); 104 | order.setName(stock.getName()); 105 | order.setCreateTime(new Date()); 106 | int res = orderMapper.insertSelective(order); 107 | if (res == 0) { 108 | throw new RuntimeException("创建订单失败"); 109 | } 110 | return res; 111 | } 112 | // 扣库存 Mapper 文件 113 | @Update("UPDATE stock SET count = #{count, jdbcType = INTEGER}, name = #{name, jdbcType = VARCHAR}, " + "sale = #{sale,jdbcType = INTEGER},version = #{version,jdbcType = INTEGER} " + "WHERE id = #{id, jdbcType = INTEGER}") 114 | ``` 115 | 116 | ### 1. 乐观锁更新库存,解决超卖问题 117 | 118 | 超卖问题出现的场景 119 | 120 | ![](https://github.com/gongfukangEE/gongfukangEE.github.io/raw/master/_pic/%E5%88%86%E5%B8%83%E5%BC%8F/%E7%A7%92%E6%9D%80%E8%B6%85%E5%8D%96.png) 121 | 122 | 悲观锁虽然可以解决超卖问题,但是加锁的时间可能会很长,会长时间的限制其他用户的访问,导致很多请求等待锁,卡死在这里,如果这种请求很多就会耗尽连接,系统出现异常。乐观锁默认不加锁,更失败就直接返回抢购失败,可以承受较高并发 123 | 124 | ![](https://github.com/gongfukangEE/gongfukangEE.github.io/raw/master/_pic/%E5%88%86%E5%B8%83%E5%BC%8F/%E4%B9%90%E8%A7%82%E9%94%81%E6%89%A3%E5%BA%93%E5%AD%98.png) 125 | 126 | ```java 127 | @Override 128 | public int createOptimisticOrder(int sid) throws Exception { 129 | // 校验库存 130 | Stock stock = checkStock(sid); 131 | // 乐观锁更新 132 | saleStockOptimstic(stock); 133 | // 创建订单 134 | int id = createOrder(stock); 135 | return id; 136 | } 137 | // 乐观锁 Mapper 文件 138 | @Update("UPDATE stock SET count = count - 1, sale = sale + 1, version = version + 1 WHERE " + 139 | "id = #{id, jdbcType = INTEGER} AND version = #{version, jdbcType = INTEGER}") 140 | ``` 141 | 142 | ### 2. Redis 计数限流 143 | 144 | 根据前面的优化分析,假设现在有 10 个商品,有 1000 个并发秒杀请求,最终只有 10 个订单会成功创建,也就是说有 990 的请求是无效的,这些无效的请求也会给数据库带来压力,因此可以在在请求落到数据库之前就将无效的请求过滤掉,将并发控制在一个可控的范围,这样落到数据库的压力就小很多 145 | 146 | 关于限流的方法,可以看这篇博客[浅析限流算法](),由于计数限流实现起来比较简单,因此采用计数限流,限流的实现可以直接使用 Guava 的 RateLimit 方法,但是由于后续需要将实例通过 Nginx 实现负载均衡,这里选用 Redis 实现分布式限流 147 | 148 | 在 `RedisPool` 中对 `Jedis` 线程池进行了简单的封装,封装了初始化和关闭方法,同时在 `RedisPoolUtil` 中对 Jedis 常用 API 进行简单封装,每个方法调用完毕则关闭 Jedis 连接。 149 | 150 | 限流要保证写入 Redis 操作的原子性,因此利用 Redis 的单线程机制,通过 LUA 脚本来完成。 151 | 152 | ![](https://github.com/gongfukangEE/gongfukangEE.github.io/raw/master/_pic/%E5%88%86%E5%B8%83%E5%BC%8F/%E7%A7%92%E6%9D%80%E9%99%90%E6%B5%81.png) 153 | 154 | ```java 155 | @Slf4j 156 | public class RedisLimit { 157 | 158 | private static final int FAIL_CODE = 0; 159 | 160 | private static Integer limit = 5; 161 | 162 | /** 163 | * Redis 限流 164 | */ 165 | public static Boolean limit() { 166 | Jedis jedis = null; 167 | Object result = null; 168 | try { 169 | // 获取 jedis 实例 170 | jedis = RedisPool.getJedis(); 171 | // 解析 Lua 文件 172 | String script = ScriptUtil.getScript("limit.lua"); 173 | // 请求限流 174 | String key = String.valueOf(System.currentTimeMillis() / 1000); 175 | // 计数限流 176 | result = jedis.eval(script, Collections.singletonList(key), Collections.singletonList(String.valueOf(limit))); 177 | if (FAIL_CODE != (Long) result) { 178 | log.info("成功获取令牌"); 179 | return true; 180 | } 181 | } catch (Exception e) { 182 | log.error(limit, e); 183 | } finally { 184 | RedisPool.jedisPoolClose(jedis); 185 | } 186 | return false; 187 | } 188 | } 189 | // 在 Controller 中,每个请求到来先取令牌,获取到令牌再执行后续操作,获取不到直接返回 ERROR 190 | public String createOptimisticLimitOrder(HttpServletRequest request, int sid) { 191 | int res = 0; 192 | try { 193 | if (RedisLimit.limit()) { 194 | res = orderService.createOptimisticOrder(sid); 195 | } 196 | } catch (Exception e) { 197 | log.error("Exception: " + e); 198 | } 199 | return res == 1 ? success : error; 200 | } 201 | ``` 202 | 203 | ### 3. Redis 缓存商品库存信息 204 | 205 | 虽然限流能够过滤掉一些无效的请求,但是还是会有很多请求落在数据库上,通过 `Druid` 监控可以看出,实时查询库存的语句被大量调用,对于每个没有被过滤掉的请求,都会去数据库查询库存来判断库存是否充足,对于这个查询可以放在缓存 Redis 中,Redis 的数据是存放在内存中的,速度快很多。 206 | 207 | ![]() 208 | 209 | #### 缓存预热 210 | 211 | 在秒杀开始前,需要将秒杀商品信息提前缓存到 Redis 中,这么秒杀开始时则直接从 Redis 中读取,也就是缓存预热,Springboot 中开发者通过 `implement ApplicationRunner` 来设定 SpringBoot 启动后立即执行的方法 212 | 213 | ```java 214 | @Component 215 | public class RedisPreheatRunner implements ApplicationRunner { 216 | 217 | @Autowired 218 | private StockService stockService; 219 | 220 | @Override 221 | public void run(ApplicationArguments args) throws Exception { 222 | // 从数据库中查询热卖商品,商品 id 为 1 223 | Stock stock = stockService.getStockById(1); 224 | // 删除旧缓存 225 | RedisPoolUtil.del(RedisKeysConstant.STOCK_COUNT + stock.getCount()); 226 | RedisPoolUtil.del(RedisKeysConstant.STOCK_SALE + stock.getSale()); 227 | RedisPoolUtil.del(RedisKeysConstant.STOCK_VERSION + stock.getVersion()); 228 | //缓存预热 229 | int sid = stock.getId(); 230 | RedisPoolUtil.set(RedisKeysConstant.STOCK_COUNT + sid, String.valueOf(stock.getCount())); 231 | RedisPoolUtil.set(RedisKeysConstant.STOCK_SALE + sid, String.valueOf(stock.getSale())); 232 | RedisPoolUtil.set(RedisKeysConstant.STOCK_VERSION + sid, String.valueOf(stock.getVersion())); 233 | } 234 | } 235 | ``` 236 | 237 | #### 缓存和数据一致性 238 | 239 | 缓存和 DB 的一致性是一个讨论很多的问题,推荐看参考中的 [使用缓存的正确姿势](),首先看下先更新数据库,再更新缓存策略,假设 A、B 两个线程,A 成功更新数据,在要更新缓存时,A 的时间片用完了,B 更新了数据库接着更新了缓存,这是 CPU 再分配给 A,则 A 又更新了缓存,这种情况下缓存中就是脏数据,具体逻辑如下图所示: 240 | 241 | ![]() 242 | 243 | 那么,如果避免这个问题呢?就是缓存不做更新,仅做删除,先更新数据库再删除缓存。对于上面的问题,A 更新了数据库,还没来得及删除缓存,B 又更新了数据库,接着删除了缓存,然后 A 删除了缓存,这样只有下次缓存未命中时,才会从数据库中重建缓存,避免了脏数据。但是,也会有极端情况出现脏数据,A 做查询操作,没有命中缓存,从数据库中查询,但是还没来得及更新缓存,B 就更新了数据库,接着删除了缓存,然后 A 又重建了缓存,这时 A 中的就是脏数据,如下图所示。但是这种极端情况需要数据库的写操作前进入数据库,又晚于写操作删除缓存来更新缓存,发生的概率极其小,不过为了避免这种情况,可以为缓存设置过期时间。 244 | 245 | ![]() 246 | 247 | 安装先更新数据库再删除缓存的策略来执行,代码如下所示: 248 | 249 | ```java 250 | @Override 251 | public int createOrderWithLimitAndRedis(int sid) throws Exception { 252 | // 校验库存,从 Redis 中获取 253 | Stock stock = checkStockWithRedis(sid); 254 | // 乐观锁更新库存和Redis 255 | saleStockOptimsticWithRedis(stock); 256 | // 创建订单 257 | int res = createOrder(stock); 258 | return res; 259 | } 260 | // Redis 校验库存 261 | private Stock checkStockWithRedisWithDel(int sid) throws Exception { 262 | Integer count = null; 263 | Integer sale = null; 264 | Integer version = null; 265 | List data = RedisPoolUtil.listGet(RedisKeysConstant.STOCK + sid); 266 | if (data.size() == 0) { 267 | // Redis 不存在,先从数据库中获取,再放到 Redis 中 268 | Stock newStock = stockService.getStockById(sid); 269 | RedisPoolUtil.listPut(RedisKeysConstant.STOCK + newStock.getId(), String.valueOf(newStock.getCount()), 270 | String.valueOf(newStock.getSale()), String.valueOf(newStock.getVersion())); 271 | count = newStock.getCount(); 272 | sale = newStock.getSale(); 273 | version = newStock.getVersion(); 274 | } else { 275 | count = Integer.parseInt(data.get(0)); 276 | sale = Integer.parseInt(data.get(1)); 277 | version = Integer.parseInt(data.get(2)); 278 | } 279 | if (count < 1) { 280 | log.info("库存不足"); 281 | throw new RuntimeException("库存不足 Redis currentCount: " + sale); 282 | } 283 | Stock stock = new Stock(); 284 | stock.setId(sid); 285 | stock.setCount(count); 286 | stock.setSale(sale); 287 | stock.setVersion(version); 288 | // 此处应该是热更新,但是在数据库中只有一个商品,所以直接赋值 289 | stock.setName("手机"); 290 | return stock; 291 | } 292 | private void saleStockOptimsticWithRedisWithDel(Stock stock) throws Exception { 293 | // 乐观锁更新数据库 294 | int res = stockService.updateStockByOptimistic(stock); 295 | // 删除缓存,应该使用 Redis 事务 296 | RedisPoolUtil.del(RedisKeysConstant.STOCK + stock.getId()); 297 | log.info("删除缓存成功"); 298 | if (res == 0) { 299 | throw new RuntimeException("并发更新库存失败"); 300 | } 301 | } 302 | ``` 303 | 304 | 在 Jmeter 压力测试中,并发效果并不好,跟前面的限流并发差不多,观察 Redis 中的数据看出,由于每次都删除缓存,因此导致多次缓存都不能命中,能命中缓存的次数很少,因此这种方案并不可取。 305 | 306 | 考虑到使用乐观锁更新数据库,因此在使用先更新数据库再更新缓存的策略中,实际情况如下所示 307 | 308 | ![]() 309 | 310 | 在 A 未更新缓存阶段,虽然 B 从缓存中获取到的库存信息脏数据,但是,乐观锁使得 B 在更新数据库时失败,这时 A 又更新了缓存,则保证了数据的最终一致性,并且由于缓存一直都可以命中,对并发量的提升也是很显著的。 311 | 312 | ```java 313 | @Override 314 | public int createOrderWithLimitAndRedis(int sid) throws Exception { 315 | // 校验库存,从 Redis 中获取 316 | Stock stock = checkStockWithRedis(sid); 317 | // 乐观锁更新库存和Redis 318 | saleStockOptimsticWithRedis(stock); 319 | // 创建订单 320 | int res = createOrder(stock); 321 | return res; 322 | } 323 | // Redis 中校验库存 324 | private Stock checkStockWithRedis(int sid) throws Exception { 325 | Integer count = Integer.parseInt(RedisPoolUtil.get(RedisKeysConstant.STOCK_COUNT + sid)); 326 | Integer sale = Integer.parseInt(RedisPoolUtil.get(RedisKeysConstant.STOCK_SALE + sid)); 327 | Integer version = Integer.parseInt(RedisPoolUtil.get(RedisKeysConstant.STOCK_VERSION + sid)); 328 | if (count < 1) { 329 | log.info("库存不足"); 330 | throw new RuntimeException("库存不足 Redis currentCount: " + sale); 331 | } 332 | Stock stock = new Stock(); 333 | stock.setId(sid); 334 | stock.setCount(count); 335 | stock.setSale(sale); 336 | stock.setVersion(version); 337 | // 此处应该是热更新,但是在数据库中只有一个商品,所以直接赋值 338 | stock.setName("手机"); 339 | 340 | return stock; 341 | } 342 | // 更新 DB 和 Redis 343 | private void saleStockOptimsticWithRedis(Stock stock) throws Exception { 344 | int res = stockService.updateStockByOptimistic(stock); 345 | if (res == 0){ 346 | throw new RuntimeException("并发更新库存失败") ; 347 | } 348 | // 更新 Redis 349 | StockWithRedis.updateStockWithRedis(stock); 350 | } 351 | // Redis 多个写入操作的事务 352 | public static void updateStockWithRedis(Stock stock) { 353 | Jedis jedis = null; 354 | try { 355 | jedis = RedisPool.getJedis(); 356 | // 开始事务 357 | Transaction transaction = jedis.multi(); 358 | // 事务操作 359 | RedisPoolUtil.decr(RedisKeysConstant.STOCK_COUNT + stock.getId()); 360 | RedisPoolUtil.incr(RedisKeysConstant.STOCK_SALE + stock.getId()); 361 | RedisPoolUtil.incr(RedisKeysConstant.STOCK_VERSION + stock.getId()); 362 | // 结束事务 363 | List list = transaction.exec(); 364 | } catch (Exception e) { 365 | log.error("updateStock 获取 Jedis 实例失败:", e); 366 | } finally { 367 | RedisPool.jedisPoolClose(jedis); 368 | } 369 | } 370 | ``` 371 | 372 | #### 发现热点数据 373 | 374 | 热点数据就是用户的热点请求对应的数据,分成静态热点数据和动态热点数据。 375 | 376 | 静态热点数据就是能够提前预测的数据,比如约定商品 A、B、C 参与秒杀,则可以提前对商品进行标记处理。动态热点数据就是不能被提前预测的,比如在商家在抖音上投放广告,导致商品短时间内被大量购买,临时产生热点数据。对于动态热点数据,最主要的就是能够提前预测和发现,以便于及时处理,这里给出[极客时间:许令波 - 如何设计一个秒杀系统]()中对于热点数据发现系统的实现: 377 | 378 | 1. 构建一个异步的系统,它可以收集交易链路上各个环节中的中间件产品的热点 Key 379 | 2. 建立一个热点上报和可以按照需求订阅的热点服务的下发规范,主要目的是通过交易链路上各个系统(包括详情、购物车、交易、优惠、库存、物流等)访问的时间差,把上游已经发现的热点透传给下游系统,提前做好保护。 380 | 3. 将上游系统收集的热点数据发送到热点服务台,然后下游系统(如交易系统)就会知道哪些商品会被频繁调用,然后做热点保护。 381 | 382 | ![]() 383 | 384 | 我们通过部署在每台机器上的 Agent 把日志汇总到聚合和分析集群中,然后把符合一定规则的热点数据,通过订阅分发系统再推送到相应的系统中。你可以是把热点数据填充到 Cache 中,或者直接推送到应用服务器的内存中,还可以对这些数据进行拦截,总之下游系统可以订阅这些数据,然后根据自己的需求决定如何处理这些数据。 385 | 386 | 对于热点数据,除了上文所提到的缓存,还要进行隔离和限制,比如把热点商品限制在一个请求队列里,防止因某些热点商品占用太多的服务器资源,而使其他请求始终得不到服务器的处理资源;将这种热点数据隔离出来,不要让 1% 的请求影响到另外的 99% 387 | 388 | ### 4. Kafka 异步 389 | 390 | 服务器的资源是恒定的,你用或者不用它的处理能力都是一样的,所以出现峰值的话,很容易导致忙到处理不过来,闲的时候却又没有什么要处理,因此可以通过削峰来延缓用户请求的发出,让服务端处理变得更加平稳。 391 | 392 | 项目中采用的是用消息队列 Kafka 来缓冲瞬时流量,将同步的直接调用转成异步的间接推送,中间通过一个队列在一端承接瞬时的流量洪峰,在另一端平滑地将消息推送出去。 393 | 394 | ![]() 395 | 396 | 关于 Kafka 的学习,推荐[朱小厮的博客]()和博主的书《深入理解 Kafka:核心设计与实践原理》,向 Kafka 发送消息和从 Kafka 拉取消息需要对消息进行序列化处理,这里采用的是`Gson`框架 397 | 398 | ```java 399 | // 向 Kafka 发送消息 400 | public void createOrderWithLimitAndRedisAndKafka(int sid) throws Exception { 401 | // 校验库存 402 | Stock stock = checkStockWithRedis(sid); 403 | // 下单请求发送至 kafka,需要序列化 stock 404 | kafkaTemplate.send(kafkaTopic, gson.toJson(stock)); 405 | log.info("消息发送至 Kafka 成功"); 406 | } 407 | // 监听器从 Kafka 拉取消息 408 | public class ConsumerListen { 409 | 410 | private Gson gson = new GsonBuilder().create(); 411 | 412 | @Autowired 413 | private OrderService orderService; 414 | 415 | @KafkaListener(topics = "SECONDS-KILL-TOPIC") 416 | public void listen(ConsumerRecord record) throws Exception { 417 | Optional kafkaMessage = Optional.ofNullable(record.value()); 418 | // Object -> String 419 | String message = (String) kafkaMessage.get(); 420 | // 反序列化 421 | Stock stock = gson.fromJson((String) message, Stock.class); 422 | // 创建订单 423 | orderService.consumerTopicToCreateOrderWithKafka(stock); 424 | } 425 | } 426 | // Kafka 消费消息执行创建订单业务 427 | public int consumerTopicToCreateOrderWithKafka(Stock stock) throws Exception { 428 | // 乐观锁更新库存和 Redis 429 | saleStockOptimsticWithRedis(stock); 430 | int res = createOrder(stock); 431 | if (res == 1) { 432 | log.info("Kafka 消费 Topic 创建订单成功"); 433 | } else { 434 | log.info("Kafka 消费 Topic 创建订单失败"); 435 | } 436 | 437 | return res; 438 | } 439 | ``` 440 | 441 | ### 5. Nginx 负载均衡 442 | 443 | 单台服务器的处理性能是有瓶颈的,当并发量十分大时,无论怎么优化都满足不了需求,这时候就需要增加一台服务器分担原有服务器的访问压力,通过负载均衡服务器 Nginx 可以将来自用户的访问请求发到应用服务器集群中的任何一台机器 444 | 445 | Nginx 配置如下: 446 | 447 | 在项目的配置文件 application.properties 中分别设置两个应用的端口号如 8888 和 9999 。 448 | ``` 449 | server.port=8888 450 | server.port=9999 451 | ``` 452 | 453 | 然后进入nginx/conf文件目录将nginx.conf配置文件中的http部分修改为如下代码: 454 | ``` 455 | http { 456 | include mime.types; 457 | default_type application/octet-stream; 458 | 459 | #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 460 | # '$status $body_bytes_sent "$http_referer" ' 461 | # '"$http_user_agent" "$http_x_forwarded_for"'; 462 | 463 | #access_log logs/access.log main; 464 | 465 | sendfile on; 466 | #tcp_nopush on; 467 | 468 | #keepalive_timeout 0; 469 | keepalive_timeout 65; 470 | 471 | #gzip on; 472 | 473 | upstream server_miaosha{ 474 | server 127.0.0.1:8888 weight=1; 475 | server 127.0.0.1:9999 weight=1; 476 | } 477 | 478 | server { 479 | listen 80; 480 | server_name localhost; 481 | 482 | #charset koi8-r; 483 | 484 | #access_log logs/host.access.log main; 485 | 486 | location / { 487 | #root html; 488 | #index index.html index.htm; 489 | set $xheader $remote_addr; 490 | if ( $http_x_forwarded_for != '' ){ 491 | set $xheader $http_x_forwarded_for; 492 | } 493 | proxy_set_header X-Real-IP $xheader; 494 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 495 | proxy_set_header Host $http_host; 496 | proxy_redirect off; 497 | proxy_http_version 1.1; 498 | proxy_set_header Connection ""; 499 | proxy_pass http://server_miaosha; 500 | } 501 | 502 | #error_page 404 /404.html; 503 | ``` 504 | 权重weight可以根据个人需求进行设置,本文均设置为 1 ,表示访问 IP + 80 端口时两个应用按 1:1 进行轮询。 505 | 506 | ## 数据库建表 507 | 508 | ```mysql 509 | CREATE TABLE `stock` ( 510 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 511 | `name` varchar(50) NOT NULL DEFAULT '' COMMENT '名称', 512 | `count` int(11) NOT NULL COMMENT '库存', 513 | `sale` int(11) NOT NULL COMMENT '已售', 514 | `version` int(11) NOT NULL COMMENT '乐观锁,版本号', 515 | PRIMARY KEY (`id`) 516 | ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; 517 | CREATE TABLE `stock_order` ( 518 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 519 | `sid` int(11) NOT NULL COMMENT '库存ID', 520 | `name` varchar(30) NOT NULL DEFAULT '' COMMENT '商品名称', 521 | `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间', 522 | PRIMARY KEY (`id`) 523 | ) ENGINE=InnoDB AUTO_INCREMENT=55 DEFAULT CHARSET=utf8; 524 | ``` 525 | 526 | ## 参考 527 | 528 | >- [极客时间:许令波 - 如何设计一个秒杀系统]() 529 | >- [crossoverjie:SSM(十八)秒杀架构实践]() 530 | >- [秒杀系统优化方案(下)吐血整理]() 531 | >- [电商网站秒杀与抢购的系统架构](http://www.codeceo.com/article/spike-system-artch.html) 532 | >- [使用缓存的正确姿势]() 533 | >- [SpringBoot Kafka 整合使用]() 534 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # https://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Mingw, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | # TODO classpath? 118 | fi 119 | 120 | if [ -z "$JAVA_HOME" ]; then 121 | javaExecutable="`which javac`" 122 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 123 | # readlink(1) is not available as standard on Solaris 10. 124 | readLink=`which readlink` 125 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 126 | if $darwin ; then 127 | javaHome="`dirname \"$javaExecutable\"`" 128 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 129 | else 130 | javaExecutable="`readlink -f \"$javaExecutable\"`" 131 | fi 132 | javaHome="`dirname \"$javaExecutable\"`" 133 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 134 | JAVA_HOME="$javaHome" 135 | export JAVA_HOME 136 | fi 137 | fi 138 | fi 139 | 140 | if [ -z "$JAVACMD" ] ; then 141 | if [ -n "$JAVA_HOME" ] ; then 142 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 143 | # IBM's JDK on AIX uses strange locations for the executables 144 | JAVACMD="$JAVA_HOME/jre/sh/java" 145 | else 146 | JAVACMD="$JAVA_HOME/bin/java" 147 | fi 148 | else 149 | JAVACMD="`which java`" 150 | fi 151 | fi 152 | 153 | if [ ! -x "$JAVACMD" ] ; then 154 | echo "Error: JAVA_HOME is not defined correctly." >&2 155 | echo " We cannot execute $JAVACMD" >&2 156 | exit 1 157 | fi 158 | 159 | if [ -z "$JAVA_HOME" ] ; then 160 | echo "Warning: JAVA_HOME environment variable is not set." 161 | fi 162 | 163 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 164 | 165 | # traverses directory structure from process work directory to filesystem root 166 | # first directory with .mvn subdirectory is considered project base directory 167 | find_maven_basedir() { 168 | 169 | if [ -z "$1" ] 170 | then 171 | echo "Path not specified to find_maven_basedir" 172 | return 1 173 | fi 174 | 175 | basedir="$1" 176 | wdir="$1" 177 | while [ "$wdir" != '/' ] ; do 178 | if [ -d "$wdir"/.mvn ] ; then 179 | basedir=$wdir 180 | break 181 | fi 182 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 183 | if [ -d "${wdir}" ]; then 184 | wdir=`cd "$wdir/.."; pwd` 185 | fi 186 | # end of workaround 187 | done 188 | echo "${basedir}" 189 | } 190 | 191 | # concatenates all lines of a file 192 | concat_lines() { 193 | if [ -f "$1" ]; then 194 | echo "$(tr -s '\n' ' ' < "$1")" 195 | fi 196 | } 197 | 198 | BASE_DIR=`find_maven_basedir "$(pwd)"` 199 | if [ -z "$BASE_DIR" ]; then 200 | exit 1; 201 | fi 202 | 203 | ########################################################################################## 204 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 205 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 206 | ########################################################################################## 207 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 208 | if [ "$MVNW_VERBOSE" = true ]; then 209 | echo "Found .mvn/wrapper/maven-wrapper.jar" 210 | fi 211 | else 212 | if [ "$MVNW_VERBOSE" = true ]; then 213 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 214 | fi 215 | jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" 216 | while IFS="=" read key value; do 217 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 218 | esac 219 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 220 | if [ "$MVNW_VERBOSE" = true ]; then 221 | echo "Downloading from: $jarUrl" 222 | fi 223 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 224 | 225 | if command -v wget > /dev/null; then 226 | if [ "$MVNW_VERBOSE" = true ]; then 227 | echo "Found wget ... using wget" 228 | fi 229 | wget "$jarUrl" -O "$wrapperJarPath" 230 | elif command -v curl > /dev/null; then 231 | if [ "$MVNW_VERBOSE" = true ]; then 232 | echo "Found curl ... using curl" 233 | fi 234 | curl -o "$wrapperJarPath" "$jarUrl" 235 | else 236 | if [ "$MVNW_VERBOSE" = true ]; then 237 | echo "Falling back to using Java to download" 238 | fi 239 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 240 | if [ -e "$javaClass" ]; then 241 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 242 | if [ "$MVNW_VERBOSE" = true ]; then 243 | echo " - Compiling MavenWrapperDownloader.java ..." 244 | fi 245 | # Compiling the Java class 246 | ("$JAVA_HOME/bin/javac" "$javaClass") 247 | fi 248 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 249 | # Running the downloader 250 | if [ "$MVNW_VERBOSE" = true ]; then 251 | echo " - Running MavenWrapperDownloader.java ..." 252 | fi 253 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 254 | fi 255 | fi 256 | fi 257 | fi 258 | ########################################################################################## 259 | # End of extension 260 | ########################################################################################## 261 | 262 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 263 | if [ "$MVNW_VERBOSE" = true ]; then 264 | echo $MAVEN_PROJECTBASEDIR 265 | fi 266 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 267 | 268 | # For Cygwin, switch paths to Windows format before running java 269 | if $cygwin; then 270 | [ -n "$M2_HOME" ] && 271 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 272 | [ -n "$JAVA_HOME" ] && 273 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 274 | [ -n "$CLASSPATH" ] && 275 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 276 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 277 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 278 | fi 279 | 280 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 281 | 282 | exec "$JAVACMD" \ 283 | $MAVEN_OPTS \ 284 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 285 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 286 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 287 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 50 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" 124 | FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO ( 125 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 126 | ) 127 | 128 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 129 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 130 | if exist %WRAPPER_JAR% ( 131 | echo Found %WRAPPER_JAR% 132 | ) else ( 133 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 134 | echo Downloading from: %DOWNLOAD_URL% 135 | powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')" 136 | echo Finished downloading %WRAPPER_JAR% 137 | ) 138 | @REM End of extension 139 | 140 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 141 | if ERRORLEVEL 1 goto error 142 | goto end 143 | 144 | :error 145 | set ERROR_CODE=1 146 | 147 | :end 148 | @endlocal & set ERROR_CODE=%ERROR_CODE% 149 | 150 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 151 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 152 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 153 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 154 | :skipRcPost 155 | 156 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 157 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 158 | 159 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 160 | 161 | exit /B %ERROR_CODE% 162 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 1.5.7.RELEASE 9 | 10 | 11 | com.daydreamdev 12 | seconds-kill 13 | 0.0.1-SNAPSHOT 14 | seconds-kill 15 | Demo project for Spring Boot 16 | 17 | 18 | 1.8 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-web 25 | 26 | 27 | 28 | org.projectlombok 29 | lombok 30 | true 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-test 35 | test 36 | 37 | 38 | 39 | 40 | org.slf4j 41 | slf4j-api 42 | 43 | 44 | 45 | ch.qos.logback 46 | logback-core 47 | 48 | 49 | 50 | ch.qos.logback 51 | logback-classic 52 | 1.2.3 53 | 54 | 55 | 56 | 57 | log4j 58 | log4j 59 | 1.2.17 60 | 61 | 62 | 63 | org.mybatis.spring.boot 64 | mybatis-spring-boot-starter 65 | 1.1.1 66 | 67 | 68 | 69 | com.alibaba 70 | druid-spring-boot-starter 71 | 72 | 73 | 74 | com.alibaba 75 | druid-spring-boot-starter 76 | 1.1.0 77 | 78 | 79 | 80 | mysql 81 | mysql-connector-java 82 | 83 | 84 | 85 | 86 | org.springframework.data 87 | spring-data-redis 88 | 89 | 90 | 91 | redis.clients 92 | jedis 93 | 2.9.0 94 | 95 | 96 | 97 | 98 | org.springframework.kafka 99 | spring-kafka 100 | 101 | 102 | 103 | 104 | com.alibaba 105 | fastjson 106 | 1.2.31 107 | 108 | 109 | 110 | com.google.code.gson 111 | gson 112 | 2.8.4 113 | 114 | 115 | 116 | 117 | 118 | 119 | org.springframework.boot 120 | spring-boot-maven-plugin 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /secondkill.log.2019-06-10.0.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daydreamdev/seconds-kill/57a31cb2c15b082bc84472b479e17534ee9ace53/secondkill.log.2019-06-10.0.gz -------------------------------------------------------------------------------- /seconds-kill.iml: -------------------------------------------------------------------------------- 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 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /src/main/java/com/daydreamdev/secondskill/Kafka/ConsumerListen.java: -------------------------------------------------------------------------------- 1 | package com.daydreamdev.secondskill.Kafka; 2 | 3 | import com.daydreamdev.secondskill.pojo.Stock; 4 | import com.daydreamdev.secondskill.service.api.OrderService; 5 | import com.google.gson.Gson; 6 | import com.google.gson.GsonBuilder; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.apache.kafka.clients.consumer.ConsumerRecord; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.kafka.annotation.KafkaListener; 11 | import org.springframework.stereotype.Component; 12 | 13 | import java.util.Optional; 14 | 15 | 16 | /** 17 | * @auther G.Fukang 18 | * @date 6/12 19:17 19 | */ 20 | @Slf4j 21 | @Component 22 | public class ConsumerListen { 23 | 24 | private Gson gson = new GsonBuilder().create(); 25 | 26 | @Autowired 27 | private OrderService orderService; 28 | 29 | @KafkaListener(topics = "SECONDS-KILL-TOPIC") 30 | public void listen(ConsumerRecord record) throws Exception { 31 | Optional kafkaMessage = Optional.ofNullable(record.value()); 32 | // Object -> String 33 | String message = (String) kafkaMessage.get(); 34 | // 反序列化 35 | Stock stock = gson.fromJson((String) message, Stock.class); 36 | // 创建订单 37 | orderService.consumerTopicToCreateOrderWithKafka(stock); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/daydreamdev/secondskill/SecondsKillApplication.java: -------------------------------------------------------------------------------- 1 | package com.daydreamdev.secondskill; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.context.annotation.ComponentScan; 7 | import org.springframework.kafka.annotation.EnableKafka; 8 | import org.springframework.transaction.annotation.EnableTransactionManagement; 9 | 10 | @Slf4j 11 | @SpringBootApplication 12 | @EnableTransactionManagement 13 | @EnableKafka 14 | @ComponentScan("com.daydreamdev.secondskill") 15 | public class SecondsKillApplication { 16 | 17 | /** 18 | * @author G.Fukang 19 | * @date: 6/7 20:49 20 | */ 21 | public static void main(String[] args) { 22 | SpringApplication.run(SecondsKillApplication.class, args); 23 | /* new SpringApplicationBuilder(SecondsKillApplication.class). 24 | listeners(new ApplicationPidFileWriter()) 25 | .run(args); 26 | Consumer consumer = SpringBeanFactory.getBean(Consumer.class); 27 | new Thread(consumer, "消费者").start(); 28 | log.info("消费者线程启动成功!");*/ 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/daydreamdev/secondskill/common/Limit/RedisLimit.java: -------------------------------------------------------------------------------- 1 | package com.daydreamdev.secondskill.common.limit; 2 | 3 | import com.daydreamdev.secondskill.common.utils.RedisPool; 4 | import com.daydreamdev.secondskill.common.utils.ScriptUtil; 5 | import lombok.extern.slf4j.Slf4j; 6 | import redis.clients.jedis.Jedis; 7 | 8 | import java.util.Collections; 9 | 10 | /** 11 | * @auther G.Fukang 12 | * @date 6/7 21:45 13 | */ 14 | @Slf4j 15 | public class RedisLimit { 16 | 17 | private static final int FAIL_CODE = 0; 18 | 19 | private static Integer limit = 5; 20 | 21 | /** 22 | * Redis 限流 23 | */ 24 | public static Boolean limit() { 25 | Jedis jedis = null; 26 | Object result = null; 27 | try { 28 | // 获取 jedis 实例 29 | jedis = RedisPool.getJedis(); 30 | // 解析 Lua 文件 31 | String script = ScriptUtil.getScript("limit.lua"); 32 | // 请求限流 33 | String key = String.valueOf(System.currentTimeMillis() / 1000); 34 | // 计数限流 35 | result = jedis.eval(script, Collections.singletonList(key), Collections.singletonList(String.valueOf(limit))); 36 | if (FAIL_CODE != (Long) result) { 37 | log.info("成功获取令牌"); 38 | return true; 39 | } 40 | } catch (Exception e) { 41 | log.error("limit 获取 Jedis 实例失败:", e); 42 | } finally { 43 | RedisPool.jedisPoolClose(jedis); 44 | } 45 | return false; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/daydreamdev/secondskill/common/RedisPreheatRunner.java: -------------------------------------------------------------------------------- 1 | package com.daydreamdev.secondskill.common; 2 | 3 | import com.daydreamdev.secondskill.common.stockWithRedis.RedisKeysConstant; 4 | import com.daydreamdev.secondskill.common.utils.RedisPoolUtil; 5 | import com.daydreamdev.secondskill.pojo.Stock; 6 | import com.daydreamdev.secondskill.service.api.StockService; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.ApplicationArguments; 9 | import org.springframework.boot.ApplicationRunner; 10 | import org.springframework.stereotype.Component; 11 | 12 | /** 13 | * @auther G.Fukang 14 | * @date 6/8 13:41 15 | * 缓存预热,在 Spring Boot 启动后立即执行此方法 16 | */ 17 | @Component 18 | public class RedisPreheatRunner implements ApplicationRunner { 19 | 20 | @Autowired 21 | private StockService stockService; 22 | 23 | @Override 24 | public void run(ApplicationArguments args) throws Exception { 25 | // 从数据库中查询热卖商品 26 | Stock stock = stockService.getStockById(1); 27 | 28 | // 删除旧缓存 29 | RedisPoolUtil.del(RedisKeysConstant.STOCK_COUNT + stock.getCount()); 30 | RedisPoolUtil.del(RedisKeysConstant.STOCK_SALE + stock.getSale()); 31 | RedisPoolUtil.del(RedisKeysConstant.STOCK_VERSION + stock.getVersion()); 32 | //缓存预热 33 | int sid = stock.getId(); 34 | RedisPoolUtil.set(RedisKeysConstant.STOCK_COUNT + sid, String.valueOf(stock.getCount())); 35 | RedisPoolUtil.set(RedisKeysConstant.STOCK_SALE + sid, String.valueOf(stock.getSale())); 36 | RedisPoolUtil.set(RedisKeysConstant.STOCK_VERSION + sid, String.valueOf(stock.getVersion())); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/daydreamdev/secondskill/common/StockWithRedis/StockWithRedis.java: -------------------------------------------------------------------------------- 1 | package com.daydreamdev.secondskill.common.stockWithRedis; 2 | 3 | import com.daydreamdev.secondskill.common.utils.RedisPool; 4 | import com.daydreamdev.secondskill.common.utils.RedisPoolUtil; 5 | import com.daydreamdev.secondskill.pojo.Stock; 6 | import lombok.extern.slf4j.Slf4j; 7 | import redis.clients.jedis.Jedis; 8 | import redis.clients.jedis.Transaction; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * @auther G.Fukang 14 | * @date 6/8 21:47 15 | */ 16 | @Slf4j 17 | public class StockWithRedis { 18 | 19 | /** 20 | * Redis 事务保证库存更新 21 | * 捕获异常后应该删除缓存 22 | */ 23 | public static void updateStockWithRedis(Stock stock) { 24 | Jedis jedis = null; 25 | try { 26 | jedis = RedisPool.getJedis(); 27 | // 开始事务 28 | Transaction transaction = jedis.multi(); 29 | // 事务操作 30 | RedisPoolUtil.decr(RedisKeysConstant.STOCK_COUNT + stock.getId()); 31 | RedisPoolUtil.incr(RedisKeysConstant.STOCK_SALE + stock.getId()); 32 | RedisPoolUtil.incr(RedisKeysConstant.STOCK_VERSION + stock.getId()); 33 | // 结束事务 34 | List list = transaction.exec(); 35 | } catch (Exception e) { 36 | log.error("updateStock 获取 Jedis 实例失败:", e); 37 | } finally { 38 | RedisPool.jedisPoolClose(jedis); 39 | } 40 | } 41 | 42 | /** 43 | * 重置缓存 44 | */ 45 | public static void initRedisBefore() { 46 | Jedis jedis = null; 47 | try { 48 | jedis = RedisPool.getJedis(); 49 | // 开始事务 50 | Transaction transaction = jedis.multi(); 51 | // 事务操作 52 | RedisPoolUtil.set(RedisKeysConstant.STOCK_COUNT + 1, "50"); 53 | RedisPoolUtil.set(RedisKeysConstant.STOCK_SALE + 1, "0"); 54 | RedisPoolUtil.set(RedisKeysConstant.STOCK_VERSION + 1, "0"); 55 | // 结束事务 56 | List list = transaction.exec(); 57 | } catch (Exception e) { 58 | log.error("initRedis 获取 Jedis 实例失败:", e); 59 | } finally { 60 | RedisPool.jedisPoolClose(jedis); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/daydreamdev/secondskill/common/stockWithRedis/RedisKeysConstant.java: -------------------------------------------------------------------------------- 1 | package com.daydreamdev.secondskill.common.stockWithRedis; 2 | 3 | /** 4 | * @auther G.Fukang 5 | * @date 6/8 10:59 6 | */ 7 | public class RedisKeysConstant { 8 | 9 | /** 10 | * 库存值 11 | */ 12 | public final static String STOCK_COUNT = "stock_count_"; 13 | 14 | /** 15 | * 库存值 16 | */ 17 | public final static String STOCK_SALE = "stock_sale_"; 18 | 19 | /** 20 | * 库存值 21 | */ 22 | public final static String STOCK_VERSION = "stock_version_"; 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/daydreamdev/secondskill/common/utils/RedisPool.java: -------------------------------------------------------------------------------- 1 | package com.daydreamdev.secondskill.common.utils; 2 | 3 | import org.springframework.stereotype.Component; 4 | import redis.clients.jedis.Jedis; 5 | import redis.clients.jedis.JedisPool; 6 | import redis.clients.jedis.JedisPoolConfig; 7 | 8 | /** 9 | * @auther G.Fukang 10 | * @date 6/7 21:30 11 | */ 12 | @Component 13 | public class RedisPool { 14 | 15 | private static JedisPool pool; 16 | 17 | private static Integer maxTotal = 300; 18 | 19 | private static Integer maxIdle = 100; 20 | 21 | private static Integer maxWait = 10000; 22 | 23 | private static Boolean testOnBorrow = true; 24 | 25 | private static String redisIP= "119.3.214.253"; 26 | 27 | private static Integer redisPort = 6379; 28 | 29 | private static void initPool() { 30 | JedisPoolConfig config = new JedisPoolConfig(); 31 | 32 | config.setMaxTotal(maxTotal); 33 | config.setMaxIdle(maxIdle); 34 | config.setTestOnBorrow(testOnBorrow); 35 | config.setBlockWhenExhausted(true); 36 | config.setMaxWaitMillis(maxWait); 37 | 38 | pool = new JedisPool(config, redisIP, redisPort, 1000 * 2); 39 | } 40 | 41 | // 类加载到 jvm 时直接初始化连接池 42 | static { 43 | initPool(); 44 | } 45 | 46 | public static Jedis getJedis() { 47 | return pool.getResource(); 48 | } 49 | 50 | public static void jedisPoolClose(Jedis jedis) { 51 | if (jedis != null) { 52 | jedis.close(); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/daydreamdev/secondskill/common/utils/RedisPoolUtil.java: -------------------------------------------------------------------------------- 1 | package com.daydreamdev.secondskill.common.utils; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import redis.clients.jedis.Jedis; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * @auther G.Fukang 10 | * @date 6/8 10:07 11 | */ 12 | @Slf4j 13 | public class RedisPoolUtil { 14 | 15 | /** 16 | * 设置 key - value 值 17 | * 18 | * @param key 19 | * @param value 20 | */ 21 | public static String set(String key, String value) { 22 | Jedis jedis = null; 23 | String result = null; 24 | 25 | try { 26 | jedis = RedisPool.getJedis(); 27 | result = jedis.set(key, value); 28 | } catch (Exception e) { 29 | log.error("set key:{} value:{} error", key, value, e); 30 | } finally { 31 | RedisPool.jedisPoolClose(jedis); 32 | } 33 | return result; 34 | } 35 | 36 | /** 37 | * 获取 key - value 值 38 | * 39 | * @param key 40 | */ 41 | public static String get(String key) { 42 | Jedis jedis = null; 43 | String result = null; 44 | 45 | try { 46 | jedis = RedisPool.getJedis(); 47 | result = jedis.get(key); 48 | } catch (Exception e) { 49 | log.error("get key:{} error", key, e); 50 | } finally { 51 | RedisPool.jedisPoolClose(jedis); 52 | } 53 | return result; 54 | } 55 | 56 | /** 57 | * 删除 key - value 值 58 | * 59 | * @param key 60 | */ 61 | public static Long del(String key) { 62 | Jedis jedis = null; 63 | Long result = null; 64 | try { 65 | jedis = RedisPool.getJedis(); 66 | result = jedis.del(key); 67 | } catch (Exception e) { 68 | log.error("del key:{} error", key, e); 69 | } finally { 70 | RedisPool.jedisPoolClose(jedis); 71 | } 72 | return result; 73 | } 74 | 75 | /** 76 | * key - value 自增 77 | */ 78 | public static Long incr (String key) { 79 | Jedis jedis = null; 80 | Long result = null; 81 | try { 82 | jedis = RedisPool.getJedis(); 83 | result = jedis.incr(key); 84 | } catch (Exception e) { 85 | log.error("listGet key:{} error", key, e); 86 | } finally { 87 | RedisPool.jedisPoolClose(jedis); 88 | } 89 | return result; 90 | } 91 | 92 | /** 93 | * key - value 自减 94 | */ 95 | public static Long decr (String key) { 96 | Jedis jedis = null; 97 | Long result = null; 98 | try { 99 | jedis = RedisPool.getJedis(); 100 | result = jedis.decr(key); 101 | } catch (Exception e) { 102 | log.error("listGet key:{} error", key, e); 103 | } finally { 104 | RedisPool.jedisPoolClose(jedis); 105 | } 106 | return result; 107 | } 108 | 109 | /** 110 | * List - get 操作 111 | */ 112 | public static List listGet(String key) { 113 | Jedis jedis = null; 114 | List result = null; 115 | try { 116 | jedis = RedisPool.getJedis(); 117 | result = jedis.lrange(key, 0, -1); 118 | } catch (Exception e) { 119 | log.error("listGet key:{} error", key, e); 120 | } finally { 121 | RedisPool.jedisPoolClose(jedis); 122 | } 123 | return result; 124 | } 125 | 126 | /** 127 | * List - put 操作 128 | */ 129 | public static Long listPut(String key, String count, String sale, String version) { 130 | Jedis jedis = null; 131 | Long result = null; 132 | try { 133 | jedis = RedisPool.getJedis(); 134 | result = jedis.lpush(key, version, sale, count); 135 | } catch (Exception e) { 136 | log.error("listPut key:{} error", key, e); 137 | } finally { 138 | RedisPool.jedisPoolClose(jedis); 139 | } 140 | return result; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/main/java/com/daydreamdev/secondskill/common/utils/ScriptUtil.java: -------------------------------------------------------------------------------- 1 | package com.daydreamdev.secondskill.common.utils; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.io.InputStreamReader; 7 | import java.util.Arrays; 8 | 9 | /** 10 | * @auther G.Fukang 11 | * @date 6/7 20:42 12 | */ 13 | public class ScriptUtil { 14 | 15 | // 解析 lua 16 | public static String getScript(String path) { 17 | StringBuilder stringBuilder = new StringBuilder(); 18 | 19 | InputStream inputStream = ScriptUtil.class.getClassLoader().getResourceAsStream(path); 20 | 21 | try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) { 22 | String str; 23 | while ((str = bufferedReader.readLine()) != null) { 24 | stringBuilder.append(str).append(System.lineSeparator()); 25 | } 26 | } catch (IOException e) { 27 | System.out.println(Arrays.toString(e.getStackTrace())); 28 | } 29 | 30 | return stringBuilder.toString(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/daydreamdev/secondskill/controller/IndexController.java: -------------------------------------------------------------------------------- 1 | package com.daydreamdev.secondskill.controller; 2 | 3 | import com.daydreamdev.secondskill.common.limit.RedisLimit; 4 | import com.daydreamdev.secondskill.common.stockWithRedis.StockWithRedis; 5 | import com.daydreamdev.secondskill.service.api.OrderService; 6 | import com.daydreamdev.secondskill.service.api.StockService; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Controller; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | import org.springframework.web.bind.annotation.RequestMethod; 12 | import org.springframework.web.bind.annotation.ResponseBody; 13 | 14 | import javax.servlet.http.HttpServletRequest; 15 | 16 | /** 17 | * @auther G.Fukang 18 | * @date 6/7 12:32 19 | */ 20 | @Slf4j 21 | @Controller 22 | @RequestMapping(value = "/") 23 | public class IndexController { 24 | 25 | private static final String success = "SUCCESS"; 26 | private static final String error = "ERROR"; 27 | 28 | @Autowired 29 | private OrderService orderService; 30 | 31 | @Autowired 32 | private StockService stockService; 33 | 34 | /** 35 | * 压测前先请求该方法,初始化数据库和缓存 36 | */ 37 | @RequestMapping(value = "initDBAndRedis", method = RequestMethod.POST) 38 | @ResponseBody 39 | public String initDBAndRedisBefore(HttpServletRequest request) { 40 | int res = 0; 41 | try { 42 | // 初始化库存信息 43 | res = stockService.initDBBefore(); 44 | // 清空订单表 45 | res &= (orderService.delOrderDBBefore() == 0 ? 1 : 0); 46 | // 重置缓存 47 | StockWithRedis.initRedisBefore(); 48 | } catch (Exception e) { 49 | log.error("Exception: ", e); 50 | } 51 | if (res == 1) { 52 | log.info("重置数据库和缓存成功!"); 53 | } 54 | return res == 1 ? success : error; 55 | } 56 | 57 | /** 58 | * 秒杀基本逻辑,存在超卖问题 59 | * 60 | * @param sid 61 | * @return 62 | */ 63 | @RequestMapping(value = "createWrongOrder", method = RequestMethod.POST) 64 | @ResponseBody 65 | public String createWrongOrder(HttpServletRequest request, int sid) { 66 | int res = 0; 67 | try { 68 | res = orderService.createWrongOrder(sid); 69 | } catch (Exception e) { 70 | log.error("Exception: ", e); 71 | } 72 | return res == 1 ? success : error; 73 | } 74 | 75 | /** 76 | * 乐观锁扣库存 77 | * 78 | * @param sid 79 | * @return 80 | */ 81 | @RequestMapping(value = "createOptimisticOrder", method = RequestMethod.POST) 82 | @ResponseBody 83 | public String createOptimisticOrder(HttpServletRequest request, int sid) { 84 | int res = 0; 85 | try { 86 | res = orderService.createOptimisticOrder(sid); 87 | } catch (Exception e) { 88 | log.error("Exception: " + e); 89 | } 90 | return res == 1 ? success : error; 91 | } 92 | 93 | /** 94 | * 乐观锁更新 + 限流 95 | * 96 | * @param sid 97 | */ 98 | @RequestMapping(value = "createOptimisticLimitOrder", method = RequestMethod.POST) 99 | @ResponseBody 100 | public String createOptimisticLimitOrder(HttpServletRequest request, int sid) { 101 | int res = 0; 102 | try { 103 | if (RedisLimit.limit()) { 104 | res = orderService.createOptimisticOrder(sid); 105 | } 106 | } catch (Exception e) { 107 | log.error("Exception: " + e); 108 | } 109 | return res == 1 ? success : error; 110 | } 111 | 112 | /** 113 | * Redis 缓存库存,减少 DB 压力 114 | * 在 RedisPreheatRunner 做缓存预热,需要 stock.id = 1 115 | * @param sid 116 | */ 117 | @RequestMapping(value = "createOrderWithLimitAndRedis", method = RequestMethod.POST) 118 | @ResponseBody 119 | public String createOrderWithLimitAndRedis(HttpServletRequest request, int sid) { 120 | int res = 0; 121 | try { 122 | if (RedisLimit.limit()) { 123 | res = orderService.createOrderWithLimitAndRedis(sid); 124 | if (res == 1) { 125 | log.info("秒杀成功"); 126 | } 127 | } 128 | } catch (Exception e) { 129 | log.error("Exception: " + e); 130 | } 131 | return res == 1 ? success : error; 132 | } 133 | 134 | /** 135 | * 限流 + Redis 缓存库存 + KafkaTest 异步下单 136 | * @param sid 137 | */ 138 | @RequestMapping(value = "createOrderWithLimitAndRedisAndKafka", method = RequestMethod.POST) 139 | @ResponseBody 140 | public String createOrderWithLimitAndRedisAndKafka(HttpServletRequest request, int sid) { 141 | try { 142 | if (RedisLimit.limit()) { 143 | orderService.createOrderWithLimitAndRedisAndKafka(sid); 144 | } 145 | } catch (Exception e) { 146 | log.error("Exception: " + e); 147 | } 148 | return "秒杀请求正在处理,排队中"; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/main/java/com/daydreamdev/secondskill/dao/StockMapper.java: -------------------------------------------------------------------------------- 1 | package com.daydreamdev.secondskill.dao; 2 | 3 | import com.daydreamdev.secondskill.pojo.Stock; 4 | import org.apache.ibatis.annotations.Mapper; 5 | import org.apache.ibatis.annotations.Param; 6 | import org.apache.ibatis.annotations.Select; 7 | import org.apache.ibatis.annotations.Update; 8 | 9 | /** 10 | * @auther G.Fukang 11 | * @date 6/7 11:52 12 | */ 13 | @Mapper 14 | public interface StockMapper { 15 | 16 | /** 17 | * 初始化 DB 18 | */ 19 | @Update("UPDATE stock SET count = 50, sale = 0, version = 0") 20 | int initDBBefore(); 21 | 22 | @Select("SELECT * FROM stock WHERE id = #{id, jdbcType = INTEGER}") 23 | Stock selectByPrimaryKey(@Param("id") int id); 24 | 25 | @Update("UPDATE stock SET count = #{count, jdbcType = INTEGER}, name = #{name, jdbcType = VARCHAR}, " + 26 | "sale = #{sale,jdbcType = INTEGER},version = #{version,jdbcType = INTEGER} " + 27 | "WHERE id = #{id, jdbcType = INTEGER}") 28 | int updateByPrimaryKeySelective(Stock stock); 29 | 30 | /** 31 | * 乐观锁 version 32 | */ 33 | @Update("UPDATE stock SET count = count - 1, sale = sale + 1, version = version + 1 WHERE " + 34 | "id = #{id, jdbcType = INTEGER} AND version = #{version, jdbcType = INTEGER}") 35 | int updateByOptimistic(Stock stock); 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/daydreamdev/secondskill/dao/StockOrderMapper.java: -------------------------------------------------------------------------------- 1 | package com.daydreamdev.secondskill.dao; 2 | 3 | import com.daydreamdev.secondskill.pojo.StockOrder; 4 | import org.apache.ibatis.annotations.Insert; 5 | import org.apache.ibatis.annotations.Mapper; 6 | import org.apache.ibatis.annotations.Update; 7 | 8 | /** 9 | * @auther G.Fukang 10 | * @date 6/7 14:32 11 | */ 12 | @Mapper() 13 | public interface StockOrderMapper { 14 | 15 | @Insert("INSERT INTO stock_order (id, sid, name, create_time) VALUES " + 16 | "(#{id, jdbcType = INTEGER}, #{sid, jdbcType = INTEGER}, #{name, jdbcType = VARCHAR}, #{createTime, jdbcType = TIMESTAMP})") 17 | int insertSelective(StockOrder order); 18 | 19 | /** 20 | * 清空订单表 21 | * 成功为 0,失败为 -1 22 | */ 23 | @Update("TRUNCATE TABLE stock_order") 24 | int delOrderDBBefore(); 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/daydreamdev/secondskill/pojo/Stock.java: -------------------------------------------------------------------------------- 1 | package com.daydreamdev.secondskill.pojo; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | /** 7 | * @auther G.Fukang 8 | * @date 6/7 10:36 9 | */ 10 | @Getter 11 | @Setter 12 | public class Stock { 13 | 14 | private Integer id; 15 | 16 | private String name; 17 | 18 | private Integer count; 19 | 20 | private Integer sale; 21 | 22 | private Integer version; 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/daydreamdev/secondskill/pojo/StockOrder.java: -------------------------------------------------------------------------------- 1 | package com.daydreamdev.secondskill.pojo; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | import java.util.Date; 7 | 8 | /** 9 | * @auther G.Fukang 10 | * @date 6/7 10:37 11 | */ 12 | @Getter 13 | @Setter 14 | public class StockOrder { 15 | 16 | private Integer id; 17 | 18 | private Integer sid; 19 | 20 | private String name; 21 | 22 | private Date createTime; 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/daydreamdev/secondskill/service/api/OrderService.java: -------------------------------------------------------------------------------- 1 | package com.daydreamdev.secondskill.service.api; 2 | 3 | import com.daydreamdev.secondskill.pojo.Stock; 4 | 5 | /** 6 | * @auther G.Fukang 7 | * @date 6/7 12:35 8 | */ 9 | public interface OrderService { 10 | 11 | /** 12 | * 清空订单表 13 | */ 14 | int delOrderDBBefore(); 15 | 16 | /** 17 | * 创建订单(存在超卖问题) 18 | * 19 | * @param sid 20 | * @return int 21 | */ 22 | int createWrongOrder(int sid) throws Exception; 23 | 24 | /** 25 | * 数据库乐观锁更新库存,解决超卖问题 26 | * 27 | * @param sid 28 | * @return int 29 | */ 30 | int createOptimisticOrder(int sid) throws Exception; 31 | 32 | /** 33 | * 数据库乐观锁更新库存,库存查询 Redis 减小数据库读压力 34 | * 35 | * @param sid 36 | * @return int 37 | */ 38 | int createOrderWithLimitAndRedis(int sid) throws Exception; 39 | 40 | /** 41 | * 限流 + Redis 缓存库存信息 + KafkaTest 异步发送消息 42 | * 43 | * @param sid 44 | */ 45 | void createOrderWithLimitAndRedisAndKafka(int sid) throws Exception; 46 | 47 | /** 48 | * Kafka 消费消息 49 | * 50 | * @param stock 51 | */ 52 | int consumerTopicToCreateOrderWithKafka(Stock stock) throws Exception; 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/daydreamdev/secondskill/service/api/StockService.java: -------------------------------------------------------------------------------- 1 | package com.daydreamdev.secondskill.service.api; 2 | 3 | import com.daydreamdev.secondskill.pojo.Stock; 4 | 5 | /** 6 | * @auther G.Fukang 7 | * @date 6/7 12:35 8 | */ 9 | public interface StockService { 10 | 11 | /** 12 | * 根据 id 获取剩余库存 13 | * @param id 14 | * @return int 15 | */ 16 | int getStockCount(int id); 17 | 18 | /** 19 | * 根据 id 查询剩余库存信息 20 | * @param id 21 | * @return stock 22 | */ 23 | Stock getStockById(int id); 24 | 25 | /** 26 | * 根据 id 更新库存信息 27 | * @param stock 28 | * @return int 29 | */ 30 | int updateStockById(Stock stock); 31 | 32 | /** 33 | * 乐观锁更新库存,解决超卖问题 34 | */ 35 | int updateStockByOptimistic(Stock stock); 36 | 37 | /** 38 | * 初始化数据库 39 | */ 40 | int initDBBefore(); 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/daydreamdev/secondskill/service/impl/OrderServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.daydreamdev.secondskill.service.impl; 2 | 3 | import com.daydreamdev.secondskill.common.stockWithRedis.RedisKeysConstant; 4 | import com.daydreamdev.secondskill.common.stockWithRedis.StockWithRedis; 5 | import com.daydreamdev.secondskill.common.utils.RedisPoolUtil; 6 | import com.daydreamdev.secondskill.dao.StockOrderMapper; 7 | import com.daydreamdev.secondskill.pojo.Stock; 8 | import com.daydreamdev.secondskill.pojo.StockOrder; 9 | import com.daydreamdev.secondskill.service.api.OrderService; 10 | import com.google.gson.Gson; 11 | import com.google.gson.GsonBuilder; 12 | import lombok.extern.slf4j.Slf4j; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.beans.factory.annotation.Value; 15 | import org.springframework.kafka.core.KafkaTemplate; 16 | import org.springframework.stereotype.Service; 17 | import org.springframework.transaction.annotation.Transactional; 18 | 19 | import java.util.Date; 20 | 21 | /** 22 | * @auther G.Fukang 23 | * @date 6/7 12:44 24 | */ 25 | @Slf4j 26 | @Transactional(rollbackFor = Exception.class) 27 | @Service(value = "OrderService") 28 | public class OrderServiceImpl implements OrderService { 29 | 30 | @Autowired 31 | private StockServiceImpl stockService; 32 | 33 | @Autowired 34 | private StockOrderMapper orderMapper; 35 | 36 | @Autowired 37 | private KafkaTemplate kafkaTemplate; 38 | 39 | @Value("${spring.kafka.template.default-topic}") 40 | private String kafkaTopic; 41 | 42 | private Gson gson = new GsonBuilder().create(); 43 | 44 | @Override 45 | public int delOrderDBBefore() { 46 | return orderMapper.delOrderDBBefore(); 47 | } 48 | 49 | @Override 50 | public int createWrongOrder(int sid) throws Exception { 51 | Stock stock = checkStock(sid); 52 | saleStock(stock); 53 | int res = createOrder(stock); 54 | 55 | return res; 56 | } 57 | 58 | @Override 59 | public int createOptimisticOrder(int sid) throws Exception { 60 | // 校验库存 61 | Stock stock = checkStock(sid); 62 | // 乐观锁更新 63 | saleStockOptimstic(stock); 64 | // 创建订单 65 | int id = createOrder(stock); 66 | 67 | return id; 68 | } 69 | 70 | @Override 71 | public int createOrderWithLimitAndRedis(int sid) throws Exception { 72 | // 校验库存,从 Redis 中获取 73 | Stock stock = checkStockWithRedis(sid); 74 | // 乐观锁更新库存和Redis 75 | saleStockOptimsticWithRedis(stock); 76 | // 创建订单 77 | int res = createOrder(stock); 78 | 79 | return res; 80 | } 81 | 82 | @Override 83 | public void createOrderWithLimitAndRedisAndKafka(int sid) throws Exception { 84 | // 校验库存 85 | Stock stock = checkStockWithRedis(sid); 86 | // 下单请求发送至 kafka,需要序列化 stock 87 | kafkaTemplate.send(kafkaTopic, gson.toJson(stock)); 88 | log.info("消息发送至 Kafka 成功"); 89 | } 90 | 91 | @Override 92 | public int consumerTopicToCreateOrderWithKafka(Stock stock) throws Exception { 93 | // 乐观锁更新库存和 Redis 94 | saleStockOptimsticWithRedis(stock); 95 | int res = createOrder(stock); 96 | if (res == 1) { 97 | log.info("Kafka 消费 Topic 创建订单成功"); 98 | } else { 99 | log.info("Kafka 消费 Topic 创建订单失败"); 100 | } 101 | 102 | return res; 103 | } 104 | 105 | /** 106 | * Redis 校验库存 107 | * 108 | * @param sid 109 | */ 110 | private Stock checkStockWithRedis(int sid) throws Exception { 111 | Integer count = Integer.parseInt(RedisPoolUtil.get(RedisKeysConstant.STOCK_COUNT + sid)); 112 | Integer sale = Integer.parseInt(RedisPoolUtil.get(RedisKeysConstant.STOCK_SALE + sid)); 113 | Integer version = Integer.parseInt(RedisPoolUtil.get(RedisKeysConstant.STOCK_VERSION + sid)); 114 | if (count < 1) { 115 | log.info("库存不足"); 116 | throw new RuntimeException("库存不足 Redis currentCount: " + sale); 117 | } 118 | Stock stock = new Stock(); 119 | stock.setId(sid); 120 | stock.setCount(count); 121 | stock.setSale(sale); 122 | stock.setVersion(version); 123 | // 此处应该是热更新,但是在数据库中只有一个商品,所以直接赋值 124 | stock.setName("手机"); 125 | 126 | return stock; 127 | } 128 | 129 | /** 130 | * 更新数据库和 DB 131 | */ 132 | private void saleStockOptimsticWithRedis(Stock stock) throws Exception { 133 | int res = stockService.updateStockByOptimistic(stock); 134 | if (res == 0) { 135 | throw new RuntimeException("并发更新库存失败"); 136 | } 137 | // 更新 Redis 138 | StockWithRedis.updateStockWithRedis(stock); 139 | } 140 | 141 | /** 142 | * 校验库存 143 | */ 144 | private Stock checkStock(int sid) throws Exception { 145 | Stock stock = stockService.getStockById(sid); 146 | if (stock.getCount() < 1) { 147 | throw new RuntimeException("库存不足"); 148 | } 149 | return stock; 150 | } 151 | 152 | /** 153 | * 扣库存 154 | */ 155 | private int saleStock(Stock stock) { 156 | stock.setSale(stock.getSale() + 1); 157 | stock.setCount(stock.getCount() - 1); 158 | return stockService.updateStockById(stock); 159 | } 160 | 161 | /** 162 | * 乐观锁扣库存 163 | */ 164 | private void saleStockOptimstic(Stock stock) throws Exception { 165 | int count = stockService.updateStockByOptimistic(stock); 166 | if (count == 0) { 167 | throw new RuntimeException("并发更新库存失败"); 168 | } 169 | } 170 | 171 | /** 172 | * 创建订单 173 | */ 174 | private int createOrder(Stock stock) throws Exception { 175 | StockOrder order = new StockOrder(); 176 | order.setSid(stock.getId()); 177 | order.setName(stock.getName()); 178 | order.setCreateTime(new Date()); 179 | int res = orderMapper.insertSelective(order); 180 | if (res == 0) { 181 | throw new RuntimeException("创建订单失败"); 182 | } 183 | return res; 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/main/java/com/daydreamdev/secondskill/service/impl/StockServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.daydreamdev.secondskill.service.impl; 2 | 3 | import com.daydreamdev.secondskill.dao.StockMapper; 4 | import com.daydreamdev.secondskill.pojo.Stock; 5 | import com.daydreamdev.secondskill.service.api.StockService; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Service; 8 | 9 | /** 10 | * @auther G.Fukang 11 | * @date 6/7 12:45 12 | */ 13 | @Service(value = "StockService") 14 | public class StockServiceImpl implements StockService { 15 | 16 | @Autowired 17 | private StockMapper stockMapper; 18 | 19 | @Override 20 | public int getStockCount(int id) { 21 | Stock stock = stockMapper.selectByPrimaryKey(id); 22 | return stock.getCount(); 23 | } 24 | 25 | @Override 26 | public Stock getStockById(int id) { 27 | 28 | return stockMapper.selectByPrimaryKey(id); 29 | } 30 | 31 | @Override 32 | public int updateStockById(Stock stock) { 33 | 34 | return stockMapper.updateByPrimaryKeySelective(stock); 35 | } 36 | 37 | @Override 38 | public int updateStockByOptimistic(Stock stock) { 39 | 40 | return stockMapper.updateByOptimistic(stock); 41 | } 42 | 43 | @Override 44 | public int initDBBefore() { 45 | 46 | return stockMapper.initDBBefore(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # 日志 2 | logging.file=secondkill.log 3 | logging.level.root=info 4 | 5 | # 数据库 6 | spring.datasource.url=jdbc:mysql://119.3.214.253:3306/seconds_kill?useUnicode=true&characterEncoding=utf8 7 | spring.datasource.username=root 8 | spring.datasource.password=123456789qwER# 9 | spring.datasource.driver-class-name=com.mysql.jdbc.Driver 10 | spring.datasource.type=com.alibaba.druid.pool.DruidDataSource 11 | 12 | # redis 配置 静态变量注入失败,直接在代码中配置 13 | spring.redis.host=119.3.214.253 14 | spring.redis.port=6379 15 | spring.redis.password= 16 | redis.maxIdle=100 17 | redis.maxTotal=300 18 | redis.maxWait=10000 19 | redis.testOnBorrow=true 20 | redis.timeout=100000 21 | # 限流 22 | redis.limit=5 23 | 24 | # kafka 25 | # kafka 代理地址 26 | spring.kafka.bootstrap-servers=119.3.214.253:9092 27 | # consumer group-id 28 | spring.kafka.consumer.group-id=seconds-kill 29 | # 指定 topic id 30 | spring.kafka.template.default-topic=SECONDS-KILL-TOPIC 31 | # 容器中的线程数,用于提高并发数量 32 | spring.kafka.listener.concurrency=3 -------------------------------------------------------------------------------- /src/main/resources/limit.lua: -------------------------------------------------------------------------------- 1 | -- 计数限流 2 | -- 每次请求都将当前时间,精确到秒作为 key 放入 Redis 中,超时时间设置为 2s, Redis 将该 key 的值进行自增 3 | -- 当达到阈值时返回错误,表示请求被限流 4 | -- 写入 Redis 的操作用 Lua 脚本来完成,利用 Redis 的单线程机制可以保证每个 Redis 请求的原子性 5 | 6 | -- 资源唯一标志位 7 | local key = KEYS[1] 8 | -- 限流大小 9 | local limit = tonumber(ARGV[1]) 10 | 11 | -- 获取当前流量大小 12 | local currentLimit = tonumber(redis.call('get', key) or "0") 13 | 14 | if currentLimit + 1 > limit then 15 | -- 达到限流大小 返回 16 | return 0; 17 | else 18 | -- 没有达到阈值 value + 1 19 | redis.call("INCRBY", key, 1) 20 | -- 设置过期时间 21 | redis.call("EXPIRE", key, 2) 22 | return currentLimit + 1 23 | end -------------------------------------------------------------------------------- /src/test/java/com/daydreamdev/secondskill/SecondsKillApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.daydreamdev.secondskill; 2 | 3 | import com.daydreamdev.secondskill.dao.StockMapper; 4 | import com.daydreamdev.secondskill.dao.StockOrderMapper; 5 | import com.daydreamdev.secondskill.pojo.Stock; 6 | import com.daydreamdev.secondskill.pojo.StockOrder; 7 | import org.junit.Assert; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.boot.test.context.SpringBootTest; 14 | import org.springframework.test.context.junit4.SpringRunner; 15 | 16 | import java.util.Date; 17 | import java.util.concurrent.ExecutorService; 18 | import java.util.concurrent.Executors; 19 | 20 | @RunWith(SpringRunner.class) 21 | @SpringBootTest 22 | public class SecondsKillApplicationTests { 23 | 24 | private Logger logger = LoggerFactory.getLogger(SecondsKillApplicationTests.class); 25 | 26 | @Autowired 27 | private StockMapper stockMapper; 28 | 29 | @Autowired 30 | private StockOrderMapper orderMapper; 31 | 32 | @Test 33 | public void StockMapperSelectByPrimaryKeyTest() { 34 | Stock stock = stockMapper.selectByPrimaryKey(1); 35 | logger.info("stock: id = {}, name = {}, count = {}, sale = {}, version = {}", 36 | stock.getId(), stock.getName(), stock.getCount(), stock.getSale(), stock.getVersion()); 37 | } 38 | 39 | @Test 40 | public void StockMapperUpdateByPrimaryKeySelective() { 41 | Stock stock = new Stock(); 42 | stock.setId(1); 43 | stock.setName("苹果手机"); 44 | stock.setCount(11); 45 | stock.setSale(5); 46 | stock.setVersion(0); 47 | int res = stockMapper.updateByPrimaryKeySelective(stock); 48 | Assert.assertEquals(res, 1); 49 | } 50 | 51 | @Test 52 | public void StockMapperUpdateByOptimistic() { 53 | 54 | class ThreadOrder implements Runnable { 55 | @Override 56 | public void run() { 57 | Stock stock = new Stock(); 58 | stock.setId(1); 59 | stock.setName("测试手机"); 60 | stock.setCount(10); 61 | stock.setSale(5); 62 | stock.setVersion(0); 63 | int res = stockMapper.updateByOptimistic(stock); 64 | System.out.println(res); 65 | logger.info("res: " + res); 66 | } 67 | } 68 | 69 | // 线程池 70 | ExecutorService service = Executors.newFixedThreadPool(10); 71 | for (int i = 0; i < 15; i++) { 72 | service.submit(new ThreadOrder()); 73 | } 74 | } 75 | 76 | @Test 77 | public void StockOrderMapperInsertSelective() { 78 | StockOrder order = new StockOrder(); 79 | order.setId(2); 80 | order.setSid(1); 81 | order.setName("苹果手机"); 82 | order.setCreateTime(new Date()); 83 | int res = orderMapper.insertSelective(order); 84 | Assert.assertEquals(res, 1); 85 | } 86 | 87 | } 88 | --------------------------------------------------------------------------------