在分布式环境下,传统的一些技术会失败,比如传统的 synchronized 或者 lock 锁,以及创建数据库的事务,无法保证ACID,还有定时任务也可能会出现重复执行的问题。
分布式锁。Zookeeper 实现分布式锁。
创建 spring boot 工程。
// pom.xml
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.6.0</version>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
<exclusion>
<artifactId>slf4j-log4j12</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
<exclusion>
<artifactId>log4j</artifactId>
<groupId>log4j</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.0.1</version>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
// ZkUtils.java
@Configuration
public class ZkUtils {
@Bean
public CuratorFramework cf(){
RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000,2);
CuratorFramework curatorFramework = CuratorFrameworkFactory.builder()
.connectString("10.36.144.157:2181,10.36.144.157:2182,10.36.144.157:2183")
.retryPolicy(retryPolicy)
.build();
curatorFramework.start();
return curatorFramework;
}
}
// SecondKillController.java
@RestController
public class SecondKillController {
@Autowired
private CuratorFramework curatorFramework;
// 商品库存
public static Map<String, Integer> itemStock = new HashMap<>();
// 商品清单
public static Map<String, Integer> itemOrder = new HashMap<>();
static {
itemStock.put("apple", 10000);
itemOrder.put("apple", 0);
}
@RequestMapping("/zkKill")
public String zkKill(String item) throws Exception {
//InterProcessMutex基于Zookeeper实现了分布式互斥锁
InterProcessMutex lock = new InterProcessMutex(curatorFramework,"/lock");
lock.acquire();
//限时等待,指定排队多久就放弃获取锁资源
// lock.acquire(1, TimeUnit.SECONDS);
Integer stock = itemStock.get(item);
if (stock <= 0) {
return "商品库存不足!!!!";
}
itemStock.put(item, stock - 1);
Thread.sleep(100);
Integer order = itemOrder.get(item);
itemOrder.put(item, order + 1);
lock.release();
//3. 返回信息
return "抢购成功!!!" + item + ": 剩余库存数为: " + itemStock.get(item) + ",订单数为: " + itemOrder.get(item);
}
}
分布式定时任务。
Elastic-Job 实现分布式任务。
由当当网基于Quartz + Zookeeper的二次开放产品。
基于Zookeeper分布式锁,保证只有一个服务去执行定时任务。
基于Zookeeper实现了注册中心,自动帮助我们去调度指定的服务执行定时任务。
基于Zookeeper实现了注册中心,基于心跳的方式,自动去检测服务的健康情况。
配置 VM options 运行多个服务,多个服务的任务不重复。
// pom.xml
<dependency>
<groupId>com.dangdang</groupId>
<artifactId>elastic-job-lite-spring</artifactId>
<version>2.1.5</version>
</dependency>
// MyElasticJob
@Component
public class MyElasticJob implements SimpleJob {
@Override
public void execute(ShardingContext shardingContext) {
switch (shardingContext.getShardingItem()) {
case 0:
System.out.println("执行任务1");
break;
case 1:
System.out.println("执行任务2");
break;
case 2:
System.out.println("执行任务3");
break;
}
}
}
// ZkUtils.java
@Configuration
public class ZkUtils {
@Bean
public CoordinatorRegistryCenter center () {
CoordinatorRegistryCenter registryCenter = new ZookeeperRegistryCenter(
new ZookeeperConfiguration("10.36.144.157:2181,10.36.144.157:2182,10.36.144.157:2183", "elastic-job-demo")
);
registryCenter.init();
return registryCenter;
}
//创建一个任务调度对象,并设置相关任务的调度信息(任务的执行时间)
@Bean
public SpringJobScheduler scheduler(MyElasticJob job, CoordinatorRegistryCenter registryCenter){
// 0/5 * * * * 每五秒执行一次,3表示作业的分片
JobCoreConfiguration simpleCoreConfig = JobCoreConfiguration.newBuilder("demoSimpleJob",
"0/5 * * * * ?", 3).build();
SimpleJobConfiguration simpleJobConfig = new SimpleJobConfiguration(simpleCoreConfig,
MyElasticJob.class.getCanonicalName());
LiteJobConfiguration simpleJobRootConfig = LiteJobConfiguration.newBuilder(simpleJobConfig).build();
//在项目启动时,就会自动开始执行任务调度
SpringJobScheduler jobScheduler = new SpringJobScheduler(job,registryCenter,simpleJobRootConfig);
jobScheduler.init();
return jobScheduler;
}
}
LCN 实现分布式事务。基于三段提交和TCC实现的。
创建 spring boot 一个协调者工程。
// manager
// pom.xml
<dependency>
<groupId>com.codingapi.txlcn</groupId>
<artifactId>txlcn-tm</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
// application.xml
// 注意 application.properties 文件需要保留,不然数据库报错
server:
port: 8080
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql:///dbTest1?serverTimezone=UTC
username: root
password: 123
redis:
host: 10.36.144.157
port: 6379
#协调端口
tx-lcn:
manager:
port: 8070
// DemoApplication.java
@SpringBootApplication
@EnableTransactionManagerServer
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
创建一个 product 服务。
// product
// pom.xml
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
<dependency>
<groupId>com.codingapi.txlcn</groupId>
<artifactId>txlcn-tc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>com.codingapi.txlcn</groupId>
<artifactId>txlcn-txmsg-netty</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
// application.yml
server:
port: 8081
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql:///dbTest1?serverTimezone=UTC
username: root
password: 123
# 注册到管理端
tx-lcn:
client:
manager-address: localhost:8070
// ProductMapper.java
public interface ProductMapper {
@Update("update air set pm10=pm10-1 where id=1")
void updateStock();
}
// ProductService.java
public interface ProductService {
void updateStock();
}
// ProductServiceImpl.java
@Service
public class ProductServiceImpl implements ProductService {
@Autowired
private ProductMapper productMapper;
@Override
@Transactional
@LcnTransaction
public void updateStock() {
// int i = 1 / 0;
productMapper.updateStock();
}
}
// DemoApplication.java
@SpringBootApplication
@EnableDistributedTransaction
@MapperScan("com.example.demo.mapper")
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
// ProductController
// http://localhost:8081/updateStock
@RestController
public class ProductController {
@Autowired
private ProductService productService;
@RequestMapping("/updateStock")
public String updateStock() {
productService.updateStock();
return "product success";
}
}
创建一个 order 服务。
// order
// pom.xml
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
<dependency>
<groupId>com.codingapi.txlcn</groupId>
<artifactId>txlcn-tc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>com.codingapi.txlcn</groupId>
<artifactId>txlcn-txmsg-netty</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
// application.yml
server:
port: 8082
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql:///dbTest1?serverTimezone=UTC
username: root
password: 123
# 注册到管理端
tx-lcn:
client:
manager-address: localhost:8070
// OrderMapper.java
public interface OrderMapper {
@Insert("insert into role values(null,'kk','kkk',0)")
void add();
}
// OrderService.java
public interface OrderService {
void add();
}
// OrderServiceImpl.java
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderMapper orderMapper;
@Override
@Transactional
@LcnTransaction
public void add() {
orderMapper.add();
}
}
// DemoApplication.java
@SpringBootApplication
@EnableDistributedTransaction
@MapperScan("com.example.demo.mapper")
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
// OrderController.java
@RestController
public class OrderController {
@Autowired
private OrderService orderService;
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/add")
public String add() {
orderService.add();
String res = restTemplate.getForObject("http://localhost:8081/updateStock", String.class);
if (res != null) {
return res;
}
return "order success";
}
}