考虑问题:分布式商品秒杀
在分布式部署中,以nginx提供负载均衡,提供服务主机以集群形式部署。mysql作为db服务。
比如:现在db中存储了1w+的商品信息,用于提供服务的主机集群里面包含三台高性能主机,预估此次参与秒杀的人有3w+,
秒杀活动9点中开始,nginx对外暴露的域名为 www.goodms.com
分析过程:
1. 在活动开始之后,由于nginx的负载均衡作用,3w+的人员被分流至三台主机上。
2. 为了实现高并发秒杀商品,在每一台主机上面再启500的线程,从db中获取商品,并进行商品获取之后的信息校验,商品删除的相关操作。
3. 由于启动的线程可能位于不同的主机上面,而jdk并发包提供的加锁手段此时将失效,因为加锁的实现是针对用一个JVM的。目前而言,db中的
数据为分布式环境中的共享数据,故引入分布式锁的概念。
4. 针对分布式锁的实现:目前主要存在三种技术:
a. 基于数据库的实现
b. 基于redis缓存的实现
c. 基于zk的实现
分布式锁的实现:
1. 基于数据库
基于数据库的实现,主要是在数据库中维持一个记录锁信息的表。该表中存在一个字段,且该字段的值时唯一确认,且不能重复,比如值为:lock。
当不同主机上面的线程需要秒杀商品时,首先去锁表里面插入lock值,如果可以插入成功,说明当前没有其他线程操作db,也就是当前线程获得了锁,比如处理商品相关服务(商品信息校验,商品数据删除等等)需要10s中,在这10s之内进入的线程首先去表里面插入lock值,由于该字段属性是唯一的,那么会出现插入失败,则认为获取锁失败。然后尝试再次获取锁,直到获取成功(前提是上一个锁拥线程删除锁表中的记录),才可以进行商品的相关操作。
缺点:如果锁拥有线程在执行业务逻辑中,突然宕机,那么锁表中的记录将永远无法清除,或造成死锁现象。可以通过设置过期时间来阻止该现象的发生。
2. 基于Redis
实现原理一样,缺点也是容易造成死锁现象,因为必须是在连接存在的情况下才能删除锁记录。可以通过设置过期时间来阻止该现象的发生。
3. 基于zk实现:
主要依赖的是临时节点的创建,临时节点会在连接关闭时自动删除,所以以临时节点作为锁,可以避免死锁现象。
注意:每一次获取锁时,都要进行连接的创建,因为释放锁的时候,节点已经关闭。
分布式锁可以适用于分布式场景,也可以适用单机部署场景,但是单机场景不建议使用分布式锁,因为分布式锁的实现是依赖连接的建立,会导致性能严重下降。
依赖:

1 <dependency> 2 <groupId>com.101tec</groupId> 3 <artifactId>zkclient</artifactId> 4 <version>0.10</version> 5 </dependency>
分布式锁:

package com.example.demo.test; import org.I0Itec.zkclient.IZkDataListener; import org.I0Itec.zkclient.ZkClient; import java.util.concurrent.CountDownLatch; /** * Created by 156 on 2019/2/10. */ public class MyLocak { // zk连接地址 private static final String CONNECTSTRING = "192.168.20.1:2181"; // 创建zk连接 protected ZkClient zkClient = new ZkClient(CONNECTSTRING); // 锁 protected static final String PATH = "/lock"; // 信号量 private CountDownLatch countDownLatch = null; //获取到锁的资源 public void getLock(){ if (tryLock()) { System.out.println("##获取lock锁的资源####"); } else { // 等待 waitLock(); // 重新获取锁资源 getLock(); } } // 释放锁 public void unLock(){ if (zkClient != null) { zkClient.close(); System.out.println("释放锁..."); } } private boolean tryLock() { try { // 创建临时节点 zkClient.createEphemeral(PATH); return true; } catch (Exception e) { // e.printStackTrace(); return false; } } private void waitLock() { IZkDataListener izkDataListener = new IZkDataListener() { public void handleDataDeleted(String path) throws Exception { // 唤醒被等待的线程 if (countDownLatch != null) { countDownLatch.countDown(); } } public void handleDataChange(String path, Object data) throws Exception { } }; // 注册事件 zkClient.subscribeDataChanges(PATH, izkDataListener); if (zkClient.exists(PATH)) { countDownLatch = new CountDownLatch(1); try { countDownLatch.await(); } catch (Exception e) { e.printStackTrace(); } } // 删除监听 zkClient.unsubscribeDataChanges(PATH, izkDataListener); } }
测试代码:

public class T { public static void main(String[] args) { for (int i=0;i<100;i++){ // 每一次获取锁的连接都必须重建 ThreadDemo demo = new ThreadDemo(); new Thread(demo).start(); } } }
ThreadDemo代码:

public class ThreadDemo implements Runnable{ // 在分布式场景中,该变量的存在用于标识网站的访问量 static int count = 0; private Object lock = new Object(); private MyLocak locak = new MyLocak(); @Override public void run() { /* synchronized (lock){ System.out.println("当前累计访问人数:"+ ++count +" 人"); }*/ locak.getLock(); System.out.println("当前累计访问人数:"+ ++count +" 人"); locak.unLock(); } }