2020年12月29日
Java静态内部类实现雪花算法(SnowFlake) 前言 在生成表主键时,通常可以考虑自增主键/UUID/雪花算法 - 自增主键: - 1.容易被爬虫遍历数据。 - 2.分库分表可能会有ID冲突。 - UUID - 1.太长,有索引碎片 - 2.无序 - 雪花算法 - 适合在分布式场景下生成唯一ID,保证唯一可排序
原理 SnowFlake算法生成ID的是一个64bit大小的整数。 由于在Java中64bit的整数是long类型,所以在Java中SnowFlake算法生成的id就是long来存储的。 - id结构如下: 0-0000000000 0000000000 0000000000 0000000000 0-00000-00000-000000000000
算法描述: 1bit 因为二进制中最高位是符号位,1表示负数,0表示正数。生成的ID都是正整数,所以最高位固定为0。 41bit-时间戳 精确到毫秒级,41位的长度可以使用69年。时间位还有一个很重要的作用是可以根据时间进行排序。 10bit-工作机器id,Twitter实现中使用前5位作为数据中心标识,后5位作为机器标识,可以部署2^10 = 1024个节点; 12bit-序列号 这个是用来记录同一个毫秒内产生的不同 id。 >12位(bit)可以表示的最大正整数是2^{12}-1 = 4095,即可以用0、1、2、3、….4094这4095个数字,来表示同一机器同一时间截(毫秒)内产生的4095个ID序号。 实现代码 /** * 描述: Twitter的分布式自增ID雪花算法snowflake (Java版) * **/ public class SnowFlake { /** * 起始的时间戳 * 2021-01-01 00:00:00 */ private final static long START_STMP = 1609430400000L; /** * 每一部分占用的位数 */ private final static long SEQUENCE_BIT = 12; //序列号占用的位数 private final static long MACHINE_BIT = 5; //机器标识占用的位数 private final static long DATACENTER_BIT = 5;//数据中心占用的位数 /** * 每一部分的最大值 */ private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT); private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT); private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT); /** * 每一部分向左的位移 */ private final static long MACHINE_LEFT = SEQUENCE_BIT; private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT; private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT; private long datacenterId; //数据中心 private long machineId; //机器标识 private long sequence = 0L; //序列号 private long lastStmp = -1L;//上一次时间戳 public SnowFlake(long datacenterId, long machineId) { if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) { throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0"); } if (machineId > MAX_MACHINE_NUM || machineId < 0) { throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0"); } this.……
阅读全文
2020年12月25日
Oauth2.0 什么是Oauth2.0? OAuth 2.0 是目前最流行的授权机制,用来授权第三方应用,获取用户数据。 简单说:OAuth 就是一种授权机制。数据的所有者告诉系统,同意授权第三方应用进入系统,获取这些数据。系统从而产生一个短期的进入令牌(token),用来代替密码,供第三方应用使用。
令牌与密码 令牌(token)与密码(password)的作用是一样的,都可以进入系统,但是有三点差异。 - (1)令牌是短期的,到期会自动失效,用户自己无法修改。密码一般长期有效,用户不修改,就不会发生变化。
(2)令牌可以被数据所有者撤销,会立即失效。以上例而言,屋主可以随时取消快递员的令牌。密码一般不允许被他人撤销。
(3)令牌有权限范围(scope),比如只能进小区的二号门。对于网络服务来说,只读令牌就比读写令牌更安全。密码一般是完整权限。
上面这些设计,保证了令牌既可以让第三方应用获得权限,同时又随时可控,不会危及系统安全。这就是 OAuth 2.0 的优点。
OAuth 2.0 对于如何颁发令牌?四种授权类型(authorization grant) 授权码(authorization code)方式 授权码方式指的是:第三方应用先申请一个授权码,然后再用该码获取令牌。 这种方式是最常用的流程,安全性也最高,它适用于那些有后端的 Web 应用。授权码通过前端传送,令牌则是储存在后端,而且所有与资源服务器的通信都在后端完成。这样的前后端分离,可以避免令牌泄漏。 - 第一步:A网站生成授权链接,用户点击后跳转到B网站,授权用户数据给A网站使用。
https://b.com/oauth/authorize? response_type=code& client_id=CLIENT_ID& redirect_uri=CALLBACK_URL& scope=read 主要参数:
response_type参数表示要求返回授权码(code) client_id参数让 B 知道是谁在请求 redirect_uri参数是 B 接受或拒绝请求后的跳转网址 scope参数表示要求的授权范围(这里是只读) 第二步:跳转到B网站后,需要用户确认授权,用户确认后跳转到第一步的redirect_url,并返回一个授权码。
https://a.com/callback?code=AUTHORIZATION_CODE 第三步:A网站拿到授权码后,后台请求B网站,获取令牌。
https://b.com/oauth/token? client_id=CLIENT_ID& client_secret=CLIENT_SECRET& grant_type=authorization_code& code=AUTHORIZATION_CODE& redirect_uri=CALLBACK_URL 主要参数
client_id参数和client_secret参数用来让 B 确认 A 的身份(client_secret参数是保密的,因此只能在后端发请求)。 grant_type参数的值是AUTHORIZATION_CODE,表示采用的授权方式是授权码。 code参数是上一步拿到的授权码。 redirect_uri参数是令牌颁发后的回调网址。 第四步:B 网站收到请求以后,就会颁发令牌。具体为向第三 步的redirect_url发送一段令牌json。……
阅读全文
2020年12月24日
Condition Condition概述 在线程的同步时可以使一个线程阻塞而等待一个信号,同时放弃锁使其他线程可以能竞争到锁。
在synchronized中我们可以使用Object的wait()和notify方法实现这种等待和唤醒。
在Lock可以实现相同的功能就是通过Condition。Condition中的await()和signal()/signalAll()就相当于Object的wait()和notify()/notifyAll()。
除此之外,Condition还是对多线程条件进行更精确的控制。notify()是唤醒一个线程,但它无法确认是唤醒哪一个线程。 但是,通过Condition,就能明确的指定唤醒读线程。
参考:https://www.cnblogs.com/qdhxhz/p/9206076.html……
阅读全文
2020年12月19日
线程的生命周期和通讯机制 生命周期 ###1、yield()方法 - yield()让当前正在运行的线程回到就绪,以允许具有相同优先级的其他线程获得运行的机会。但是,实际中无法保证yield()达到让步的目的,因为,让步的线程可能被线程调度程序再次选中。
同时yield()不会放弃锁资源,所以有可能会出现死锁。 ###2、wait和sleep方法的区别 - 1)第一个很重要的区别就是,wait方法必须正在同步环境下使用,比如synchronized方法或者同步代码块。如果你不在同步条件下使用,会抛出IllegalMonitorStateException异常。另外,sleep方法不需要再同步条件下调用,你可以任意正常的使用。
2)第二个区别是,wait方法用于和定义于Object类的,而sleep方法操作于当前线程,定义在java.lang.Thread类里面。
3)第三个区别是,调用wait()的时候方法会释放当前持有的锁,而sleep方法不会释放任何锁。
###3、wait和sleep方法使用场景 -(1)wait方法定义在Object类里面,所有对象都能用到,一般wait()和notify()方法或notifyAll使用于线程间的通信。
-(2)sleep()方法用于暂停当前线程的执行。
###4、join方法() - thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。 - 比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。 ###5、stop方法 线程启动完毕后,在运行可能需要终止,Java提供的终止方法只有一个stop,但是不建议使用此方法,因为它有以下三个问题:
1)stop方法是过时的。 从Java编码规则来说,已经过时的方式不建议采用.
2)stop方法会导致代码逻辑不完整 stop方法是一种”恶意” 的中断,一旦执行stop方法,即终止当前正在运行的线程,不管线程逻辑是否完整,这是非常危险的.
3)stop方法会破坏原子逻辑 多线程为了解决共享资源抢占的问题,使用了锁的概念,避免资源不同步,但是正是因为此原因,stop方法却会带来更大的麻烦,它会丢弃所有的锁,导致原子逻辑受损
线程通讯 1.依次运行用join 2.交叉运行用wait()和notify()或者notifyAll() 3.四个线程 A B C D,其中 D 要等到 A B C 全执行完毕后才执行,而且 A B C 是同步运行的。 用CountdownLatch,主要方法latch.await(),latch.countDown();
4.三个运动员各自准备,等到三个人都准备好后,再一起跑 用CyclicBarrier
5.子线程完成某件任务后,把得到的结果回传给主线程 用FutureTask和Callable……
阅读全文
2020年12月18日
GC算法和种类 GC的工作区域 1.不是GC的工作区域 程序计数器、虚拟机栈和本地方法栈三个区域是线程私有的,随线程生而生,随线程灭而灭; 在这几个区域不需要过多考虑回收的问题方法结束或线程结束时,内存自然就跟随着回收了。 2.GC的工作区域(哪些内存需要GC回收?) 垃圾回收重点关注的是堆和方法区部分的内存。 程序处于运行期间才能知道会创建哪些对象,这部分内存的分配和回收都是动态的,所以垃圾回收器所关注的主要是这部分的内存。 垃圾对象的判定 Java堆中存放着几乎所有的对象实例,垃圾收集器对堆中的对象进行回收前,要先确定这些对象是否还有用,哪些还活着。对象死去的时候才需要回收。 1. 引用计数法 在堆中存储对象时,在对象头处维护一个counter计数器,如果一个对象增加了一个引用与之相连,则将counter++。 如果一个引用关系失效则counter–。如果一个对象的counter变为0,则说明该对象已经被废弃,不处于存活状态。 2. 可达性分析算法 通过一系列名为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,就证明此对象是不可用的。 - 可作为GC Roots的对象包括下面几种: (1)虚拟机栈(栈帧中的本地变量表)中引用的对象。 (2)方法区中的常量、类静态属性引用的对象(静态变量)。 (3)本地方法栈中JNI(Native方法)的引用对象。
垃圾回收算法 标记清除算法 (1). 标记阶段:找到所有可访问的对象,做个标记 (2). 清除阶段:遍历堆,把未被标记的对象回收 缺点: (1)因为涉及大量的内存遍历工作,所以执行性能较低 (2)对象被清除之后,被清除的对象留下内存的空缺位置会造成内存不连续,空间浪费。
标记整理算法 适合用于存活对象较多的场合,如老年代。 (1)、标记阶段:它的第一个阶段与标记/清除算法是一模一样的。 (2)、整理阶段:移动所有存活的对象,且按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收。 优点:内存连续,消除复制算法内存减半的代价问题 缺点:不仅要标记所有存活对象,还要整理所有存活对象的引用地址。性能低于复制算法。
复制算法 复制算法简单来说就是把内存一分为二,但只使用其中一份,在垃圾回收时,将正在使用的那份内存中存活的对象复制到另一份空白的内存中,最后将正在使用的内存空间的对象清除,完成垃圾回收。 优点:复制算法使得每次都只对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。 缺点:复制算法的代价是将内存缩小为原来的一半,这个太要命了。
**现在的虚拟机使用复制算法来进行新生代的内存回收。因为在新生代中绝大多数的对象都是“朝生夕亡”,所以不需要将整个内存分为两个部分,而是分为三个部分,一块为Eden(伊甸区)和两块较小的Survivor(幸存区)空间(默认比例->8:1:1)。每次使用Eden和其中的一块Survivor,垃圾回收时候将上述两块中存活的对象复制到另外一块Survivor上,同时清理上述Eden和Survivor。所以每次新生代就可以使用90%的内存。只有10%的内存是浪费的。(不能保证每次新生代都少于10%的对象存活,当在垃圾回收复制时候如果一块Survivor不够时候,需要老年代来分担,大对象直接进入老年代) **
总的来讲:复制算法不适用于存活对象较多的场合,如老年代(复制算法适合做新生代的GC)
三种算法比较 相同点: 进行GC时需要暂停应用程序。 区别: 效率:复制算法>标记-整理算法>标记-清除算法; 内存整齐度:复制算法=标记-整理算法>标记-清除算法 内存利用率:标记-整理算法=标记-清除算法>复制算法
分代收集思想 新生代:由于存活的对象相对比较少,因此可以采用复制算法该算法效率比较快。 > 新生代包含Eden(伊甸区)和两块Survivor(幸存区)
老年代:由于存活的对象比较多哈,可以采用标记-清除算法或是标记-整理算法。
java8以后,已经没有(PermGen Space)永久区了,之前永久区存放的东西基本上放到了元空间(Meta Space)中。
参考:https://www.……
阅读全文
2020年12月18日
垃圾回收器 基础概念 1、并发和并行 - a:并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。 - b:并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集程序运行在另一个CPU上。
2、新生代 GC 和老年代GC - a:新生代GC(Minor GC):指发生在新生代的垃圾收集动作,因为Java对象大多都具备朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也比较快。 - b:老年代GC(Major GC / Full GC):指发生在老年代的GC,出现了Major GC,经常会伴随至少一次的Minor GC(但非绝对的)。Major GC的速度一般会比Minor GC慢10倍以上。
3、吞吐量 - 吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量 = 运行用户代码时间 /(运行用户代码时间 + 垃圾收集时间)。
虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。
垃圾收集器 Serial收集器 新生代、老年代使用串行回收;新生代复制算法、老年代标记-压缩;收集过程中服务会暂停。 参数控制:-XX:+UseSerialGC 串行收集器 ParNew收集器
新生代并行,老年代串行;新生代复制算法、老年代标记-压缩;ParNew收集器其实就是Serial收集器的多线程版本。 参数控制: -XX:+UseParNewGC ParNew收集器 -XX:ParallelGCThreads 限制线程数量 Parallel Scavenge收集器
新生代复制算法。老年代标记压缩,串行 参数控制:-XX:+UseParallelGC Serial Old收集器
它是Serial收集器的老年代版,它同样是一个单线程收集器,使用“标记–整理”算法。
Parallel Old 收集器……
阅读全文
2020年12月18日
性能调优 调优策略 调优的目的 调优的最终目的当然增大吞吐量,减少暂停时间,映射到GC层面主要关心下面这两点: (1)将转移到老年代的对象数量降低到最小。 (2)减少full GC的执行时间。(尽量减少GC的次数)
那什么情况对象会转移到老年代 主要有这四种:
(1)新生代对象每经历依次minor gc,年龄会加一,当达到年龄阀值会直接进入老年代。阀值大小一般为15。 (2)Survivor空间中年龄所有对象大小的总和大于survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,而无需等到年龄阀值。 (3)大对象直接进入老年代。 (4)新生代复制算法需要一个survivor区进行轮换备份,如果出现大量对象在minor gc后仍然存活的情况时,就需要老年代进行分配担保,让survivor无法容纳的对象直接进入老年代。
再来分析为什么说要减少full GC时间次数,那得先看GC的两大分类
Partial GC:并不收集整个GC堆的模式 Young GC:只收集young gen的GC Old GC:只收集old gen的GC。只有CMS的concurrent collection是这个模式 Mixed GC:收集整个young gen以及部分old gen的GC。只有G1有这个模式 Full GC:针对整个新生代、老生代、元空间(metaspace,java8以上版本取代perm gen)的全局范围的GC。
一般Full GC所花费的时间是Young GC的十倍,这里就明白为什么要减少Full GC的次数了。
哪些方面可以考虑调优? (1)减少使用全局变量和大对象。 (2)新生代和老年代的大小是否合适。 (3)新生代和老年代所占的比例是否合适。 (4)幸存者区和新生区所占的比例到是否合适。 (5)选择合适的GC收集器。
什么情况说明GC已经不错了呢? 此外,如果GC执行时间满足下列所有条件,就没有必要进行GC优化了: Minor GC执行非常迅速(50ms以内) Minor GC没有频繁执行(大约10s执行一次) Full GC执行非常迅速(1s以内) Full GC没有频繁执行(大约10min执行一次) 括号中的数字并不是绝对的,它们也随着服务的状态而变化。
二、调优经验(规则) 这些规则,一般是大家比较建议的,可以作为初始配置的时候进行配置建议,当然具体的还得通过JVM工具监测来具体分析。 (1) -Xmx 和-Xms 一般设置为一样大小。这样能稍微提高GC的运行效率,因为他/她不再需要估算堆是否需要调整大小了。
(2)官方推荐新生代占堆的3/8。 (3)幸存区占新生代的1/10。 (4)垃圾收集器如果内存比较大建议G1收集器,当然也可以用CMS收集器。 (5)-XX:+DisableExplicitGC禁止System.……
阅读全文
2020年12月8日
系统高并发 1.集群化部署 采用nginx负载均衡将请求分发到多台机器上。
2.数据库分库(比较麻烦)分表分区、读写分离 3.缓存集群 写数据库的时候同时写一份数据到缓存集群里,然后用缓存集群来承载大部分的读请求。这样的话,通过缓存集群,就可以用更少的机器资源承载更高的并发。
4.消息中间件 可以引入消息中间件集群,把允许异步化的请求请求写入MQ,然后基于MQ做一个削峰填谷。……
阅读全文
2020年11月24日
Explain Explain 列 mysql> explain select * from users where userid=1; +----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+ | 1 | SIMPLE | users | NULL | const | PRIMARY | PRIMARY | 8 | const | 1 | 100.00 | NULL | +----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+ 1 row in set, 1 warning (0.……
阅读全文
2020年11月24日
SQL优化 1.SQL语句中过滤条件where和having的区别 where 是一个约束声明,使用 where 约束来自数据库的数据,where 是在结果返回之前起作用,where 中不能使用聚合函数。 Having 是一个过滤声明,是在查询返回结果集以后对查询结果进行的过滤操作,在 Having 中可以使用聚合函数。 在查询过程中,where 子句执行优先级高于聚合语句。聚合语句(sum,min,max,avg,count)优先级高于 having 子句。 2.优化查询 MySQL只有对以下操作符才使用索引:<,<=,=,>,>=,BETWEEN,IN,以及右模糊查询LIKE(’a%‘)。 对于不等于(!= 和 <>)会放弃使用索引而进行全表扫描。 对于左模糊like ‘%…’无法直接使用索引,但可以利用reverse,变化成 右模糊like ‘…%’。 对于联合索引来说,如果存在范围查询,比如between、>、<等条件时,会造成后面的索引字段失效。 对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。 在 where 子句中对字段进行 null 值判断,会导致引擎放弃使用索引而进行全表扫描,所以字段要有默认值 在 where 子句中对字段进行函数、算术运算或其他表达式运算,会导致引擎放弃使用索引而进行全表扫描。 如果字段类型是字符串,where 时一定用引号括起来,否则索引失效 将大的 delete,update 或者 insert 查询变成多个小查询 如果插入数据过多,考虑批量插入 select语句务必指明字段名称,尽量避免使用”select * “。因为它会进行全表扫描。 Inner join 、left join、right join,优先使用 Inner join。如果是 left join,左边表结果尽量小。Inner join 内连接,在两张表进行连接查询时,只保留两张表中完全匹配的结果集。left join 在两张表进行连接查询时,会返回左表所有的行,即使在右表中没有匹配的记录。right join 在两张表进行连接查询时,会返回右表所有的行,即使在左表中没有匹配的记录。都满足 SQL 需求的前提下,推荐优先使用 Inner join(内连接),如果要使用 left join,左边表数据结果尽量小,如果有条件的尽量放到左边处理。 区分 in 和 exists、not in 和not exists……
阅读全文