提交 8fcefb47 authored 作者: xuyang's avatar xuyang

snowflake第一次提交

上级 c0fbc2b8
# datetime和timestamp的区别
## 1、自动更新日期
datetime默认值是null,不会自动更新
timestamp默认值not null,默认时间为CURRENT_TIMESTAMP,当行数据发生变化的时候采用当前时间
## 2、日期存储方式
timestamp有八小时的时差
\ No newline at end of file
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SnowflakeDemo {
private final long sequenceLength = 12L;
private final long workerIdLength = 10L;
private final long lastTimeStampLength = 41L;
private final long workerIdShift = sequenceLength;
private final long lastTimeStampShift = sequenceLength + workerIdLength;
private long lastTimeStamp = -1L;
private long workerId;
private long sequence = 0L;
private long mark = ~(-1L << sequenceLength);
private static final int loopNum = 1*10000;
public synchronized String getId() {
long timeStamp = System.currentTimeMillis();
if(timeStamp < lastTimeStamp) {
throw new RuntimeException("time error");
}
if(timeStamp == lastTimeStamp) {
sequence = (sequence + 1) & mark;
if(sequence == 0) {
while(timeStamp <= lastTimeStamp) {
timeStamp = System.currentTimeMillis();
}
}
} else {
sequence = 0;
}
lastTimeStamp = timeStamp;
return Long.toBinaryString(sequence | (workerId << workerIdShift) | (timeStamp << lastTimeStampShift));
}
public synchronized long waitTime(long lastTimeStamp) {
long timeStamp = System.currentTimeMillis();
while(timeStamp <= lastTimeStamp) {
timeStamp = System.currentTimeMillis();
}
return timeStamp;
}
private SnowflakeDemo(){}
private SnowflakeDemo(long lastTimeStamp, long workerId, long sequence) {
this.lastTimeStamp = lastTimeStamp;
this.workerId = workerId;
this.sequence = sequence;
}
public void m1() {
ExecutorService pool = Executors.newCachedThreadPool();
long currentTime = System.currentTimeMillis();
SnowflakeDemo snowflakeDemo = new SnowflakeDemo(currentTime, 0, 0);
for (int index = 0; index < loopNum; index++) {
Runnable run = new Runnable() {
public void run() {
try {
//new Thread().sleep(1000); //模拟耗时操作
System.out.println("[1]" + Thread.currentThread().getName() + " " + snowflakeDemo.getId());
} catch (Exception e) {
}
}
};
pool.execute(run);
}
System.out.println("[1] done!");
pool.shutdown();
}
public static void main(String[] args) {
long bt = System.currentTimeMillis();
SnowflakeDemo snowflakeWorker0 = new SnowflakeDemo(bt, 0, 0);
for(int i = 0; i < loopNum; i++) {
System.out.println(snowflakeWorker0.getId());
}
long et = System.currentTimeMillis();
System.out.println("耗时:"+(et2 - bt)+ "ms");
System.out.println("吞吐量为:"+loopNum*1.0/(et - bt));
/*SnowflakeDemo TestThreadPool = new SnowflakeDemo();
long bt = System.currentTimeMillis();
TestThreadPool.m1();
long et2 = System.currentTimeMillis();
System.out.println("耗时:"+(et2 - bt)+ "ms");
System.out.println("吞吐量为:"+loopNum*1.0/(et2 - bt));*/
}
}
### **1. 数据库自增长序列或字段**
最常见的方式。利用数据库,全数据库唯一。
优点:
1)简单,代码方便,性能可以接受。
2)数字ID天然排序,对分页或者需要排序的结果很有帮助。
缺点:
1)不同数据库语法和实现不同,数据库迁移的时候或多数据库版本支持的时候需要处理。
2)在单个数据库或读写分离或一主多从的情况下,只有一个主库可以生成。有单点故障的风险。
3)在性能达不到要求的情况下,比较难于扩展。
4)如果遇见多个系统需要合并或者涉及到数据迁移会相当痛苦。
5)分表分库的时候会有麻烦。
优化方案:
1)针对主库单点,如果有多个Master库,则每个Master库设置的起始数字不一样,步长一样,可以是Master的个数。比如:Master1 生成的是 1,4,7,10,Master2生成的是2,5,8,11 Master3生成的是 3,6,9,12。这样就可以有效生成集群中的唯一ID,也可以大大降低ID生成数据库操作的负载。
### **2. UUID**
常见的方式。可以利用数据库也可以利用程序生成,一般来说全球唯一。
优点:
1)简单,代码方便。
2)生成ID性能非常好,基本不会有性能问题。
3)全球唯一,在遇见数据迁移,系统数据合并,或者数据库变更等情况下,可以从容应对。
缺点:
1)没有排序,无法保证趋势递增。
2)UUID往往是使用字符串存储,查询的效率比较低。
3)存储空间比较大,如果是海量数据库,就需要考虑存储量的问题。
4)传输数据量大
5)不可读。
### **3.snowflake算法 **
snowflake是Twitter开源的分布式ID生成算法。
**· 优点:**
1)不依赖于数据库,灵活方便,且性能优于数据库。
2)ID按照时间在单机上是递增的。
**· 缺点:**
1)在单机上是递增的,但是由于涉及到分布式环境,每台机器上的时钟不可能完全同步,也许有时候也会出现不是全局递增的情况。
2)存在时间回退问题,时间回退后,生成的ID就有可能重复。
**· ID结构:**
结果是一个long型的ID,分成四个部分:
0(符号位)— 000···000(41位表示毫秒数)— 000···000(10位表示机器的ID)— 000···000(12位作为序列号)
**· 原理:**
​ ①设置一个初始对照时间戳*lastTime*,默认为0
​ ②获取当前时间戳*currentTime**lastTime*比较,假如在同一毫秒内,序列号+1;假如 *currentTime*>*lastTime*,重置序列号为0;假如*currentTime*<*lastTime*,说明时间倒退应该报错。
​ ③若序列号达到最大值(12位全为1),则会触发等待直到下一毫秒,并将下一毫秒的时间戳作为*currentTime*
​ ④将*lastTime*更新为*currentTime*作为对照时间戳
· **关于如何解决时间回退问题:**
可以使用RingBuffer环形数组,预先生成一批ID,放在数组中。当低于阈值时触发数据填充。填充的时候时间戳为当前的时间戳。
\ No newline at end of file
......@@ -32,9 +32,16 @@
⑥环绕增强,相当于MethodInterceptor
`@Around("Pointcut()")`
```
@Around("Pointcut()")
public Object Around(ProceedingJoinPoint pjp) throws Throwable {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest(); //获取http请求数据
Object object = pjp.proceed();
log.info("Request IP>>>"+request.getRemoteAddr()+"#######Response result>>>" +object);
return object;
}
```
执行顺序:**@Around**===>**@Before**===>**方法调用**===>**@Around**===>**@After**===>**@AfterReturning**
......
......@@ -2,6 +2,8 @@
Springboot整合JPA
#### 使用实例
实体类
```
......@@ -25,10 +27,32 @@ DAO层使用接口继承JpaRepository
public interface UserRepository extends JpaRepository<Person, Long> { }
```
## 使用jpa的 CrudRepository 基本查询
使用jpa的 CrudRepository 基本查询
使用jpa的 PagingAndSortingRepository 分页查询和排序
使用jpa的 Repository 自定义声明式查询方法
使用jpa的 JpaRepository 使用hql、jpql或sql查询,@Query等注解
![image-20200426111508494](upload\image-20200426111508494.png)
#### 关于save方法:
###### 当传入的时候不带自增主键时
​ 无论每次传入的数据是否一样,都会当做一条新的数据插入
​ 即只有一条insert语句,即直接插入一条
​ id为数据库中最后一条ID+1
## 使用jpa的 PagingAndSortingRepository 分页查询和排序
###### 当传入的时候带自增主键时
## 使用jpa的 Repository 自定义声明式查询方法
​ 若数据不存在或者主键不存在,则先select再insert
## 使用jpa的 JpaRepository 使用hql、jpql或sql查询,@Query等注解
\ No newline at end of file
​ 若此主键有数据存在,则先select再update
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论