从技术角度出发,注册一个网站,再高并发的时候,有可能出现用户名重复这样的问题(虽然一般情况下不会出现这种问题),如何解决呢?
从数据库角度,对于单表,我可以用select .. for update
悲观锁实现,或者用version这种乐观锁的思想。
更好的方法是将这个字段添加唯一索引,用数据库来保证不会重复。一旦插入重复,那么就会抛出异常,程序就可以捕获到。
但是,假如我们这里分表了,以上都是针对单表,第一种方案是锁表,不行,设置唯一索引是没有用。怎么办呢?
解决方案:用ZK做一个分布式锁。
首先准备一个ZK客户端,用的是Curator
来连接我们的ZK:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| @Component public class ZkClient {
@Autowired private Parameters parameters;
@Bean public CuratorFramework getZkClient(){
CuratorFrameworkFactory.Builder builder= CuratorFrameworkFactory.builder() .connectString(parameters.getZkHost()) .connectionTimeoutMs(3000) .retryPolicy(new RetryNTimes(5, 10)); CuratorFramework framework = builder.build(); framework.start(); return framework;
}
}
|
注册用一个分布式锁来控制:
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
| @Override public void registerUser(User user) throws Exception { InterProcessLock lock = null; try{ lock = new InterProcessMutex(zkClient, Constants.USER_REGISTER_DISTRIBUTE_LOCK_PATH); boolean retry = true; do{ if (lock.acquire(3000, TimeUnit.MILLISECONDS)){ User repeatedUser = userMapper.selectByEmail(user.getEmail()); if(repeatedUser!=null){ throw new MamaBuyException("用户邮箱重复"); } user.setPassword(passwordEncoder.encode(user.getPassword())); user.setNickname("码码购用户"+user.getEmail()); userMapper.insertSelective(user); retry = false; } }while (retry); }catch (Exception e){ log.error("用户注册异常",e); throw e; }finally { if(lock != null){ try { lock.release(); log.info(user.getEmail()+Thread.currentThread().getName()+"释放锁"); } catch (Exception e) { e.printStackTrace(); } } } }
|
思路非常简单,就是先尝试上锁,即acquire
,但是有可能失败,所以这里用一个超时时间,即3000ms
之内上不了锁就失败,进入下一次循环。最后释放锁即可。
ok,这里要来说说ZK实现分布式锁了。这里用了开源客户端Curator
,他对于实现分布式锁进行了封装,但是,我还是想了解一下它的实现原理:
每个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。 判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。 当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。
也就是说,最小的那个节点就是Leader,进来判断是不是为那个节点,是的话就可以获取到锁,反之不行。
为什么不能通过大家一起创建节点,如果谁成功了就算获取到了锁。 多个client创建一个同名的节点,如果节点谁创建成功那么表示获取到了锁,创建失败表示没有获取到锁。
答:使用临时顺序节点可以保证获得锁的公平性,及谁先来谁就先得到锁,这种方式是随机获取锁,会造成无序和饥饿。