201905W21 Share
又遇到一个使用分布式锁的情况, 但是没有用 Redis 或者 Zookeeper, 而是直接依赖 MySQL 给实现了。
其实用 MySQL 做分布式锁不是很适合, 但是在原理上非常容易理解。
我们只需要在数据库里创建一张表, 然后对这个表写数据, 多个实例中, 成功写入的那一个, 便拿到了锁, 可以进行下一步逻辑。
这里我们不考虑任何的可重入性、 超时、 阻塞等, 仅仅区满足分布式锁的互斥性, 其他的特性我们可以在下一次来实现。
下面开始我们的实现, 使用 SpringBoot 和 MySQL。
- 首先, 需要在 maven 引入依赖
1 2 3 4 5 6 7 8 9
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency>
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency>
|
- 创建 Event 的 model 和 dao
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
| import java.util.Date; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id;
@Entity(name = "event") public class Event {
@Id @GeneratedValue(strategy = GenerationType.AUTO) private int id;
private String name;
private Date startTime;
private Date finishTime;
public int getId() { return id; }
public Event setId(int id) { this.id = id; return this; }
public String getName() { return name; }
public Event setName(String name) { this.name = name; return this; }
public Date getStartTime() { return startTime; }
public Event setStartTime(Date startTime) { this.startTime = startTime; return this; }
public Date getFinishTime() { return finishTime; }
public Event setFinishTime(Date finishTime) { this.finishTime = finishTime; return this; }
}
|
1 2 3 4 5 6 7 8
| import indi.yanss.dls.model.Event; import org.springframework.data.jpa.repository.JpaRepository;
public interface EventRepository extends JpaRepository<Event, Long> {
Event findByName(String name);
}
|
- 创建锁的获取和释放方法
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
| import java.util.Date;
import indi.yanss.dls.model.Event; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service;
@Service public class EventLock {
@Autowired private EventRepository eventRepository;
public Event acquire(String name) { Event event = eventRepository.findByName(name);
if (event == null) { event = new Event().setName(name).setStartTime(new Date()); eventRepository.save(event); } else if (event.getFinishTime() == null) { throw new RuntimeException("Lock has been acquired."); } else if (event.getFinishTime().before(new Date())) { int id = event.getId(); event = new Event().setId(id).setName(name).setStartTime(new Date()); eventRepository.save(event); } return event; }
public void release(Event event) { eventRepository.save(event.setFinishTime(new Date())); } }
|
- 创建 Schedule 任务
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
| import java.util.Date;
import indi.yanss.dls.model.Event; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service;
@Service public class EventLock {
@Autowired private EventRepository eventRepository;
public Event acquire(String name) { Event event = eventRepository.findByName(name);
if (event == null) { event = new Event().setName(name).setStartTime(new Date()); eventRepository.save(event); } else if (event.getFinishTime() == null) { throw new RuntimeException("Lock has been acquired."); } else if (event.getFinishTime().before(new Date())) { int id = event.getId(); event = new Event().setId(id).setName(name).setStartTime(new Date()); eventRepository.save(event); } return event; }
public void release(Event event) { eventRepository.save(event.setFinishTime(new Date())); } }
|
- 启动 MySQL, 配置连接。 这里不用手动创建表, JPA 会在实例启动时自动帮我们创建和 Event model 对应的表。
1 2 3 4
| spring.datasource.url = jdbc:mysql://localhost:3306/demo spring.datasource.username = root spring.datasource.password = 123456
|
- 启动多个实例, 用不同端口即可。
- Done! 观察控制台输出吧。