目录java
其实zk的应用场景都是针对zk能够监听某个节点,而且能够感知到节点的修改或者节点的数据的修改,这样就能够利用根据节点或者节点的数据变化这一特性而应用到不少的场景中,只要抓住这一个特性就能够了。node
这个实际上是zk很经典的一个用法,简单来讲,就比如,你A系统发送个请求到mq,而后B消息消费以后处理了。那A系统如何知道B系统的处理结果?用zk就能够实现分布式系统之间的协调工做。A系统发送请求以后能够在zk上对某个节点的值注册个监听器,一旦B系统处理完了就修改zk那个节点的值,A立马就能够收到通知,完美解决。
mysql
对某一个数据连续发出两个修改操做,两台机器同时收到了请求,可是只能一台机器先执行另一个机器再执行。那么此时就可使用zk分布式锁,一个机器接收到了请求以后先获取zk上的一把分布式锁,就是能够去建立一个znode,接着执行操做;而后另一个机器也尝试去建立那个znode,结果发现本身建立不了,由于被别人建立了。。。。那只能等着,等第一个机器执行完了本身再执行。
web
zk能够用做不少系统的配置信息的管理,好比kafka、storm等等不少分布式系统都会选用zk来作一些元数据、配置信息的管理,包括dubbo注册中心不也支持zk么。
redis
这个应该是很常见的,好比hadoop、hdfs、yarn等不少大数据系统,都选择基于zk来开发HA高可用机制,就是一个重要进程通常会作主备两个,主进程挂了立马经过zk感知到切换到备用进程。
算法
SET my:lock 随机值 NX PX 30000,这个命令就ok,这个的NX的意思就是只有key不存在的时候才会设置成功,PX 30000的意思是30秒后锁自动释放。别人建立的时候若是发现已经有了就不能加锁了。
PX 30000避免一直占有锁,会自动释放锁。
释放锁就是删除key,可是通常能够用lua脚本删除,判断value同样才删除:
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
为何须要判断value的值呢?
由于若是某个客户端获取到了锁,可是阻塞了很长时间才执行完,此时可能已经自动释放锁了,此时可能别的客户端已经获取到了这个锁,要是你这个时候直接删除key的话会有问题,因此得用随机值加上面的lua脚原本释放锁。
可是此种仍是存在必定的问题,由于若是是普通的redis单实例,那就是单点故障。或者是redis普通主从,那redis主从异步复制,若是主节点挂了,key还没同步到从节点,此时从节点切换为主节点,别人就会拿到锁。
spring
第二种,使用的是RedLock算法(有争议的算法)
这个场景是假设有一个redis cluster,有5个redis master实例。而后执行以下步骤获取一把锁:
1)获取当前时间戳,单位是毫秒
2)跟上面相似,轮流尝试在每一个master节点上建立锁,过时时间较短,通常就几十毫秒
3)尝试在大多数节点上创建一个锁,好比5个节点就要求是3个节点(n / 2 +1)
4)客户端计算创建好锁的时间,若是创建锁的时间小于超时时间,就算创建成功了
5)要是锁创建失败了,那么就依次删除这个锁
6)只要别人创建了一把分布式锁,你就得不断轮询去尝试获取锁
sql
其实能够作的比较简单,就是某个节点尝试建立临时znode,此时建立成功了就获取了这个锁;这个时候别的客户端来建立锁会失败,只能注册个监听器监听这个锁。释放锁就是删除这个znode,一旦释放掉就会通知客户端,而后有一个等待着的客户端就能够再次从新枷锁。
固然也能够基于建立有序临时节点,这样的话,就能够实现公平锁。先建立锁的就能够先获取锁。数据库
redis分布锁,须要字段不断去尝试获取锁,比较消耗性能
zk分布锁,获取不到锁,注册个监听器便可,不须要不断主动尝试获取锁,性能开销较小
而且,若是是redis获取锁的那个客户端bug了或者挂了,那么只能等待超时时间以后才能释放锁;而zk的话,由于建立的是临时znode,只要客户端挂了,znode就没了,此时就自动释放锁浏览器
浏览器有个cookie,在一段时间内这个cookie都存在,而后每次发请求过来都带上一个特殊的jsessionid cookie,就根据这个东西,在服务端能够维护一个对应的session域,里面能够放点儿数据。
通常只要你没关掉浏览器,cookie还在,那么对应的那个session就在,可是cookie没了,session就没了。常见于什么购物车之类的东西,还有登陆状态保存之类的。
这个其实还挺方便的,就是使用session的代码跟之前同样,仍是基于tomcat原生的session支持便可,而后就是用一个叫作Tomcat RedisSessionManager的东西,让全部咱们部署的tomcat都将session数据存储到redis便可。
在tomcat的配置文件中,配置一下
<Valve className="com.orangefunction.tomcat.redissessions.RedisSessionHandlerValve" /> <Manager className="com.orangefunction.tomcat.redissessions.RedisSessionManager" host="{redis.host}" port="{redis.port}" database="{redis.dbnum}" maxInactiveInterval="60"/>
搞一个相似上面的配置便可,你看是否是就是用了RedisSessionManager,而后指定了redis的host和 port就ok了。
<Valve className="com.orangefunction.tomcat.redissessions.RedisSessionHandlerValve" /> <Manager className="com.orangefunction.tomcat.redissessions.RedisSessionManager" host="{redis.host}" port="{redis.port}" database="{redis.dbnum}" maxInactiveInterval="60"/>
还能够用上面这种方式基于redis哨兵支持的redis高可用集群来保存session数据,都是能够的的
分布式会话的这个东西重耦合在tomcat中,若是我要将web容器迁移成jetty,难道从新把jetty都配置一遍吗?
spring基本上是一站式解决方案了,spirng cloud作微服务了,spring boot作脚手架了,因此用sping session是一个很好的选择。
pom.xml <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> <version>1.2.1.RELEASE</version> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.8.1</version> </dependency>
spring配置文件种 <bean id="redisHttpSessionConfiguration" class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"> <property name="maxInactiveIntervalInSeconds" value="600"/> </bean> <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"> <property name="maxTotal" value="100" /> <property name="maxIdle" value="10" /> </bean> <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" destroy-method="destroy"> <property name="hostName" value="${redis_hostname}"/> <property name="port" value="${redis_port}"/> <property name="password" value="${redis_pwd}" /> <property name="timeout" value="3000"/> <property name="usePool" value="true"/> <property name="poolConfig" ref="jedisPoolConfig"/> </bean>
web.xml <filter> <filter-name>springSessionRepositoryFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSessionRepositoryFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
将一个系统拆分为多个子系统,用微服务dubbo,spring cloud来搞,而后每隔系统连一个数据库,这样原来是一个库,如今多个数据库,在必定程度上提升并发能力
对于应对高并发,必定得用缓存。大部分的高并发场景,都是读多写少你彻底能够在数据库和缓存里都写一份,而后读的时候大量走缓存不就得了。毕竟redis轻轻松松单机几万的并发啊。没问题的。因此你能够考虑考虑你的项目里,那些承载主要请求的读场景,怎么用缓存来抗高并发。
可能仍是会出现高并发写的场景,好比说一个业务操做里要频繁搞数据库几十次,增删改增删改,疯了。那高并发绝对搞挂系统,要是用redis来承载写那确定不行,人家是缓存,数据随时就被LRU了,数据格式还无比简单,没有事务支持。因此该用mysql还得用mysql。这个时候可使用MQ,大量的写请求灌入MQ里,排队慢慢玩儿,后边系统消费后慢慢写,控制在mysql承载范围以内。因此你得考虑考虑你的项目里,那些承载复杂写业务逻辑的场景里,如何用MQ来异步写,提高并发性。MQ单机抗几万并发也是能够的。
能到了最后数据库层面仍是免不了抗高并发的要求,好吧,那么就将一个数据库拆分为多个库,多个库来抗更高的并发;而后将一个表拆分为多个表,每一个表的数据量保持少一点,提升sql跑的性能。
这个就是说大部分时候数据库可能也是读多写少,不必全部请求都集中在一个库上吧,能够搞个主从架构,主库写入,从库读取,搞一个读写分离。读流量太多的时候,还能够加更多的从库。
es是分布式的,能够随便扩容,分布式自然就能够支撑高并发,由于动不动就能够扩容加机器来抗更高的并发。那么一些比较简单的查询、统计类的操做,能够考虑用es来承载,还有一些全文搜索类的操做,也能够考虑用es来承载。
分库:就是一个库通常而言,最多支撑到并发2000,若是并发超过了2000,通常须要扩容了,并且一个健康的单库并发值最好保持在每秒1000左右,不要太大。那么你能够将一个库的数据拆分到多个库中,访问的时候就访问一个库好了。
分表:是把一个表的数据放到多个表中,而后查询的时候你就查一个表。好比按照用户id来分表,将一个用户的数据就放在一个表中。而后操做的时候你对一个用户就操做那个表就行了。这样能够控制每一个表的数据量在可控的范围内,好比每一个表就固定在200万之内。
水平拆分的意思,就是把一个表的数据给弄到多个库的多个表里去,可是每一个库的表结构都同样,只不过每一个库表放的数据是不一样的,全部库表的数据加起来就是所有数据。水平拆分的意义,就是将数据均匀放更多的库里,而后用多个库来抗更高的并发,还有就是用多个库的存储容量来进行扩容。
垂直拆分的意思,就是把一个有不少字段的表给拆分红多个表,或者是多个库上去。每一个库表的结构都不同,每一个库表都包含部分字段。通常来讲,会将较少的访问频率很高的字段放到一个表里去,而后将较多的访问频率很低的字段放到另一个表里去。由于数据库是有缓存的,你访问频率高的行字段越少,就能够在缓存里缓存更多的行,性能就越好。这个通常在表层面作的较多一些。
能够是按照id得hash来分(扩容起来特别困难),也能够按照时间的range来分。
(1)停机迁移
系统停掉,而后将单库单表的数据读取出来,而后插入到数据库中间件种。修改系统的配置,
(2)双写迁移
就是在线上系统里面,以前全部写库的地方,增删改操做,都除了对老库增删改,都加上对新库的增删改,这就是所谓双写,同时写俩库,老库和新库。
若是扩容了,原来扩容3个库,每一个库4个表,如今须要扩容成6个库,每一个库须要12个表。
(1)停机扩容(极度不靠谱)
由于既然分库分表就说明数据量实在是太大了,可能多达几亿条,甚至几十亿,你这么玩儿,可能会出问题。
(2)优化方案
扩容的时候,申请增长更多的数据库服务器,装好mysql,倍数扩容,4台服务器,扩到8台服务器,16台服务器。这样的话,路由规则是不须要修改的
由dba负责将原先数据库服务器的库,迁移到新的数据库服务器上去,不少工具,库迁移,比较便捷
而后修改一些配置便可,调整迁移的库所在数据库服务器地址
从新发布系统,上线,原先的路由规则变都不用变,直接能够基于2倍的数据库服务器的资源,继续进行线上系统的提供服务
就是指在系统里每次获得一个id,都是往一个库的一个表里插入一条没什么业务含义的数据,而后获取一个数据库自增的一个id。拿到这个id以后再往对应的分库分表里去写入。
这个方案的好处就是方便简单,谁都会用;缺点就是单库生成自增id,要是高并发的话,就会有瓶颈的;若是你硬是要改进一下,那么就专门开一个服务出来,这个服务每次就拿到当前id最大值,而后本身递增几个id,一次性返回一批id,而后再把当前最大id值修改为递增几个id以后的一个值;可是不管怎么说都是基于单个数据库。
适合的场景:你分库分表就俩缘由,要不就是单库并发过高,要不就是单库数据量太大;除非是你并发不高,可是数据量太大致使的分库分表扩容,你能够用这个方案,由于可能每秒最高并发最多就几百,那么就走单独的一个库和表生成自增主键便可。
并发很低,几百/s,可是数据量大,几十亿的数据,因此须要靠分库分表来存放海量的数据
好处就是本地生成,不要基于数据库来了;很差之处就是,uuid太长了,做为主键性能太差了,不适合用于主键。
适合的场景:若是你是要随机生成个什么文件名了,编号之类的,你能够用uuid,可是做为主键是不能用uuid的。
获取当前时间便可,可是问题是,并发很高的时候,好比一秒并发几千,会有重复的状况,这个是确定不合适的。基本就不用考虑了。
适合的场景:通常若是用这个方案,是将当前时间跟不少其余的业务字段拼接起来,做为一个id,若是业务上你以为能够接受,那么也是能够的。你能够将别的业务字段值跟当前时间拼接起来,组成一个全局惟一的编号,订单编号,时间戳 + 用户id + 业务含义编码
twitter开源的分布式id生成算法,就是把一个64位的long型的id,1个bit是不用的,用其中的41 bit做为毫秒数,用10 bit做为工做机器id,12 bit做为序列号
主库将变动写binlog日志,而后从库链接到主库以后,从库有一个IO线程,将主库的binlog日志拷贝到本身本地,写入一个中继日志中。接着从库中有一个SQL线程会从中继日志读取binlog,而后执行binlog日志中的内容,也就是在本身本地再次执行一遍SQL,这样就能够保证本身跟主库的数据是同样的。
从库同步主库数据的过程是串行化(单线程)的,也就是说主库上并行的操做,在从库上会串行执行。因此这就是一个很是重要的点了,因为从库从主库拷贝日志以及串行执行SQL的特色,在高并发场景下,从库的数据必定会比主库慢一些,是有延时的。因此常常出现,刚写入主库的数据多是读不到的,要过几十毫秒,甚至几百毫秒才能读取到。
并且这里还有另一个问题,就是若是主库忽然宕机,而后刚好数据还没同步到从库,那么有些数据可能在从库上是没有的,有些数据可能就丢失了。
半同步复:指的就是主库写入binlog日志以后,就会将强制此时当即将数据同步到从库,从库将日志写入本身本地的relay log以后,接着会返回一个ack给主库,主库接收到至少一个从库的ack以后才会认为写操做完成了
通常来讲,若是主从延迟较为严重
(1)、分库,将一个主库拆分为4个主库,每一个主库的写并发就500/s,此时主从延迟能够忽略不计
(2)、打开mysql支持的并行复制,多个库并行复制,若是说某个库的写入并发就是特别高,单库写并发达到了2000/s,并行复制仍是没意义。28法则,不少时候好比说,就是少数的几个订单表,写入了2000/s,其余几十个表10/s。
(3)、若是确实是存在必须先插入,立马要求就查询到,而后立马就要反过来执行一些操做,对这个查询设置直连主库。不推荐这种方法,你这么搞致使读写分离的意义就丧失了