Redis实现分布式锁

Redis实现分布式锁
Redis实现分布式锁

背景

在很多互联网产品应用中,有些场景需要加锁处理,比如:秒杀,全局递增ID,楼层生成等等。大部分的解决方案是基于DB实现的,Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系。其次Redis提供一些命令SETNX,GETSET,可以方便实现分布式锁机制。

Redis命令介绍

使用Redis实现分布式锁,有两个重要函数需要介绍

SETNX命令(SET if Not eXists)

语法:

SETNX key value

功能:

当且仅当key 不存在,将key 的值设为value ,并返回1;若给定的key 已经存在,则SETNX 不做任何动作,并返回0。

GETSET命令

语法:

GETSET key value

功能:

将给定key 的值设为value ,并返回key 的旧值(old value),当key 存在但不是字符串类型时,返回一个错误,当key不存在时,返回nil。

GET命令

语法:

GET key

功能:

返回key 所关联的字符串值,如果key 不存在那么返回特殊值nil 。

DEL命令

语法:

DEL key [KEY …]

功能:

删除给定的一个或多个key ,不存在的key 会被忽略。

兵贵精,不在多。分布式锁,我们就依靠这四个命令。但在具体实现,还有很多细节,需要仔细斟酌,因为在分布式并发多进程中,任何一点出现差错,都会导致死锁,hold住所有进程。

加锁实现

SETNX 可以直接加锁操作,比如说对某个关键词foo加锁,客户端可以尝试

SETNX foo.lock

如果返回1,表示客户端已经获取锁,可以往下操作,操作完成后,通过

DEL foo.lock

命令来释放锁。

如果返回0,说明foo已经被其他客户端上锁,如果锁是非堵塞的,可以选择返回调用。如果是堵塞调用调用,就需要进入以下个重试循环,直至成功获得锁或者重试超时。理想是美好的,现实是残酷的。仅仅使用SETNX加锁带有竞争条件的,在某些特定的情况会造成死锁错误。

处理死锁

在上面的处理方式中,如果获取锁的客户端端执行时间过长,进程被kill掉,或者因为其他异常崩溃,导致无法释放锁,就会造成死锁。所以,需要对加锁要做时效性检测。因此,我们在加锁时,把当前时间戳作为value存入此锁中,通过当前时间戳和Redis中的时间戳进行对比,如果超过一定差值,认为锁已经时效,防止锁无限期的锁下去,但是,在大并发情况,如果同时检测锁失效,并简单粗暴的删除死锁,再通过SETNX上锁,可能会导致竞争条件的产生,即多个客户端同时获取锁。

C1获取锁,并崩溃。C2和C3调用SETNX上锁返回0后,获得foo.lock的时间戳,通过比对时间戳,发现锁超时。

C2 向foo.lock发送DEL命令。

C2 向foo.lock发送SETNX获取锁。

C3 向foo.lock发送DEL命令,此时C3发送DEL时,其实DEL掉的是C2的锁。

C3 向foo.lock发送SETNX获取锁。

此时C2和C3都获取了锁,产生竞争条件,如果在更高并发的情况,可能会有更多客户端获取锁。所以,DEL锁的操作,不能直接使用在锁超时的情况下,幸好我们有GETSET方法,假设我们现在有另外一个客户端C4,看看如何使用GETSET方式,避免这种情况产生。

C1获取锁,并崩溃。C2和C3调用SETNX上锁返回0后,调用GET命令获得foo.lock 的时间戳T1,通过比对时间戳,发现锁超时。

C4 向foo.lock发送GESET命令,

GETSET foo.lock

并得到foo.lock中老的时间戳T2

如果T1=T2,说明C4获得时间戳。

如果T1!=T2,说明C4之前有另外一个客户端C5通过调用GETSET方式获取了时间戳,C4未获得锁。只能sleep下,进入下次循环中。

现在唯一的问题是,C4设置foo.lock的新时间戳,是否会对锁产生影响。其实我们可以看到C4和C5执行的时间差值极小,并且写入foo.lock中的都是有效时间错,所以对锁并没有影响。

为了让这个锁更加强壮,获取锁的客户端,应该在调用关键业务时,再次调用GET方法获取T1,和写入的T0时间戳进行对比,以免锁因其他情况被执行DEL意外解开而不知。以上步骤和情况,很容易从其他参考资料中看到。客户端处理和失败的情况非常复杂,不仅仅是崩溃这么简单,还可能是客户端因为某些操作被阻塞了相当长时间,紧接着DEL 命令被尝试执行(但这时锁却在另外的客户端手上)。也可能因为处理不当,导致死锁。还有可能因为sleep设置不合理,导致Redis在大并发下被压垮。最为常见的问题还有

GET返回nil时应该走那种逻辑?

第一种走超时逻辑

C1客户端获取锁,并且处理完后,DEL掉锁,在DEL锁之前。C2通过SETNX向foo.lock 设置时间戳T0 发现有客户端获取锁,进入GET操作。

C2 向foo.lock发送GET命令,获取返回值T1(nil)。

C2 通过T0>T1+expire对比,进入GETSET流程。

C2 调用GETSET向foo.lock发送T0时间戳,返回foo.lock的原值T2

C2 如果T2=T1相等,获得锁,如果T2!=T1,未获得锁。

第二种情况走循环走setnx逻辑

C1客户端获取锁,并且处理完后,DEL掉锁,在DEL锁之前。C2通过SETNX向foo.lock 设置时间戳T0 发现有客户端获取锁,进入GET操作。

C2 向foo.lock发送GET命令,获取返回值T1(nil)。

C2 循环,进入下一次SETNX逻辑

两种逻辑貌似都是OK,但是从逻辑处理上来说,第一种情况存在问题。当GET返回nil表示,锁是被删除的,而不是超时,应该走SETNX逻辑加锁。走第一种情况的问题是,正常的加锁逻辑应该走SETNX,而现在当锁被解除后,走的是GETST,如果判断条件不当,

就会引起死锁,很悲催,我在做的时候就碰到了,具体怎么碰到的看下面的问题

GETSET返回nil时应该怎么处理?

C1和C2客户端调用GET接口,C1返回T1,此时C3网络情况更好,快速进入获取锁,并执行DEL删除锁,C2返回T2(nil),C1和C2都进入超时处理逻辑。

C1 向foo.lock发送GETSET命令,获取返回值T11(nil)。

C1 比对C1和C11发现两者不同,处理逻辑认为未获取锁。

C2 向foo.lock发送GETSET命令,获取返回值T22(C1写入的时间戳)。

C2 比对C2和C22发现两者不同,处理逻辑认为未获取锁。

此时C1和C2都认为未获取锁,其实C1是已经获取锁了,但是他的处理逻辑没有考虑GETSET返回nil的情况,只是单纯的用GET和GETSET值就行对比,至于为什么会出现这种情况?一种是多客户端时,每个客户端连接Redis的后,发出的命令并不是连续的,

导致从单客户端看到的好像连续的命令,到Redis server后,这两条命令之间可能已经插

入大量的其他客户端发出的命令,比如DEL,SETNX等。第二种情况,多客户端之间时间

不同步,或者不是严格意义的同步。

时间戳的问题

我们看到foo.lock的value值为时间戳,所以要在多客户端情况下,保证锁有效,一定要同步各服务器的时间,如果各服务器间,时间有差异。时间不一致的客户端,在判断锁超时,就会出现偏差,从而产生竞争条件。

锁的超时与否,严格依赖时间戳,时间戳本身也是有精度限制,假如我们的时间精度为秒,从加锁到执行操作再到解锁,一般操作肯定都能在一秒内完成。这样的话,我们上面的CASE,就很容易出现。所以,最好把时间精度提升到毫秒级。这样的话,可以保证毫秒级别的锁是安全的。

分布式锁的问题

1:必要的超时机制:获取锁的客户端一旦崩溃,一定要有过期机制,否则其他客户端都降无法获取锁,造成死锁问题。

2:分布式锁,多客户端的时间戳不能保证严格意义的一致性,所以在某些特定因素下,有可能存在锁串的情况。要适度的机制,可以承受小概率的事件产生。

3:只对关键处理节点加锁,良好的习惯是,把相关的资源准备好,比如连接数据库后,调用加锁机制获取锁,直接进行操作,然后释放,尽量减少持有锁的时间。

4:在持有锁期间要不要CHECK锁,如果需要严格依赖锁的状态,最好在关键步骤中做锁的CHECK检查机制,但是根据我们的测试发现,在大并发时,每一次CHECK锁操作,都要消耗掉几个毫秒,而我们的整个持锁处理逻辑才不到10毫秒,玩客没有选择做锁的检查。5:sleep学问,为了减少对Redis的压力,获取锁尝试时,循环之间一定要做sleep操作。但是sleep时间是多少是门学问。需要根据自己的Redis的QPS,加上持锁处理时间等进行合理计算。

6:至于为什么不使用Redis的muti,expire,watch等机制,可以查一参考资料,找下原因。

锁测试数据

未使用sleep

第一种,锁重试时未做sleep。单次请求,加锁,执行,解锁时间

可以看到加锁和解锁时间都很快,当我们使用

ab -n1000 -c100 'https://www.360docs.net/doc/0615350329.html,/test/test_sequence.php?tbpm=t'

AB 并发100累计1000次请求,对这个方法进行压测时。

我们会发现,获取锁的时间变成,同时持有锁后,执行时间也变成,而delete锁的时间,将近10ms时间,为什么会这样?

1:持有锁后,我们的执行逻辑中包含了再次调用Redis操作,在大并发情况下,Redis执

行明显变慢。

2:锁的删除时间变长,从之前的0.2ms,变成9.8ms,性能下降近50倍。

在这种情况下,我们压测的QPS为49,最终发现QPS和压测总量有关,当我们并发100总共100次请求时,QPS得到110多。当我们使用sleep时

使用Sleep时

单次执行请求时

我们看到,和不使用sleep机制时,性能相当。当时用相同的压测条件进行压缩时

获取锁的时间明显变长,而锁的释放时间明显变短,仅是不采用sleep机制的一半。当然执行时间变成就是因为,我们在执行过程中,重新创建数据库连接,导致时间变长的。同时我们可以对比下Redis的命令执行压力情况

上图中细高部分是为未采用sleep机制的时的压测图,矮胖部分为采用sleep机制的压测图,通上图看到压力减少50%左右,当然,sleep这种方式还有个缺点QPS下降明显,在我们的压测条件下,仅为35,并且有部分请求出现超时情况。不过综合各种情况后,我们还是决定采用sleep机制,主要是为了防止在大并发情况下把Redis压垮,很不行,我们之前碰到过,所以肯定会采用sleep机制。

参考资料

https://www.360docs.net/doc/0615350329.html,/FileSystem/18/2518/590664/9f63555e6079482f831c8ab1dcb8c1 9c.pdf

http://redis.io/commands/setnx

https://www.360docs.net/doc/0615350329.html,/caojianhua/archive/2013/01/28/394847.html

Redis面试专题及答案

redis和memcached什么区别?为什么高并发下有时单线程的redis比多线程的memcached效率要高? 区别: 1.mc可缓存图片和视频。rd支持除k/v更多的数据结构; 2.rd可以使用虚拟内存,rd可持久化和aof灾难恢复,rd通过主从支持数据备份; 3.rd可以做消息队列。 原因:mc多线程模型引入了缓存一致性和锁,加锁带来了性能损耗。 redis主从复制如何实现的?redis的集群模式如何实现?redis的key是如何寻址的? 主从复制实现:主节点将自己内存中的数据做一份快照,将快照发给从节点,从节点将数据恢复到内存中。之后再每次增加新数据的时候,主节点以类似于mysql的二进制日志方式将语句发送给从节点,从节点拿到主节点发送过来的语句进行重放。 分片方式: -客户端分片 -基于代理的分片 ●Twemproxy ●codis -路由查询分片 ●Redis-cluster(本身提供了自动将数据分散到Redis Cluster不同节点的能力,整个数据集合的某个数据子集存储在哪个节点对于用户来说是透明的) redis-cluster分片原理:Cluster中有一个16384长度的槽(虚拟槽),编号分别为0-16383。每个Master节点都会负责一部分的槽,当有某个key被映射到某个Master负责的槽,那么这个Master负责为这个key提供服务,至于哪个Master节点负责哪个槽,可以由用户指定,也可以在初始化的时候自动生成,只有Master才拥有槽的所有权。Master节点维护着一个16384/8字节的位序列,Master节点用bit来标识对于某个槽自己是否拥有。比如对于编号为1的槽,Master只要判断序列的第二位(索引从0开始)是不是为1即可。这种结构很容易添加或者删除节点。比如如果我想新添加个节点D, 我需要从节点A、B、C中得部分槽到D上。 使用redis如何设计分布式锁?说一下实现思路?使用zk可以吗?如何实现?这两种有什么区别? redis: 1.线程A setnx(上锁的对象,超时时的时间戳t1),如果返回true,获得锁。 2.线程B 用get获取t1,与当前时间戳比较,判断是是否超时,没超时false,若超时执行第3步; 3.计算新的超时时间t2,使用getset命令返回t3(该值可能其他线程已经修改过),如果 t1==t3,获得锁,如果t1!=t3说明锁被其他线程获取了。 4.获取锁后,处理完业务逻辑,再去判断锁是否超时,如果没超时删除锁,如果已超时,不用处理(防止删除其他线程的锁)。 zk: 1.客户端对某个方法加锁时,在zk上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点node1; 2.客户端获取该路径下所有已经创建的子节点,如果发现自己创建的node1的序号是最小的,就认为这个客户端获得了锁。 3.如果发现node1不是最小的,则监听比自己创建节点序号小的最大的节点,进入等待。

zookeeper面试专题及答案

ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,它是集群的管理者,监视着集群中各个节点的状态根据节点提交的反馈进行下一步合理操作。最终,将简单易用的接口和性能高效、功能稳定的系统提供给用户。 客户端的读请求可以被集群中的任意一台机器处理,如果读请求在节点上注册了监听器,这个监听器也是由所连接的zookeeper机器来处理。对于写请求,这些请求会同时发给其他zookeeper机器并且达成一致后,请求才会返回成功。因此,随着zookeeper的集群机器增多,读请求的吞吐会提高但是写请求的吞吐会下降。有序性是zookeeper中非常重要的一个特性,所有的更新都是全局有序的,每个更新都有一个唯一的时间戳,这个时间戳称为zxid(Zookeeper Transaction Id)。而读请求只会相对于更新有序,也就是读请求的返回结果中会带有这个zookeeper最新的zxid。 1、文件系统 2、通知机制 Zookeeper提供一个多层级的节点命名空间(节点称为znode)。与文件系统不同的是,这些节点都可以设置关联的数据,而文件系统中只有文件节点可以存放数据而目录节点不行。Zookeeper为了保证高吞吐和低延迟,在内存中维护了这个树状的目录结构,这种特性使得Zookeeper不能用于存放大量的数据,每个节点的存放数据上限为1M。 1、PERSISTENT-持久化目录节点 客户端与zookeeper断开连接后,该节点依旧存在 2、PERSISTENT_SEQUENTIAL-持久化顺序编号目录节点 客户端与zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号 3、EPHEMERAL-临时目录节点 客户端与zookeeper断开连接后,该节点被删除 4、EPHEMERAL_SEQUENTIAL-临时顺序编号目录节点

memcached&redis性能测试

一、Memcached 1.1、memcached简介 Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web 应用以减轻数据库负载。它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提供动态、数据库驱动网站的速度。Memcached基于一个存储键/值对的hashmap。其守护进程(daemon )是用C写的,但是客户端可以用任何语言来编写,并通过memcached协议与守护进程通信。但是它并不提供冗余(例如,复制其hashmap条目);当某个服务器S停止运行或崩溃了,所有存放在S上的键/值对都将丢失。 Memcached由Danga Interactive开发,其最新版本发布于2010年,作者为Anatoly Vorobey和Brad Fitzpatrick。用于提升LiveJournal . com访问速度的。LJ每秒动态页面访问量几千次,用户700万。Memcached将数据库负载大幅度降低,更好的分配资源,更快速访问。 1.2、Memcached是如何工作的 Memcached的神奇来自两阶段哈希(two-stage hash)。Memcached就像一个巨大的、存储了很多对的哈希表。通过key,可以存储或查询任意的数据。客户端可以把数据存储在多台memcached上。当查询数据时,客户端首先参考节点列表计算出key的哈希值(阶段一哈希),进而选中一个节点;客户端将请求发送给选中的节点,然后memcached节点通过一个内部的哈希算法(阶段二哈希),查找真正的数据(item)。举个列子,假设有3个客户端1, 2, 3,3台memcached A, B, C:Client 1想把数 据”tuletech”以key “foo”存储。Client 1首先参考节点列表(A, B, C),计算key “foo”的哈希值,假设memcached B被选中。接着,Client 1直接connect 到memcached B,通过key “foo”把数据”tuletech”存储进去。Client 2使用与Client 1相同的客户端库(意味着阶段一的哈希算法相同),也拥有同样的

Redis的5个常见使用场景

Redis的5个常见使用场景概括 大家平时在使用Redis的时候有没有总结过Redis常用于哪些场景呢。下面科多老师带着大家一起来总结一下,希望能够帮助到各位同学。 1、会话缓存(Session Cache) 最常用的一种使用Redis的情景是会话缓存(session cache)。用Redis 缓存会话比其他存储(如Memcached)的优势在于:Redis提供持久化。当维护一个不是严格要求一致性的缓存时,如果用户的购物车信息全部丢失,大部分人都会不高兴的,现在,他们还会这样吗? 幸运的是,随着 Redis 这些年的改进,很容易找到怎么恰当的使用Redis 来缓存会话的文档。甚至广为人知的商业平台Magento也提供Redis的插件。 2、全页缓存(FPC) 除基本的会话token之外,Redis还提供很简便的FPC平台。回到一致性 问题,即使重启了Redis实例,因为有磁盘的持久化,用户也不会看到页面加载速度的下降,这是一个极大改进,类似PHP本地FPC。 再次以Magento为例,Magento提供一个插件来使用Redis作为全页缓存后端。 此外,对WordPress的用户来说,Pantheon有一个非常好的插件wp-redis,这个插件能帮助你以最快速度加载你曾浏览过的页面。 3、队列 Reids在内存存储引擎领域的一大优点是提供 list 和 set 操作,这使得Redis 能作为一个很好的消息队列平台来使用。Redis作为队列使用的操作,就类似于本地程序语言(如Python)对 list 的 push/pop 操作。 如果你快速的在Google中搜索“Redis queues”,你马上就能找到大量的开源项目,这些项目的目的就是利用Redis创建非常好的后端工具,以满足各种队列需求。例如,Celery有一个后台就是使用Redis作为broker,你可以从这 里去查看。 4、排行榜/计数器 Redis在内存中对数字进行递增或递减的操作实现的非常好。集合(Set) 和有序集合(Sorted Set)也使得我们在执行这些操作的时候变的非常简单,Redis只是正好提供了这两种数据结构。所以,我们要从排序集合中获取到排名最靠前的10个用户–我们称之为“user_scores”,我们只需要像下面一样执行即可: 当然,这是假定你是根据你用户的分数做递增的排序。如果你想返回用户及用户的分数,你需要这样执行: ZRANGE user_scores 0 10 WITHSCORES

千锋好程序员大数据技术:zookeeper的应用

Zookeper的应用场景 ZooKeeper是一种为分布式应用所设计的高可用、高性能且一致的开源协调服务,它提供了一项基本服务:分布式服务。由于ZooKeeper的开源特性,后来我们的开发者在分布式锁的基础上,摸索了出了其他的使用方法:配置维护、组服务、分布式消息队列、分布式通知/协调等。 一、统一的命名空间(name services) 在zookeeper的文件系统里创建一个目录,即有唯一的path。服务器可以在zookeper 中创建一个节点,然后将服务器地址(ip等信息写入)等信息写入这个节点,客户端可以通过这个唯一的节点获取服务器的地址。 二、Zookeeper的配置管理 程序总是需要配置的,如果程序分散部署在多台机器上,要逐个改变配置就变得困难。应用在启动的时候会主动来获取一次配置,同时,在节点上注册一个Watcher,这样一来,以后每次配置有更新的时候,都会实时通知到订阅的客户端,从来达到获取最新配置信息的目的。(数据量很小,但是数据更新可能会比较快的场景,Zookeeper不能用于存放大量的数据,每个节点的存放数据上限为1M)

三、Zookeeper集群管理 1.主备选举 应用举例:hadoop的高可用搭建 Namenode(包括YARN ResourceManager) 的主备选举是通过ActiveStandbyElector 来完成的,ActiveStandbyElector 主要是利用了Zookeeper 的写一致性和临时节点机制,具体的主备选举实现如下: 创建锁节点 如果HealthMonitor 检测到对应的NameNode 的状态正常,那么表示这个NameNode 有资格参加Zookeeper 的主备选举。如果目前还没有进行过主备选举的话,那么相应的ActiveStandbyElector 就会发起一次主备选举,尝试在Zookeeper 上创建一个路径为/hadoop-ha//ActiveStandbyElectorLock 的临时节点(${https://www.360docs.net/doc/0615350329.html,services} 为Hadoop 的配置参数https://www.360docs.net/doc/0615350329.html,services 的值,下同),Zookeeper 的写一致性会保证最终只会有一个ActiveStandbyElector 创建成功,那么创建成功的ActiveStandbyElector 对应的NameNode 就会成为主NameNode,ActiveStandbyElector 会回调ZKFailoverController 的方法进一步将对应的NameNode 切换为Active 状态。而创建失败的ActiveStandbyElector 对应的NameNode 成为备NameNode,ActiveStandbyElector 会回调ZKFailoverController 的方法进一步将对应的NameNode 切换为Standby 状态。2.上下线感知 应用举例:hbase的集群搭建 在Hbase中,也是使用ZooKeeper来实现动态HMaster的选举。在Hbase实现中,会在ZK上存储一些ROOT表的地址和HMaster的地址,HRegionServer也会把自己以临时节点(Ephemeral)的方式注册到Zookeeper中,使得HMaster可以随时感知到各个HRegionServer的存活状态,同时,一旦HMaster出现问题,会重新选举出一个HMaster 来运行,从而避免了HMaster的单点问题

memcache、redis、tair性能对比测试报告材料

memcache、redis、tair性能对比测试报告 第1章限制条件 前一周所做的分布缓存技术预言中有包括ehcache、memcache、redis、tair,还包括了基于MongoDB的分布式技术。测试中,考虑到各自功能的差异化特点,其中选择了memcache、redis、tair功能特性相近的缓存服务器进行性能对比,所以ehcache、MongoDB将不做为本次测试的规范,其原因如下: 1)Ehcache是组件级别的缓存,要搭建一个独立的缓存服务器,需要用到ehcache server 模块,这是个war包,能运行在web 容器中,决定整个缓存服务器性能的好坏因素太多,比如web服务器,集群方式等。跟memcache、redis、tair没有对比性。 2)MongoDB是面向文档的数据库,跟缓存没有可比性。 第2章测试场景概述 性能测试包括单机环境和分布式环境,主要针对memcache、redis、tair各缓存服务器在缓存了不同级别的数据下,多个线程并发操作向缓存set/get缓存数据,考虑到网络方面的负载,又将每次set/get操作的缓存数据的大小分为三个不同的级别:1KB,10KB,100KB,通过对上述的条件进行排列,取得以下的测试场景。 第3章单机环境测试 3.1.测试场景: 1.当各缓存的数据库空时,以单线程通过各缓存客户端set调用向服务端推送数据,比较 10000操作所消耗的时间,以上动作通过使用不同大小的单个缓存对象重复三次。2.在场景一完成的情况下,以单线程通过各缓存客户端get调用向服务端获取数据,比较 10000操作所消耗的时间,以上动作通过使用不同大小的单个缓存对象重复三次。3.并发200个线程通过缓存软件的客户set调用向服务端推送数据,每个线程完成10000 次的操作,比较服务器的tps大小,以上动作通过使用不同大小的单个缓存对象重复三

Redis面试题及答案

Redis 是一个基于内存的高性能key-value数据库。(有空再补充,有理解错误或不足欢迎指正) Reids的特点 Redis本质上是一个Key-Value类型的内存数据库,很像memcached,整个数据库统统加载在内存当中进行操作,定期通过异步操作把数据库数据flush到硬盘上进行保存。 因为是纯内存操作,Redis的性能非常出色,每秒可以处理超过10万次读写操作,是已知性能最快的Key-Value DB。 Redis的出色之处不仅仅是性能,Redis最大的魅力是支持保存多种数据结构,此外单个value的最大限制是1GB,不像 memcached只能保存1MB的数据,因此Redis可以用来实现很多有用的功能,比方说用他的List来做FIFO双向链表,实现一个轻量级的高性能消息队列服务,用他的Set可以做高性能的tag系统等等。 另外Redis也可以对存入的Key-Value设置expire时间,因此也可以被当作一个功能加强版的memcached来用。 Redis的主要缺点是数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。 Redis支持的数据类型 Redis通过Key-Value的单值不同类型来区分, 以下是支持的类型: Strings Lists Sets 求交集、并集 Sorted Set hashes

为什么redis需要把所有数据放到内存中? Redis为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘。所以redis具有快速和数据持久化的特征。 如果不将数据放在内存中,磁盘I/O速度为严重影响redis的性能。在内存越来越便宜的今天,redis将会越来越受欢迎。 如果设置了最大使用的内存,则数据已有记录数达到内存限值后不能继续插入新值。 Redis是单进程单线程的 redis利用队列技术将并发访问变为串行访问,消除了传统数据库串行控制的开销 虚拟内存 当你的key很小而value很大时,使用VM的效果会比较好.因为这样节约的内存比较大. 当你的key不小时,可以考虑使用一些非常方法将很大的key变成很大的value,比如你可以考虑将key,value组合成一个新的value. vm-max-threads这个参数,可以设置访问swap文件的线程数,设置最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都是串行的. 可能会造成比较长时间的延迟,但是对数据完整性有很好的保证. 自己测试的时候发现用虚拟内存性能也不错。如果数据量很大,可以考虑分布式或者其他数据库 分布式 redis支持主从的模式。原则:Master会将数据同步到slave,而slave不会将数据同步到master。Slave启动时会连接master来同步数据。 这是一个典型的分布式读写分离模型。我们可以利用master来插入数据,slave提供检索服务。这样可以有效减少单个机器的并发访问数量。

ZOOKEEPER解惑

ZOOKEEPER 解惑 今年年初的时候,写了一篇ZooKeeper 的入门文章《初识ZooKeeper 》,一直到这一周,才有时间将 ZooKeeper 整个源码通读了一遍。不能说完全理解了ZooKeeper 的工作原理与细节,但是之前心中一直关于ZooKeeper 的疑问都得到了解释。 现在网上关于ZooKeeper 的文章很多,有介绍Leader 选举算法的,有介绍ZooKeeper Server 内部原理的,还有介绍ZooKeeper Client 的。本文不打算再写类似的内容,而专注与解答读者对ZooKeeper 的相关疑问。 ZOOKEEPER 在客户端究竟做了什么事情 使用过ZooKeeper 的读者都知道,初始化客户端的代码如下: 1 2 3 System.out.println("Starting ZK:"); zk = new ZooKeeper(address, 3000, this ); System.out.println("Finished starting ZK: " + zk); 完成客户段的初始化之后,就可以对ZooKeeper 进行相应的操作了: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 if (zk != null ) { try { Stat s = zk.exists(root, false ); if (s == null ) { zk.create(root, new byte [0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } } catch (KeeperException e) { System.out .println("Keeper exception when instantiating queue: " + e.toString()); } catch (InterruptedException e) { System.out.println("Interrupted

阿里P7笔试题

1.junit 用法,before,beforeClass,after, afterClass 的执行顺序 2.分布式锁 3.nginx 的请求转发算法,如何配置根据权重转发 4.用hashmap 实现redis 有什么问题(死锁,死循环,可用 ConcurrentH ashmap) 5.线程的状态 6.线程的阻塞的方式 7.sleep 和wait 的区别 8.hashmap 的底层实现 9.一万个人抢100 个红包,如何实现(不用队列),如何保证2 个人不 能抢 到同一个红包,可用分布式锁 10.java 内存模型,垃圾回收机制,不可达算法 11.两个Integer 的引用对象传给一个swap 方法在方法内部交换引用,返 回 后,两个引用的值是否会发现变化 12.aop 的底层实现,动态代理是如何动态,假如有100 个对象,如何动 态 的为这100 个对象代理 13.是否用过maven install。maven test。git(make install 是安装本 地jar 包) 14.tomcat 的各种配置,如何配置docBase 15.spring 的bean 配置的几种方式 16.web.xml 的配置 17.spring 的监听器。 18.zookeeper 的实现机制,有缓存,如何存储注册服务的 19.IO 会阻塞吗?readLine 是不是阻塞的 20.用过spring 的线程池还是java 的线程池? 21.字符串的格式化方法(20,21 这两个问题问的太低级了) 22.时间的格式化方法 23.定时器用什么做的 24.线程如何退出结束 25.java 有哪些锁?乐观锁悲观锁synchronized 可重入锁读写锁,用过r eentrantlock 吗?reentrantlock 与synmchronized 的区别 26.ThreadLocal 的使用场景 27.java 的内存模型,垃圾回收机制 28.为什么线程执行要调用start 而不是直接run(直接run,跟普通方法 没 什么区别,先调start,run 才会作为一个线程方法运行) 29.qmq 消息的实现机制(qmq 是去哪儿网自己封装的消息队列) 30.遍历hashmap 的三种方式 31.jvm 的一些命令 32.memcache 和redis 的区别

大数据技术文档

第1章绪论 随着计算机技术、通信网、互联网的迅速发展和日益普及,Internet上的信息量快速增长。从海量的信息块中快速检索出用户真正需要的信息正变得很困难,信息搜索应向着具有分布式处理能力方向发展,本系统利用hadoop分布式开源框架良好的扩充能力、较低的运作成本、较高的效率和稳定性来满足需求。 现状: 缺陷和不足: (1)结果主题相关度不高。 (2)搜素速度慢。 引入hadoop+nutch+solr的优点: (1)hadoop平台数据处理高效。hadoop集群处理数据比起单机节省数倍的时间,数据量越大优势越明显,满足信息采集对数据处理的速度和质量要求。 (2)hadoop平台具有高扩展性。可以适当扩展集群数量来满足日益不断增加的数据量,而这并不会毁坏原集群的特性。 (3)安全可靠性高。集群的数据冗余机制使得hadoop能从单点失效中恢复,即Hadoop能自动进行数据的多次备份,以确保数据不丢失,即使当某个服务器发生故障时,它也能重新部署计算任务。 (4) Nutch不仅提供抓取网页的功能,还提供了解析网页、建立链接数据库、对网页进行评分、建立solr索引等丰富的功能。 (5)通过Nutch插件机制实现了系统的可扩展性、灵活性和可维护性,提高了开发效率。能够根据用户需求进行灵活定制抓取和解析,提高了系统使用性。

(6)通过solr集群,采用分布式索引在不同的机器上并行执行,实现检索服务器之间的信息交换。可以通过设定主题进行索引检索。 研究目标和内容 本文的研究目标是全面深入分析研究分布式搜索引擎,进而优化分布式搜索引擎中的索引构建策略,内容包括: (1)深入研究hadoop分布式平台,仔细剖析hadoop中的分布式文件系统HDFS和map/Reduce编程模型。 (2)深入研究Nutch架构、相关技术与体系结构,着重研究分析Nutch插件系统的内部结构和流程;对protocol-httpclient插件进行开发支持表单登录;对 url过滤、信息解析插件进行开发,提高搜索的主题相关度;(实现用mapreduce的google的排序算法,改进系统搜索的关联度)。 系统功能结构 (1)本地资源解析模块 对本地文本pdf,word,excel内容解析和索引,按照主题分类,添加到相应的主题中进行搜素。(2)搜索模块 用户根据不同主题进行内容索引、关键词查询,将跟查询关联度最高的前n个文档返回给用户,并统计出在这些查询结果中出现频率最高的前n个词。用户可根据需求修改配置文件,提高搜索的相关度。 (3)信息爬取模块 ①信息定制采集模块 1、种子URL:用作抓取器爬取的出发点,也叫做根URL。 2、关键字:关键字的选择很重要,描述了抓取任务的所属分类的主题方向。

redis-jedis笔记整理

启动Redis服务器 启动客户端 Redis命令目录key(建) 保存键值对set key value 查询指定键对象get key 删除给定键的对象del key1key2…

设置键过期时间EXPIRE key exptime 剩余时间ttl key 查看搜索有键值keys键 migrate指令(移动将数据移动另外一个数据) 将key原子性地从当前实例传送到目标实例的指定数据库上,一旦传送成功,key保证会出现在目标实例上,而当前实例上的key会被删除。 MOVE key db 将数据库的key移动到指定的数据库db当中。如果当前数据库(源数据库)和给定数据库(目标数据库)有相同的名字的给定key,或者key不存在于当前数据库,那么MOVE没有任何效果。因此,也可以利用这一特性,将MOVE当作锁(locking)原语(primitive)。

Obejct{refcount|encoding|idletime} 通常用在debug或者了解为了节省空间使用特殊的编码情况,当redis用作缓存时候,也可以通过OBJECT命令中的信息,决定key的驱逐策略. object refcount key:返回给定key引用所存储的值的次数. object encoding key:返回给定key所存储的值编码可以有 raw(一般字符串)或int(用字符串表示64位数字是为了节约空间)。 ziplist或linkedlist。ziplist是为节约大小较小的列表空间而作的特殊表示。 intset或者hashtable。intset是只储存数字的小集合的特殊表示。 zipmap或者hashtable。zipmap是小哈希表的特殊表示。 ziplist或者skiplist格式。ziplist用于表示小的有序集合,而skiplist则用于表 示任何大小的有序集合。 object idletime key:返回给定key自存储的空闲时间 persist key 将key从带生存时间转换为持久的不带生存时间.

高并发下的接口幂等性解决方案

我们实际系统中有很多操作,是不管做多少次,都应该产生一样的效果或返回一样的结果。 例如: 1.前端重复提交选中的数据,应该后台只产生对应这个数据的一个反应结果。 2.我们发起一笔付款请求,应该只扣用户账户一次钱,当遇到网络重发或系统 bug重发,也应该只扣一次钱; 3.发送消息,也应该只发一次,同样的短信发给用户,用户会哭的; 4.创建业务订单,一次业务请求只能创建一个,创建多个就会出大问题。 等等很多重要的情况,这些逻辑都需要幂等的特性来支持。 二、幂等性概念 幂等(idempotent、idempotence)是一个数学与计算机学概念,常见于抽象代数中。 在编程中.一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。 这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变。 例如,“getUsername()和setTrue()”函数就是一个幂等函数. 更复杂的操作幂等保证是利用唯一交易号(流水号)实现. 我的理解:幂等就是一个操作,不论执行多少次,产生的效果和返回的结果都是一样的 三、技术方案 1. 查询操作查询一次和查询多次,在数据不变的情况下,查询结果是一 样的。select是天然的幂等操作 2. 删除操作删除操作也是幂等的,删除一次和多次删除都是把数据删除。 (注意可能返回结果不一样,删除的数据不存在,返回0,删除的数据多条,返回结果多个) 3.唯一索引,防止新增脏数据比如:支付宝的资金账户,支付宝也有用户 账户,每个用户只能有一个资金账户,怎么防止给用户创建资金账户多个,那么给资金账户表中的用户ID加唯一索引,所以一个用户新增成功一个资金账户记录 要点:唯一索引或唯一组合索引来防止新增数据存在脏数据(当表存在唯一索引,并发时新增报错时,再查询一次就可以了,数据应该已经存在了,返回结果即可) 4. token机制,防止页面重复提交 业务要求: 页面的数据只能被点击提交一次。发生原因:由于重复点击或者网络重发,或者nginx重发等情况会导致数据被重复提交 解决办法:集群环境:采用token加redis(redis单线程的,处理需要排队)单JVM环境:采用token加redis或token加jvm内存 处理流程:

redis系列三-springboot如何使用redis做缓存及缓存注解的用法总结

redis系列三-springboot如何使用redis做缓存及缓存注解的 用法总结 1. 概述 本文介绍spring boot 如何使用Redis做缓存,如何对redis 缓存进行定制化配置(如key的有效期)以及spring boot 如何初始化redis做缓存。使用具体的代码介绍了@Cacheable,@CacheEvict,@CachePut,@CacheConfig等注解及其属性的用法。 2. spring boot集成redis 2.1. application.properties 配置application.properties,包含如下信息: 指定缓存的类型 配置redis的服务器信息 请不要配置spring.cache.cache-names值,原因后面再说 ## 缓存 # spring.cache.cache-names=book1,book2 spring.cache.type=REDIS # REDIS (RedisProperties)

spring.redis.database=0 spring.redis.host=192.168.188.7 spring.redis.password= spring.redis.port=6379 spring.redis.pool.max-idle=8 spring.redis.pool.min-idle=0 spring.redis.pool.max-active=100 spring.redis.pool.max-wait=-1123456789101112131234567891 0111213 2.2. 配置启动类 @EnableCaching: 启动缓存 重新配置RedisCacheManager,使用新的配置的值 @SpringBootApplication @EnableCaching // 启动缓存 public class CacheApplication { private static final Logger log = LoggerFactory.getLogger(CacheApplication.class); public static void main(String[] args) { https://www.360docs.net/doc/0615350329.html,("Start CacheApplication.. ");

【IT专家】Redis缓存Mysql模拟用户登录Java实现实例

本文由我司收集整编,推荐下载,如有疑问,请与我司联系Redis缓存Mysql模拟用户登录Java实现实例2016/03/18 1 这段时间在研究Redis,作为缓存界的新宠,现在使用它的公司越来越多。本文使用的是最新稳定版Redis3.0.实现的具体逻辑是: 1. 用户登录首先判断是否在redis缓存中,如果在redis缓存中,直接登录成功; 2. 若用户未在redis缓存,则访问Mysql,判断用户是否存在,如果不存在,则提示用户注册;如果存在,则登录成功; 3. 在mysql存在并登录成功的同时,将改条数据用Redis Hash类型进行缓存,并设置过期时间为30分钟; 4. 设置redis最大内存,若超出内存范围,则使用FIFO方式进行清除。 ?本文实现方式为最简单的模拟方式,有的代码有进一步封装得必要。 ?一、首先创建两个类,一个类连接Mysql,一个类连接Redis,并复写相关方法:?public class Mysql {public Connection conn;{try {Class.forName( com.mysql.jdbc.Driver conn=DriverManager.getConnection( jdbc:mysql://localhost/spring , root , root } catch (ClassNotFoundException e) {e.printStackTrace();} catch (SQLException e) {e.printStackTrace();}}} ?public class Redis extends Jedis {public Jedis redis;{redis = new Jedis( 192.168.217.128 , 6379);redis.auth( root }// public static void main(String[] args) {// System.out.println(redis.get( name ));// System.out.println(redis.keys( * ));// // redis.sinter(keys);// }public String get(String key) {return redis.get( name }public String set(String key, String value) {return redis.set(key, value);}public Long del(String... keys) {return redis.del(keys);}// 键值增加字符public Long append(String key, String str) {return redis.append(key, str);}public Boolean exists(String key) {return redis.exists(key);}// Need researchpublic Long setnx(String key, String value) {return redis.setnx(key, value);}public String setex(String key, String value, int seconds) {return redis.setex(key, seconds, value);}public Long setrange(String key, String str, int offset)

Ehcache-Redis-Tair缓存性能对比

Ehcache/Redis/Tair缓存性能对比 后面介绍的不同方式都有测试数据,这些测试数据都是在同一的测试环境下得出的测试结果: 测试机器的配置如下: 64位5核CPU, E5620 @ 2.40GHz,内存8G CDN端缓存 由于计数器的价值并不在,具体的值是多少,尤其是对一些大访问量的商品来说个位或者十位的数据并没有什么意义,所以对这些热门商品的计数器访问可以采用定时更新的办法,可以将计数器的值直接缓存在CDN上或者后端Nginx的缓存中,定时再到数据库服务器上获取最新的计数器的值,这样能够大量减少对后端服务器的访问请求,而且计数器的数据量很小对缓存服务器的空间需求也不大。 改进的结构图如下: 直接在Nginx中利用Cache策略缓存住热门计数器的值,利用http协议的cache+max age来失效缓存的方式更新计数器的值。

优点: 实现方式简单,改动小,能够挡住热门商品的计数器访问请求,采用这种方式对查询请求来说,能达到类似于静态服务器的性能,如Nginx能达到2w的QPS 缺点:没有解决同一商品的计数器合并请求的问题,数据量会增大一倍对更新请求没有办法缓存,只能减少查询请求的压力 基于Java的存储方式 由于目前采用Nginx模块的方法开发,每次修改要重新编译Nginx服务器,所以想采用基于Java的方式,使得维护要容易一些。 选用Ehcache作为数据存储服务器,Ehcache也是基于内存存储,支持定时持久化功能,非常适合存储像计数器这种小数据类型。处理Http请求使用Tomcat容器,结构图如下: 处理逻辑采用一个servlet实现,并且在这个servlet中通过一致性Hash从Ehcache中获取计数器值。 在实际的部署结构中,可以将Tomcat和Ehcache部署在同一台机器上。 基于这种模式的测试结果如下:

基于zookeeper高可靠性分布式理论概述

基于zookeeper高可靠性分布式理论概述 ?Zookeeper 作为 Hadoop 项目中的一个子项目,是 Hadoop 集群管理的一个必不可少的模块,它主要用来控制集群中的数据,如它管理 Hadoop 集群中的NameNode,还有 Hbase 中 Master Election、Server 之间状态同步等。 ?Zoopkeeper 提供了一套很好的分布式集群管理的机制,就是它这种基于层次型的目录树的数据结构,并对树中的节点进行有效管理,从而可以设计出多种多样的分布式的数据管理模型 ?大部分分布式应用需要一个主控、协调器或控制器来管理物理分布的子进程(如资源、任务分配等) ?目前,大部分应用需要开发私有的协调程序,缺乏一个通用的机制 ?协调程序的反复编写浪费,且难以形成通用、伸缩性好的协调器 ?ZooKeeper:提供通用的分布式锁服务,用以协调分布式应用 ?Hadoop2.0,使用Zookeeper的事件处理确保整个集群只有一个活跃的NameNode,存储配置信息等. ?HBase,使用Zookeeper的事件处理确保整个集群只有一个HMaster,察觉HRegionServer联机和宕机,存储访问控制列表等. ?特性 ? ?Zookeeper是简单的 ?Zookeeper是富有表现力的 ?Zookeeper具有高可用性 ?Zookeeper采用松耦合交互方式 ?Zookeeper是一个资源库 ? ?Zookeeper是一个由多个server组成的集群 ?一个leader,多个follower

?每个server保存一份数据副本 ?全局数据一致 ?分布式读写 ?更新请求转发,由leader实施 ?角色 ?领导者(leader),负责进行投票的发起和决议,更新系统状态 ?学习者(learner),包括跟随者(follower)和观察者(observer),follower 用于接受客户端请求并想客户端返回结果,在选主过程中参与投票?Observer可以接受客户端连接,将写请求转发给leader,但observer不参加投票过程,只同步leader的状态,observer的目的是为了扩展系统,提高读取速 度 ?客户端(client),请求发起方 工作原理 ?Zookeeper的核心是原子广播,这个机制保证了各个server之间的同步。实现这个机制的协议叫做Zab协议。Zab协议有两种模式,它们分别是恢复模式和 广播模式。当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导 者被选举出来,且大多数server的完成了和leader的状态同步以后,恢复模式 就结束了。状态同步保证了leader和server具有相同的系统状态。 ? 一旦leader已经和多数的follower进行了状态同步后,他就可以开始广播消息了,即 进入广播状态。这时候当一个server加入zookeeper服务中,它会在恢复模式下启动,发现leader,并和leader进行状态同步。待到同步结束,它也参与消息广播。Zookeeper服务一直维持在Broadcast状态,直到leader崩溃了或者leader失去了大 部分的followers支持 ?广播模式需要保证proposal被按顺序处理,因此zk采用了递增的事务id号(zxid)来保证。所有的提议(proposal)都在被提出的时候加上了zxid。实现中 zxid是一个64为的数字,它高32位是epoch用来标识leader关系是否改变, 每次一个leader被选出来,它都会有一个新的epoch。低32位是个递增计数。 ?当leader崩溃或者leader失去大多数的follower,这时候zk进入恢复模式,恢复模式需要重新选举出一个新的leader,让所有的server都恢复到一个正确 的状态。 Leader选举 ?每个Server启动以后都询问其它的Server它要投票给谁。

基于Spark的机器学习资料66、后台服务代码架构:项目实际应用中redis缓存与数据库一致性问题解决

后台服务代码架构:项目实际应用中redis缓存与数据库一致性问题解决 一、需求起因 假设先写数据库,再淘汰缓存:第一步写数据库操作成功,第二步淘汰缓存失败,则会出现DB中是新数据,Cache中是旧数据,数据不一致【如下图:db中是新数据,cache中是旧数据】。 假设先淘汰缓存,再写数据库:第一步淘汰缓存成功,第二步写数据库失败【如下图:cache 中无数据,db中是旧数据】。 结论:先淘汰缓存,再写数据库。 二、数据不一致原因 先操作缓存,在写数据库成功之前,如果有读请求发生,可能导致旧数据入缓存,引发数据不一致。 写流程: (1)先淘汰cache (2)再写db

(1)先读cache,如果数据命中hit则返回 (2)如果数据未命中miss则读db (3)将db中读取出来的数据入缓存 什么情况下可能出现缓存和数据库中数据不一致呢? 在分布式环境下,数据的读写都是并发的,上游有多个应用,通过一个服务的多个部署(为了保证可用性,一定是部署多份的),对同一个数据进行读写,在数据库层面并发的读写并不能保证完成顺序,也就是说后发出的读请求很可能先完成(读出脏数据): (a)发生了写请求A,A的第一步淘汰了cache(如上图中的1) (b)A的第二步写数据库,发出修改请求(如上图中的2) (c)发生了读请求B,B的第一步读取cache,发现cache中是空的(如上图中的步骤3) (d)B的第二步读取数据库,发出读取请求,此时A的第二步写数据还没完成,读出了一个脏数据放入cache(如上图中的步骤4) 即在数据库层面,后发出的请求4比先发出的请求2先完成了,读出了脏数据,脏数据又入了缓存,缓存与数据库中的数据不一致出现了 三、问题解决思路 能否做到先发出的请求一定先执行完成呢?常见的思路是“串行化”

相关文档
最新文档