2019年下半年学习总结

一.本系统生成256SHA加密密码的方法

1.取得登陆的用户名及密码,如:用户名:xxxx@example.com 密码:password

2.打开网站

https://www.freeformatter.com/hmac-generator.html#ad-output

3.输入响应的信息,点击生成即可,如下图:

二.synchronized 和ReentrantLock相关的底层实现

1.关于sychronized,有两种加锁方式,

第一种是加在对象的实例上,也就是一个对象里面的一个非静态方法,加上sychronized关键字,其他线程访问此实例的sychronized方法(此实例的所有同步方法),需要等待线程释放锁,才可以执行,从而实现同步。如果访问此对象的另外一个实例,即使是访问的同步方法,也不能实现同步。

如:public sychronized void add(){}

P p1 = new P();

P p2 = new P();

P就会产生两个实例,如果2个线程访问add的话,不会实现同步。

第二种是加在对象上(类上)。一般的方式是加在对象的static方法上,也就是加在类上。如果加在类上,则不管是这个对象的任何实例,都会实现同步。

public sychronized static void add(){}

sychronized用来修饰方法(静态方法、实例方法)、代码块

常说的sychronized加锁就是指竞争获取对象头markword重量级锁状态下指向monitor类型对象。但是1.6后有变化。

jdk1.6之前,在进入sychronized修饰的方法或代码块之前要先获取重量锁(指的是获取对象头指针指向的monitor类型的对象)其中:当sychronized修饰的是静态方法时获取的是类class对象对应的monitor对象;当sychronized修饰的是实例方法时获取的是该类实例对象对应的monitor方法;当修饰的是代码块时需要自己指定。

monitor对象继承ObjectMonitor,其中ObjectMonitor结构属性为:

可以关注下几个比较关键的属性:

_owner 指向持有ObjectMonitor对象的线程

_WaitSet 存放处于wait状态的线程

_EntryList 存放处于等待锁block状态的线程队列

_recursions 锁的冲入次数

_count 记录该线程获取锁的次数

多个线程在竞争共享数据执行到同步代码块时,会在_EntryList中排队,获得对象monitor的线程在进入_Owner区域时会将monitor_owner设为当前线程,同时计数器_count1。若持有mnitor对象的线程调用了wait()方法会释放monitor_ownernull,计数器_count减一,进入到_WaitSet集合中等待被唤醒。

jdk中独占锁的实现除了使用关键字synchronized,还可以使用ReentrantLock

1.ReentrantLocksynchronized都是独占锁,只允许线程互斥的访问临界区。但是实现上两者不同:synchronized加锁解锁的过程是隐式的,用户不用手动操作,优点是操作简单,但显得不够灵活。一般并发场景使用synchronized的就够了;ReentrantLock需要手动加锁和解锁,且解锁的操作尽量要放在finally代码块中,保证线程正确释放锁。ReentrantLock操作较为复杂,但是因为可以手动控制加锁和解锁过程,在复杂的并发场景中能派上用场。

2.ReentrantLocksynchronized都是可重入的。synchronized因为可重入因此可以放在被递归执行的方法上,且不用担心线程最后能否正确释放锁;而ReentrantLock在重入时要却确保重复获取锁的次数必须和重复释放锁的次数一样,否则可能导致其他线程无法获得该锁。

ReentrantLock主要利用CAS+CLH队列来实现。它支持公平锁和非公平锁,两者的实现类似。

ReentrantLock的基本实现可以概括为:先通过CAS尝试获取锁。如果此时已经有线程占据了锁,那就加入CLH队列并且被挂起。当锁被释放之后,排在CLH队列队首的线程会被唤醒,然后CAS再次尝试获取锁。在这个时候,如果:

非公平锁:如果同时还有另一个线程进来尝试获取,那么有可能会让这个线程抢先获取;

公平锁:如果同时还有另一个线程进来尝试获取,当它发现自己不是在队首的话,就会排到队尾,由队首的线程获取到锁

CLH队列:带头结点的双向非循环链表(如下图所示)

三.分布式锁

基于数据库做分布式锁、基于数据库表版本号做分布式锁、基于数据库排他锁做分布式锁、基于redis做分布式锁、基于zookeeper做分布式锁

四.分布式事务

两阶段提交:

第一阶段:事务协调器要求每个涉及到事务的数据库预提交(precommit)此操作,并反映是否可以提交.

第二阶段:事务协调器要求每个数据库提交数据。

其中,如果有任何一个数据库否决此次提交,那么所有数据库都会被要求回滚它们在此事务中的那部分信息

 

五.分布式缓存

缓存的更新模式

Cache Aside模式

读取失效:cache数据没有命中,查询DB,成功后把数据写入缓存

读取命中:读取cache数据

更新:把数据更新到DB,失效缓存

 

分布式缓存的常见问题

缓存穿透

DB中不存在数据,每次都穿过缓存查DB,造成DB的压力。一般是网络攻击

解决方案:放入一个特殊对象(比如特定的无效对象,当然比较好的方式是使用包装对象),即把这个空放入一个对象到缓存种,避免查询db

缓存击穿

在缓存失效的瞬间大量请求,造成DB的压力瞬间增大

解决方案:更新缓存时使用分布式锁锁住服务,防止请求穿透直达DB

缓存雪崩

大量缓存设置了相同的失效时间,同一时间失效,造成服务瞬间性能急剧下降

解决方案:缓存时间使用基本时间加上随机时间

六.zookeeper集群实现数据一致性

zookeeper的选举机制:

zookeeperleader选举机制一般有三种。LeaderElectionAuthFastLeaderElectionFastLeaderElection (最新默认)

默认的算法是FastLeaderElection,所以这篇主要分析它的选举机制

选举流程简述

目前有5台服务器,每台服务器均没有数据,它们的编号分别是1,2,3,4,5,按编号依次启动,它们的选择举过程如下:

服务器1启动,给自己投票,然后发投票信息,由于其它机器还没有启动所以它收不到反馈信息,服务器1的状态一直属于Looking(选举状态)

服务器2启动,给自己投票,同时与之前启动的服务器1交换结果,由于服务器2的编号大所以服务器2胜出,但此时投票数没有大于半数,所以两个服务器的状态依然是LOOKING

服务器3启动,给自己投票,同时与之前启动的服务器1,2交换信息,由于服务器3的编号最大所以服务器3胜出,此时投票数正好大于半数,所以服务器3成为领导者,服务器1,2成为小弟。

服务器4启动,给自己投票,同时与之前启动的服务器1,2,3交换信息,尽管服务器4的编号大,但之前服务器3已经胜出,所以服务器4只能成为小弟。

服务器5启动,后面的逻辑同服务器4成为小弟。

数据同步

选完 leader 以后,zk 就进入状态同步过程。

1leader 等待 server 连接;

2follower 连接 leader,将最大的 zxid(数据最大id 发送给 leader

3leader 根据 follower zxid 确定同步点;

4、完成同步后通知 follower 已经成为 uptodate 状态;

5follower 收到 uptodate 消息后,又可以重新接受 client 的请求进行服务了。

连接方式:类似于activemqfailover连接方式。

ZooKeeper zk = new ZooKeeper("10.168.x.23:2181,10.168.x.23:2182,10.168.x.23:2183", 3000, new Watcher() {

七、redis集群实现数据一致性

redis-cluster集群架构图

所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽。

节点的fail是通过集群中超过半数的节点检测失效时才生效。

客户端与redis节点直连,不需要中间proxy.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。

redis-cluster把所有的物理节点映射到[0-16383]slot,cluster 负责维护node<->slot<->value

redis-cluster投票:容错

投票过程是集群中所有master参与,如果半数以上master节点与master节点通信超时(cluster-node-timeout),认为当前master节点挂掉.

什么时候整个集群不可用(cluster_state:fail)?

如果集群任意master挂掉,且当前master没有slave.集群进入fail状态,也可以理解成集群的slot映射[0-16383]不完整时进入fail状态

如果集群超过半数以上master挂掉,无论是否有slave,集群进入fail状态.

当部分master节点失败了,或者不能够和大多数节点通信的时候,为了保持可用,Redis集群用一个master-slave模式,这样的话每个hash slot就有1N个副本。

在我们的例子中,集群有ABC三个节点,如果节点B失败了,那么5501-11000之间的hash slot将无法提供服务。然而,当我们给每个master节点添加一个slave节点以后,我们的集群最终会变成由ABC三个master节点和A1B1C1三个slave节点组成,这个时候如果B失败了,系统仍然可用。节点B1B的副本,如果B失败了,集群会将B1提升为新的master,从而继续提供服务。然而,如果BB1同时失败了,那么整个集群将不可用。

Redis集群可能丢失写的第一个原因是因为它用异步复制。

写可能是这样发生的:

客户端写到master B

master B回复客户端OK

master B将这个写操作广播给它的slaves B1B2B3

正如你看到的那样,B没有等到B1B2B3确认就回复客户端了,也就是说,B在回复客户端之前没有等待B1B2B3的确认,这对应Redis来说是一个潜在的风险。所以,如果客户端写了一些东西,B也确认了这个写操作,但是在它将这个写操作发给它的slaves之前它宕机了,随后其中一个slave(没有收到这个写命令)可能被提升为新的master,于是这个写操作就永远丢失了。

八、mq集群数据一致性

activemq的集群部署方式一般有两种:

第一种:broker---clusters方式:实现负载均衡,多个broker之间同步消息。

使用失效转移策略,配置如下:

failover:(uri1,uri2,...,uriN)?transportOptions

一个消费者连接到多个broker集群的中的一个broker,当该broker出问题时,消费者自动连接到其他一个正常的broker

uri:消息服务器的地址

transportOptions参数说明:

randomize:默认为 true ,表示在URI列表中选择URL连接时是否采用随机策略。

initialReconnectDelay:默认为10,单位为毫秒,表示一次尝试重连之间等待的时间。

maxReconnectDelay:默认 30000,单位毫秒,最长重连的时间间隔。

broker之间通过网络互相连接,并共享queue,保证消息同步。各个broker进行消息同步使用的是networkConnection(网络连接器),用于服务器传递消息,分为:静态连接和动态连接。

静态连接:

<networkConnectors>

    <networkConnector uri="static:(tcp://127.0.0.1:61617,tcp://127.0.0.1:61618)"/> </networkConnectors>

动态连接:

<!-- 网络连接器 --><networkConnectors>

    <networkConnector uri="multicast://default"/> </networkConnectors><!-- 传输连接器 --><transprotConnectors>

    <transprotConnector uri="tcp://localhost:0" discoveryUri="multicast://default" /></transprotConnectors>

静态连接器过于局限,动态连接器可随意扩展服务器连接。

 

第二种:master---slaver方式:实现高可用。

通过部署多个broker实例,选举产生一个master和多个slavemaster宕机后由slave接管服务来达到高可用性。Master-Slave的方式虽然能解决多服务热备的高可用问题,但无法解决负载均衡和分布式的问题。Broker Cluster的部署方式刚好可以解决负载均衡的问题。一般两者结合使用。

使用ZooKeeper(集群)注册所有的ActiveMQ Broker。只有其中的一个Broker可以对外提供服务(也就是Master节点),其他的Broker处于待机状态,被视为Slave。如果Master因故障而不能提供服务,则利用ZooKeeper的内部选举机制会从Slave中选举出一个Broker充当Master节点,继续对外提供服务。

 

九、Mysql的主从同步

Mysql-A的数据库事件(例如修改数据库的sql操作语句),都会存储到日志系统A中,在相应的端口(默认3306)通过网络发送给Mysql-BMysql-B收到后,写入本地日志系统B,然后一条条的将数据库事件在数据库Mysql-B中完成。

日志系统A,是MYSQL的日志类型中的二进制日志,也就是专门用来保存修改数据库表的所有动作,即bin log,注意MYSQL会在执行语句之后,释放锁之前,写入二进制日志,确保事务安全。

日志系统B,不是二进制日志,由于它是从MYSQL-A的二进制日志复制过来的,并不是自己的数据库变化产生的,有点接力的感觉,称为中继日志,即relay log

通过上面的机制,可以保证Mysql-AMysql-B的数据库数据一致,但是时间上肯定有延迟,即Mysql-B的数据是滞后的。因此,会出现这样的问题,Mysql-A的数据库操作是可以并发的执行的,但是Mysql-B只能从relay log中一条一条的读取执行。若Mysql-A的写操作很频繁,Mysql-B很可能就跟不上了。

主从同步复制有以下几种方式:

1)同步复制,master的变化,必须等待slave-1,slave-2,...,slave-n完成后才能返回。

2)异步复制,master只需要完成自己的数据库操作即可,至于slaves是否收到二进制日志,是否完成操作,不用关心。MYSQL的默认设置。

3)半同步复制,master只保证slaves中的一个操作成功,就返回,其他slave不管。这个功能,是由googleMYSQL引入的。

(1)若在主从同步的过程中,出现其中一条语句同步失败报错了,则后面的语句也肯定不能同步成功了。例如,主库有一条数据,而从库并没有这一条数据,然而,在主库执行了删除这一条数据的操作,那么从库没有这么一条数据就肯定删除不了,从而报错了。在此时的从数据库的数据同步就失败了,因此后面的同步语句就无法继续执行。

这里提供的解决方法有两种:

1在从数据库中,使用SET全局sql_slave_skip_counter来跳过事件,跳过这一个错误,然后执行从下一个事件组开始。

2在从数据库中,重新连上主数据库。这种操作会直接跳过中间的那些同步语句,可能会导致一些数据未同步过去的问题,但这种操作也是最后的绝招。最好就是令从数据库与主数据库的数据结构和数据都一致了之后,再来恢复主从同步的操作。

十、setnx实现分布式锁

Setnx  key  value

如果key不存在,则说明设置成功,返回1,证明获得锁。(释放锁直接删除key即可)

如果key已经存在,则说明设置失败,返回0,证明未获得锁。

设置锁时,需要有超时时间和过期时间,避免忘记释放锁。超过超时时间的时候,其他线程就可以del key值,从而获取锁。详细如下:

然后释放锁的时候就直接 DEL掉;
简单思路是这样,但是这样会有很多问题

如果一个进程获得锁之后,断开了与redis的连接(进程挂断或者网络中断),那么锁一直的不断释放,其他的进程就一直获取不到锁,就出现了 死锁

然而,锁超时时,我们不能简单地使用 DEL 命令删除键 lock.foo 以释放锁。考虑以下情况,进程P1已经首先获得了锁 lock.foo,然后进程P1挂掉了。进程P2P3正在不断地检测锁是否已释放或者已超时,执行流程如下:
1 . P2P3进程读取键 lock.foo 的值,检测锁是否已超时(通过比较当前时间和键 lock.foo 的值来判断是否超时)
2.P2P3进程发现锁 lock.foo 已超时
3.P2执行 DEL lock.foo命令
4.P2执行 SETNX lock.foo命令,并返回1,即P2获得锁
5.P3执行 DEL lock.foo命令将P2刚刚设置的键 lock.foo 删除(这步是由于P3刚才已检测到锁已超时)
6.P3执行 SETNX lock.foo命令,并返回1,即P3获得锁
7.P2P3同时获得了锁

从上面的情况可以得知,在检测到锁超时后,进程不能直接简单地执行 DEL 删除键的操作以获得锁。

为了解决上述算法可能出现的多个进程同时获得锁的问题,我们再来看以下的算法。
我们同样假设进程P1已经首先获得了锁 lock.foo,然后进程P1挂掉了。接下来的情况:

进程P4执行 SETNX lock.foo 以尝试获取锁
由于进程P1已获得了锁,所以P4执行 SETNX lock.foo 返回0,即获取锁失败
P4执行 GET lock.foo 来检测锁是否已超时,如果没超时,则等待一段时间,再次检测
如果P4检测到锁已超时,即当前的时间大于键 lock.foo 的值,P4会执行以下操作
GETSET lock.foo <current Unix timestamp + lock timeout + 1>
由于 GETSET 操作在设置键的值的同时,还会返回键的旧值,通过比较键 lock.foo 的旧值是否小于当前时间,可以判断进程是否已获得锁
假如另一个进程P5也检测到锁已超时,并在P4之前执行了 GETSET 操作,那么P4GETSET 操作返回的是一个大于当前时间的时间戳,这样P4就不会获得锁而继续等待。注意到,即使P4接下来将键 lock.foo 的值设置了比P5设置的更大的值也没影响。
另外,值得注意的是,在进程释放锁,即执行 DEL lock.foo 操作前,需要先判断锁是否已超时。如果锁已超时,那么锁可能已由其他进程获得,这时直接执行 DEL lock.foo 操作会导致把其他进程已获得的锁释放掉。

假如Redis里面有1亿个key,其中有10wkey是以某个固定的已知的前缀开头的,如果将它们全部找出来? 
 使用keys指令可以扫出指定模式的key列表。 
 如果这个redis正在给线上的业务提供服务,那使用keys指令会有什么问题? 
 这个时候你要回答redis关键的一个特性:redis的单线程的。keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用scan指令scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。

 十一、内存

jvmjmm(内存模型)

内存泄漏与内存溢出的区别指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。

是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。就是内存溢出

十二、公平锁和非公平锁

公平锁(Fair):加锁前检查是否有排队等待的线程,优先排队等待的线程,先来先得

非公平锁(Nonfair):加锁时不考虑排队等待问题,直接尝试获取锁,获取不到自动到队尾等待

非公平锁性能比公平锁高5~10倍,因为公平锁需要在多核的情况下维护一个队列

Java中的ReentrantLock 默认的lock()方法采用的是非公平锁。

posted on 2019-08-14 20:04  灵之海  阅读(369)  评论(0编辑  收藏  举报