1、使用场景
- 订单号生成
UUID
时间戳
业务id
- 幂等性
重复请求
2、几种id生方式
2.1、多线程
- id生成工具类
public class OrderNumGenerator {
/**
* 业务ID
*/
private static int count = 0;
/**
* 生成订单号
* @return
*/
public String getNumber() {
try {
Thread.sleep(200);
} catch (Exception e) {
// TODO: handle exception
}
SimpleDateFormat simpt = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
return simpt.format(new Date()) + "-" + ++count;
}
}
- 多线程实现
public class OrderService implements Runnable {
/**
* 生成订单号
*/
OrderNumGenerator orderNumGenerator = new OrderNumGenerator();
// 重入锁
private Lock lock = new ReentrantLock();
@Override
public void run() {
try {
// synchronized (this) {
// getNumber();
// }
lock.getLock();
getNumber();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 釋放鎖資源
lock.unLock();
}
}
public void getNumber() {
String number = orderNumGenerator.getNumber();
System.out.println(Thread.currentThread().getName() + ",##number:" + number);
}
public static void main(String[] args) {
System.out.println("##模拟生成订单号开始...");
OrderService orderService = new OrderService();
for (int i = 0; i < 100; i++) {
new Thread(orderService).start();
}
}
}
- 线程安全问题
共享内存模型指的就是Java内存模型(简称JMM),JMM决定一个线程对共享变量的写入时,能对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。
从上图来看,线程A与线程B之间如要通信的话,必须要经历下面2个步骤:
- 首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去。
- 然后,线程B到主内存中去读取线程A之前已更新过的共享变量。
如上图所示,本地内存A和B有主内存中共享变量x的副本。假设初始时,这三个内存中的x值都为0。线程A在执行时,把更新后的x值(假设值为1)临时存放在自己的本地内存A中。当线程A和线程B需要通信时,线程A首先会把自己本地内存中修改后的x值刷新到主内存中,此时主内存中的x值变为了1。随后,线程B到主内存中去读取线程A更新后的x值,此时线程B的本地内存的x值也变为了1。
从整体来看,这两个步骤实质上是线程A在向线程B发送消息,而且这个通信过程必须要经过主内存。JMM通过控制主内存与每个线程的本地内存之间的交互,来为java程序员提供内存可见性保证。
总结:什么是Java内存模型:java内存模型简称jmm,定义了一个线程对另一个线程可见。共享变量存放在主内存中,每个线程都有自己的本地内存,当多个线程同时访问一个数据的时候,可能本地内存没有及时刷新到主内存,所以就会发生线程安全问题。
2.2、分布式id生成方式
- redis提前生成id
提前生成id,存放在在redis
多台服务器从redis获取id(类似分布式session)
使用分布式锁
1.使用数据库实现分布式锁
缺点:性能差、线程出现异常时,容易出现死锁
2.使用redis实现分布式锁
缺点:锁的失效时间难控制、容易产生死锁、非阻塞式、不可重入
3.使用zookeeper实现分布式锁
实现相对简单、可靠性强、使用临时节点,失效时间容易控制
2.3、zk实现分布式锁
- zk应用场景:
分布式协调工具
1、分布式锁
2、负载均衡
3、命名服务
4、分布式通知与协调watcher:: 事件通知
5、发布订阅:基于事件通知
6、集群环境: 选举master ,redis 哨兵机制ping
- zk存储结构(树状结构)
4种节点类型
节点名称不能重复
- zk事件通知
节点发生改变crud
- 分布式锁实现原理
zk临时节点+事件通知
1、创建临时节点path
2、那个创建节点成功那个就能拿到锁,从而生成id
3、什么时候释放锁,临时节点断开时自动清除
3、linux 的zk操作
- 客户端操作
启动客户端:
./zkCli.sh
- 创建节点
- 查看所有节点
-
查看节点与删除
-
修改节点
3.1 代码实现zk操作
public class ZooKeeperSession implements Watcher {
public static Logger logger = LoggerFactory.getLogger(ZooKeeperSession.class);
private CountDownLatch countDownLatch=new CountDownLatch(1);
private ZooKeeper zooKeeper;
private static class Singleton{
private static ZooKeeperSession instance;
static {
instance = new ZooKeeperSession();
}
public static ZooKeeperSession getInstance(){
return instance;
}
}
public static ZooKeeperSession getInstance(){
return Singleton.getInstance();
}
public ZooKeeperSession() {
try {
this.zooKeeper = new ZooKeeper("192.168.70.11:2181",50000,this);
countDownLatch.await();
logger.info("connection estalished!!");
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public ZooKeeper getZooKeeper(){ return this.zooKeeper; }
@Override
public void process(WatchedEvent event) {
if(event.getState()==SyncConnected){
countDownLatch.countDown();
}
}
public void unLock(Long id){
String path ="/lock_"+id;
try {
zooKeeper.delete(path,-1);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
}
public void tryLock(Long id){
String path ="/lock_"+id;
try {
zooKeeper.create(path,null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
} catch (KeeperException e) {
int count=0;
while (true){
try{
Thread.sleep(200);
zooKeeper.create(path,null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
}catch (InterruptedException e1) {
count++;
continue;
} catch (KeeperException e1) {
count++;
continue;
}
break;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3.2 实现分布式id生成器
public class IdGenerator implements Runnable{
private int loop;
private Long lockid;
private ZooKeeperSession zs;
private static int count = 0 ;
public IdGenerator(int loop,Long lockid) {
zs = ZooKeeperSession.getInstance();
this.loop = loop;
this.lockid = lockid;
}
@Override
public void run() {
ZooKeeperSession.getInstance().tryLock(lockid);
for(int i=0;i<loop;i++){
System.out.println(System.currentTimeMillis() +":"+ (count++));
}
zs.unLock(lockid);
}
public static void main(String[] args) {
for(int i=0;i<100;i++){
new Thread(new IdGenerator(1, 1L)).start();
}
}
}
- 计数器
//计数
public void count(int count){
try {
Stat s = zs.getZooKeeper().exists("/count", false);
if(s==null){
System.out.println("count 不存在");
zs.getZooKeeper().create("/count",String.valueOf(count).getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}else{
zs.getZooKeeper().setData("/count",String.valueOf(count).getBytes(),-1);
}
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public int getcount(){
try {
Stat s = zs.getZooKeeper().exists("/count", false);
if(s==null){
zs.getZooKeeper().create("/count",String.valueOf(0).getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
return 0;
}else{
byte[] data = zs.getZooKeeper().getData("/count", false, null);
return Integer.valueOf(new String(data));
}
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return -1;
}