lru脚本
‘壹’ java web开发缓存方案,ehcache和redis哪个更好
这里就不再逐个讨论了,我将会在一个实际应用程序开发场景中介绍其中的一些。使用Redis作为一个缓存解决方案我之前提到过,Redis可轻易地用作一个缓存解决方案,碰巧我现在正好需要这样一个!在该应用程序示例中,我将Redis集成到我基于定位的移动Web服务中,称之为Magnus。如果您没有关注本系列,那么我会先使用Play框架实现Magnus,从那时起我就已经在各种实现中开发和重构它了。Magnus是一个简单服务,可以通过HTTPPUT请求使用JSON文档。这些文档描述了特定帐号的位置,表示持有移动设备的人。现在,我想要将缓存集成到Magnus,也就是说我想要通过将不常更改的数据存储在内存中以减少I/O流量。Magnus缓存!在清单5中的第一步中,可以通过get调用了解新引入的帐户名称(一个键)是否为REdis中的一个键。get调用可以将帐户ID作为一个值返回,或者将返回null。如果返回一个值,我将用其作为我的acctId变量。如果返回的是null(表明该帐户名称不是Redis中一个键),那么我将在MongoDB查找该帐户值,并通过set命令将其添加到Redis。这里的优势是速度:接下来,被请求的帐户将提交一个位置,这样我就能够从Redis中获取其ID(作为内存缓存),而不是转到MongoDB并带来额外读取I/O成本。清单5.使用Redis作为内存缓存"/location/:account"{put{defjacksonMapper=newObjectMapper()defjson=jacksonMapper.readValue(request.contentText,Map.class)defformatter=newSimpleDateFormat("dd-MM-yyyyHH:mm")defdt=formatter.parse(json['timestamp'])defres=[:]try{defjedis=pool.getResource()defacctId=jedis.get(request.parameters['account'])if(!acctId){defacct=Account.findByName(request.parameters['account'])jedis.set(request.parameters['account'],acct.id.toString())acctId=acct.id}pool.returnResource(jedis)newLocation(acctId.toString(),dt,json['latitude'].doubleValue(),json['longitude'].doubleValue()).save()res['status']='success'}catch(exp){res['status']="error${exp.message}"}response.json=jacksonMapper.writeValueAsString(res)}}注意,清单5中的aMagnus实现(使用Groovy编写)仍然使用一个Nosql实现作为数据模型存储;它仅仅使用Redis作为一个缓存实现用于查询数据。因为我的主要帐户数据位于MongoDB中(事实上,它驻留在MongoHQ.com中),而我的Redis数据存储在本地运行。在随后查找帐户ID时,Magnus速度将显着提升。可是等等!我为什么同时需要MongoDB和Redis?难道我就不能单独使用一个吗?ORM的Node.js很多项目均提供ORM类映射用于Redis,其中包括一个极富影响力的基于Ruby的备用方案,称为Ohm。我检查了该项目基于Java的派生产品(称为JOhm),但是最终决定使用一个为Node编写的派生产品。Ohm及其派生项目的妙处在于他们允许您将一个对象模型映射到一个基于Redis的数据结构。因此,您的模型对象是持久性的,同时在大多数情况下其读取速度也非常之快。有了Nohm,我便能够使用JavaScript快速重写我的Magnus应用程序并能立即持久化Location对象。在清单6中,我已定义了一个Location模型,该模型包括3个属性。(注意,我通过将timestamp设置为一个字符串而不是一个真实的时间戳,从而简化我的示例。)清单6.Node.js中的RedisORMvarLocation=nohm.model('Location',{properties:{latitude:{type:'float',unique:false,validations:[['notEmpty']]},longitude:{type:'float',unique:false,validations:[['notEmpty']]},timestamp:{type:'string',unique:false,validations:[['notEmpty']]}}});Node的Express框架使NohmLocation对象的使用变得十分简单。在我的应用程序PUT实现中,我可以捕获正在进入的JSON值,并通过Nohm的p调用将其导入到一个Location实例。然后我再检查该示例是否有效,如果有效,我会对其进行持久化。清单7.在Node的Express.js中使用Nohmapp.put('/',function(req,res){res.contentType('json');varlocation=newLocation;location.p("timestamp",req.body.timestamp);location.p("latitude",req.body.latitude);location.p("longitude",req.body.longitude);if(location.valid()){location.save(function(err){if(!err){res.send(JSON.stringify({status:"success"}));}else{res.send(JSON.stringify({status:location.errors}));}});}else{res.send(JSON.stringify({status:location.errors}));}});正如清单7所示,可以轻易地将Redis构建成一个极其快速的内存数据存储。在一些案例中,它甚至是一个比memcached更好的缓存!结束语Redis对于许多数据存储场景非常有用,因为它可以将数据持久化到磁盘(还因为它支持一个丰富的数据集),有时候,它是memcached的有力竞争对手。有些情况下,对于您的领域也是很有意义的,您可以使用Redis作为数据模型和队列的一个备份存储。Redis客户端实现几乎可被移植到任何编程语言中。Redis不是RDMBS的完全替代品,也不是一个重量级存储,但是和MongoDB一样拥有丰富的功能。然而,在很多情况下,它可与这些技术共存。
‘贰’ oracle的字典缓存命中率是什么意思
1、 ORACLE 实例――包括内存结构与后台进程 2、 ORACLE 数据库――物理操作系统文件的集合 3、 了解内存结构的组成 4、 了解后台进程的作用
1、 Oracle 实例――包括内存结构与后台进程
2、 Oracle 数据库――物理操作系统文件的集合
3、 了解内存结构的组成
4、 了解后台进程的作用
5、 了解数据库的物理文件
6、 解释各种逻辑结构
一、Oracle实例
1、Oracle 实例
System Global Area(SGA) 和 Background Process 称为数据库的实例。
2、Oracle 数据库
一系列物理文件的集合(数据文件,控制文件,联机日志,参数文件等)
3、系统全局共享区System Global Area(SGA)
System Global Area 是一块巨大的共享内存区域,他被看做是Oracle 数据库的一个大缓冲池,这里的数据可以被Oracle的各个进程共用。其大小可以通过如下语句查看:
SQL> select * from v$sga;
NAME VALUE
-------------------- ---------
Fixed Size 39816
Variable Size 259812784
Database Buffers 1.049E+09
Redo Buffers 327680
更详细的信息可以参考V$sgastat、V$buffer_pool
主要包括以下几个部分:
a、 共享池(Shared pool)
共享池是SGA中最关键的内存片段,特别是在性能和可伸缩性上。一个太小的共享池会扼杀性能,使系统停止,太大的共享池也会有同样的效果,将会消耗大量的CPU来管理这个共享池。不正确的使用共享池只会带来灾难。共享池主要又可以分为以下两个部分:
SQL语句缓冲(Library Cache)
当一个用户提交一个SQL语句,Oracle会将这句SQL进行分析(parse),这个过程类似于编译,会耗费相对较多的时间。在分析完这个SQL,Oracle会把他的分析结果给保存在Shared pool的Library Cache中,当数据库第二次执行该SQL时,Oracle自动跳过这个分析过程,从而减少了系统运行的时间。这也是为什么第一次运行的SQL 比第二次运行的SQL要慢一点的原因。
下面举例说明parse的时间
SQL> select count(*) fromscpass ;
COUNT(*)
----------
243
Elapsed: 00:00:00.08
这是在Share_pool 和Data buffer 都没有数据缓冲区的情况下所用的时间
SQL> alter system flush SHARED_POOL;
System altered.
清空Share_pool,保留Data buffer
SQL> select count(*) from scpass ;
COUNT(*)
----------
243
Elapsed: 00:00:00.02
SQL> select count(*) from scpass ;
COUNT(*)
----------
243
Elapsed: 00:00:00.00
从两句SQL 的时间差上可以看出该SQL 的Parse 时间约为00:00:00.02
对于保存在共享池中的SQL语句,可以从V$Sqltext、v$Sqlarea中查询到,对于编程者来说,要尽量提高语句的重用率,减少语句的分析时间。一个设计的差的应用程序可以毁掉整个数据库的Share pool,提高SQL语句的重用率必须先养成良好的变成习惯,尽量使用Bind变量。
数据字典缓冲区(Data Dictionary Cache)
显而易见,数据字典缓冲区是Oracle特地为数据字典准备的一块缓冲池,供Oracle内部使用,没有什么可以说的。
b、块缓冲区高速缓存(Database Buffer Cache)
这些缓冲是对应所有数据文件中的一些被使用到的数据块。让他们能够在内存中进行操作。在这个级别里没有系统文件,,户数据文件,临时数据文件,回滚段文件之分。也就是任何文件的数据块都有可能被缓冲。数据库的任何修改都在该缓冲里完成,并由DBWR进程将修改后的数据写入磁盘。
这个缓冲区的块基本上在两个不同的列表中管理。一个是块的“脏”表(Dirty List),需要用数据库块的
书写器(DBWR)来写入,另外一个是不脏的块的列表(Free List),一般的情况下,是使用最近最少使用 (Least Recently Used,LRU)算法来管理。块缓冲区高速缓存又可以细分为以下三个部分(Default pool,Keep pool,Recycle pool)。如果不是人为设置初始化参数(Init.ora),Oracle将默认为Default pool。由于操作系统寻址能力的限制,不通过特殊设置,在32位的系统上,块缓冲区高速缓存最大可以达到1.7G,在64位系统上,块缓冲区高速缓存最大可以达到10G。
c、重做日志缓冲区(Redo log buffer)
重做日志文件的缓冲区,对数据库的任何修改都按顺序被记录在该缓冲,然后由LGWR进程将它写入磁盘。这些修改信息可能是DML语句,如(Insert,Update,Delete),或DDL语句,如(Create,Alter,Drop等)。 重做日志缓冲区的存在是因为内存到内存的操作比较内存到硬盘的速度快很多,所以重作日志缓冲区可以加快数据库的操作速度,但是考虑的数据库的一致性与可恢复性,数据在重做日志缓冲区中的滞留时间不会很长。所以重作日志缓冲区一般都很小,大于3M之后的重作日志缓冲区已经没有太大的实际意义。
d、Java程序缓冲区(Java Pool)
Java 的程序区,Oracle 8I 以后,Oracle 在内核中加入了对Java的支持。该程序缓冲区就是为Java 程序保留的。如果不用Java程序没有必要改变该缓冲区的默认大小。
e、大池(Large Pool)
大池的得名不是因为大,而是因为它用来分配大块的内存,处理比共享池更大的内存,在8.0开始引入。
下面对象使用大池:
MTS――在SGA的Large Pool中分配UGA
语句的并行查询(Parallel Executeion of Statements)――允许进程间消息缓冲区的分配,用来协调 并行查询服务器
备份(Backup)――用于RMAN磁盘I/O缓存
4、后台进程(Background process)
后台进程是Oracle的程序,用来管理数据库的读写,恢复和监视等工作。Server Process主要是通过他和user process进行联系和沟通,并由他和user process进行数据的交换。在Unix机器上,Oracle后台进程相对于操作系统进程,也就是说,一个Oracle后台进程将启动一个操作系统进程;在Windows机器上, Oracle后台进程相对于操作系统线程,打开任务管理器,我们只能看到一个Oracle.EXE的进程,但是通过另外的工具,就可以看到包含在这里进程中的线程。
在Unix上可以通过如下方法查看后台进程:
ps ?ef | grep ora_
# ps -ef | grep ora_ | grep XCLUAT
Oracle 29431 1 0 Sep 02 2:02 ora_dbwr_SID
Oracle 29444 1 0 Sep 02 0:03 ora_ckpt_SID
Oracle 29448 1 0 Sep 02 2:42 ora_smon_SID
Oracle 29442 1 0 Sep 02 3:25 ora_lgwr_SID
Oracle 29427 1 0 Sep 02 0:01 ora_pmon_SID
a、Oracle系统有5 个基本进程他们是
DBWR(数据文件写入进程)
LGWR(日志文件写入进程)
SMON(系统监护进程)
PMON(用户进程监护进程)
CKPT(检查点进程,同步数据文件, 日志文件,控制文件)
b、DBWR
将修改过的数据缓冲区的数据写入对应数据文件
维护系统内的空缓冲区
这里指出几个容易错误的概念:
当一个更新提交后,DBWR把数据写到磁盘并返回给用户提交完成.
DBWR会触发CKPT 后台进程
DBWR不会触发LGWR 进程
上面的概念都是错误的.
DBWR是一个很底层的工作进程,他批量的把缓冲区的数据写入磁盘。和任何前台用户的进程几乎没有什么关系,也不受他们的控制。至于DBWR会不会触发LGWR和CKPT进程,我们将在下面几节里讨论。
DBWR工作的主要条件如下
DBWR 超时
系统中没有多的空缓冲区用来存放数据
CKPT 进程触发DBWR 等
c、LGWR
将重做日志缓冲区的数据写入重做日志文件,LGWR是一个必须和前台用户进程通信的进程。当数据被修改的时候,系统会产生一个重做日志并记录在重做日志缓冲区内。这个重做日志可以类似的认为是以下的一个结构:
SCN=000000001000
数据块ID
对象ID=0801
数据行=02
修改后的数据=0011
提交的时候,LGWR必须将被修改的数据的重做日志缓冲区内数据写入日志数据文件,然后再通知前台进程提交成功,并由前台进程通知用户。从这点可以看出LGWR承担了维护系统数据完整性的任务。
LGWR 工作的主要条件如下
用户提交
有1/3 重做日志缓冲区未被写入磁盘
有大于1M 重做日志缓冲区未被写入磁盘
超时
DBWR需要写入的数据的SCN号大于LGWR 记录的SCN号,DBWR 触发LGWR写入
d、SMON
工作主要包含
清除临时空间
在系统启动时,完成系统实例恢复
聚结空闲空间
从不可用的文件中恢复事务的活动
OPS中失败节点的实例恢复
清除OBJ$表
缩减回滚段
使回滚段脱机
e、PMON
主要用于清除失效的用户进程,释放用户进程所用的资源。如PMON将回滚未提交的工作,释放锁,释放分配给失败进程的SGA资源。
f、CKPT
同步数据文件,日志文件和控制文件,由于DBWR/LGWR的工作原理,造成了数据文件,日志文件,控制文件的不一至,这就需要CKPT进程来同步。CKPT会更新数据文件/控制文件的头信息。
CKPT工作的主要条件如下
在日志切换的时候
数据库用immediate ,transaction , normal 选项shutdown 数据库的时候
根据初始话文件LOG_CHECKPOINT_INTERVAL、LOG_CHECKPOINT_TIMEOUT、FAST_START_IO_TARGET 的设置的数值来确定
用户触发
以下进程的启动需要手工配置
g、ARCH
当数据库以归档方式运行的时候,Oracle会启动ARCH进程,当重做日志文件被写满时,日志文件进行切换,旧的重做日志文件就被ARCH进程复制到一个/多个特定的目录/远程机器。这些被复制的重做日志文件被叫做归档日志文件。
h、RECO
负责解决分布事物中的故障。Oracle可以连接远程的多个数据库,当由于网络问题,有些事物处于悬而未决的状态。RECO进程试图建立与远程服务器的通信,当故障消除后,RECO进程自动解决所有悬而未决的会话。
i、服务进程Server Process
服务进程的分类
专用服务进程(Dedicated Server Process)
一个服务进程对应一个用户进程
共享服务进程(MultiTreaded Server Process)
一个服务进程对应多个用户进程,轮流为用户进程服务。
PGA & UGA
PGA = Process Global Area
UGA = User Global Area
他保存了用户的变量、权限、堆栈、排序空间等用户信息,对于专用服务器进程,UGA在PGA中分配。对于多线程进程,UGA在Large pool中分配。
j、用户进程User Process
在客户端,将用户的SQL 语句传递给服务进程
5、一个贯穿数据库全局的概念----系统改变号SCN(System Change Number)
系统改变号,一个由系统内部维护的序列号。当系统需要更新的时候自动增加,他是系统中维持数据的一致性和顺序恢复的重要标志。
a. 查询语句不会使SCN增加,就算是同时发生的更新,数据库内部对应的SCN也是不同的。这样一来就保证了数据恢复时候的顺序。
b. 维持数据的一致性,当一
二、Oracle 数据库
Oracle数据库的组成――物理操作系统文件的集合。主要包括以下几种。
1、控制文件(参数文件init.ora记录了控制文件的位置)
控制文件包括如下主要信息
数据库的名字,检查点信息,数据库创建的时间戳
所有的数据文件,联机日志文件,归档日志文件信息
备份信息等
有了这些信息,Oracle就知道那些文件是数据文件,现在的重做日志文件是哪些,这些都是系统启动和运行的基本条件,所以他是Oracle运行的根本。如果没有控制文件系统是不可能启动的。控制文件是非常重要的,一般采用多个镜相复制来保护控制文件,或采用RAID来保护控制文件。控制文件的丢失,将使数据库的恢复变的很复杂。
控制文件信息可以从V$Controlfile中查询获得
2、数据文件(数据文件的详细信息记载在控制文件中)
可以通过如下方式查看数据文件
SQL> select name from v$datafile;
NAME
---------------------------------------------
/u05/dbf/PROD/system_01.dbf
/u06/dbf/PROD/temp_01.dbf
/u04/dbf/PROD/users_01.dbf
/u09/dbf/PROD/rbs_01.dbf
/u06/dbf/PROD/applsys_indx_01.dbf
/u05/dbf/PROD/applsys_data_01.dbf
从以上可以看出,数据文件大致可以分为以下几类:
i. 系统数据文件(system_01.dbf)
存放系统表和数据字典,一般不放用户的数据,但是用户脚本,如过程,函数,包等却是保存在数据字典中的。
名词解释:数据字典 数据字典是一些系统表或视图,他存放系统的信息,他包括数据库版本,数据文件信息,表与索引等段信息,系统的运行状态等各种和系统有关的信息和用户脚本信息。数据库管理员可以通过对数据字典的查询,就可以了解到Oracle的运行状态。
ii. 回滚段文件(rbs_01.dbf)
如果数据库进行对数据的修改,那么就必须使用回滚段,回滚段是用来临时存放修改前的数据(Before Image)。回滚段通常都放在一个单独的表空间上(回滚表空间),避免表空间碎片化,这个表空间包含的数据文件就是回滚数据文件。
iii. 临时数据文件(temp_01.dbf)
主要存放用户的排序等临时数据,与回滚段相似,临时段也容易引起表空间碎片化,而且没有办法在一个永久表空间上开辟临时段,所以就必须有一个临时表空间,它所包含的数据文件就是临时数据文件,主要用于不能在内存上进行的排序操作。我们必须为用户指定一个临时表空间。
iv. 用户数据文件(/applsys_data_01.dbf ,applsys_indx_01.dbf)
存放用户数据,这里列举了两类常见的用户型数据,一般数据和索引数据,一般来说,如果条件许可的话,可以考虑放在不同的磁盘上。
3、重做日志文件(联机重做日志)
用户对数据库进行的任何操作都会记录在重做日志文件。在了解重做日志之前必须了解重做日志的两个概念,重做日志组和重做日志组成员(Member),一个数据库中至少要有两个日志组文件,一组写完后再写另一组,即轮流写。每个日志组中至少有一个日志成员,一个日志组中的多个日志成员是镜相关系,有利于日志文件的保护,因为日志文件的损坏,特别是当前联机日志的损坏,对数据库的影响是巨大的。
联机日志组的交换过程叫做切换,需要特别注意的是,日志切换在一个优化效果不好的数据库中会引起临时的“挂起”。挂起大致有两种情况:
在归档情况下,需要归档的日志来不及归档,而联机日志又需要被重新利用
检查点事件还没有完成(日志切换引起检查点),而联机日志需要被重新利用
解决这种问题的常用手段是:
i.增加日志组
ii.增大日志文件成员大小
通过v$log可以查看日志组,v$logfile可以查看具体的成员文件。
4、归档日志文件
Oracle可以运行在两种模式之中,归档模式和不归档模式。如果不用归档模式,当然,你就不会有归档日志,但是,你的系统将不会是一个实用系统,特别是不能用于生产系统,因为你可能会丢失数据。但是在归档模式中,为了保存用户的所有修改,在重做日志文件切换后和被覆盖之间系统将他们另外保存成一组连续的文件系列,该文件系列就是归档日志文件。
有人或许会说,归档日志文件占领我大量的硬盘空间,其实,具体想一想,你是愿意浪费一点磁盘空间来保护你的数据,还是愿意丢失你的数据呢?显而义见,我们需要保证我们的数据的安全性。其实,归档并不是一直占领你的磁盘空间,你可以把她备份到磁带上,或则删除上一次完整备份前的所有日志文件。
5、初始化参数文件
initSID.ora或init.ora文件,因为版本的不一样,其位置也可能会不一样。在8i中,通常位于$Oracle_HOME/admin//Pfile下,初始化文件记载了许多数据库的启动参数,如内存,控制文件,进程数等,在数据库启动的时候加载(Nomount时加载),初始化文件记录了很多重要参数,对数据库的性能影响很大,如果不是很了解,不要轻易乱改写,否则会引起数据库性能下降。
6、其他文件
i . 密码文件
用于Oracle 的具有sysdba权限用户的认证.
ii. 日志文件
报警日志文件(alert.log或alrt.ora)
记录数据库启动,关闭和一些重要的出错信息。数据库管理员应该经常检查这个文件,并对出现的问题作出即使的反应。你可以通过以下SQL 找到他的路径select value from v$PARAMETER where name ="background_mp_dest";
后台或用户跟踪文件
系统进程或用户进程出错前写入的信息,一般不可能读懂,可以通过Oracle的TKPROF工具转化为可以读懂的格式。对于系统进程产生的跟踪文件与报警日志文件的路径一样,用户跟踪文件的路径,你可以通过以下SQL找到他的路径select value from v$PARAMETER where name ="user_mp_dest";
三、Oracle逻辑结构
1、 表空间(tablespace)
表空间是数据库中的基本逻辑结构,一系列数据文件的集合。一个表空间可以包含多个数据文件,但是一个数据文件只能属于一个表空间。
2、 段(Segment)
段是对象在数据库中占用的空间,虽然段和数据库对象是一一对应的,但段是从数据库存储的角度来看的。一个段只能属于一个表空间,当然一个表空间可以有多个段。
表空间和数据文件是物理存储上的一对多的关系,表空间和段是逻辑存储上的一对多的关系,段不直接和数据文件发生关系。一个段可以属于多个数据文件,关于段可以指定扩展到哪个数据文件上面。
段基本可以分为以下四种
数据段(Data Segment)
索引段(Index Segment)
回滚段(Rollback Segment)
临时段(Temporary Segment)
3、区间(Extent)
关于Extent的翻译有多种解释,有的译作扩展,有的译作盘区,我这里通常译为区间。在一个段中可以存在多个区间,区间是为数据一次性预留的一个较大的存储空间,直到那个区间被用满,数据库会继续申请一个新的预留存储空间,即新的区间,一直到段的最大区间数(Max Extent)或没有可用的磁盘空间可以申请。 在Oracle8i以上版本,理论上一个段可以无穷个区间,但是多个区间对Oracle却是有性能影响的,Oracle建议把数据分布在尽量少的区间上,以减少Oracle的管理与磁头的移动。
4、Oracle数据块(Block)
Oracle最基本的存储单位,他是OS数据块的整数倍。Oracle的操作都是以块为基本单位,一个区间可以包含多个块(如果区间大小不是块大小的整数倍,Oracle实际也扩展到块的整数倍)。
5、基本表空间介绍
a. 系统表空间
主要存放数据字典和内部系统表基表
查看数据数据字典的SQL
select * from dict
查看内部系统表的SQL
select * from v$fixed_view_definition
DBA对系统的系统表中的数据字典必须有一个很深刻的了解,他们必须准备一些基础的SQL语句,通过这些SQL可以立即了解系统的状况和数据库的状态,这些基本的SQL包括
系统的剩余空间
系统的SGA
状态系统的等待
用户的权限
当前的用户锁
缓冲区的使用状况等
在成为DBA 的道路上我们不建议你过分的依赖于OEM/Quest 等优秀的数据库管理工具,因为他们不利于你对数据数据字典的理解,SQL语句可以完成几乎全部的数据库管理工作。
大量的读少量的写是该表空间的一个显着的特点。
b. 临时表空间.
临时表空间顾名思义是用来存放临时数据的,例如排序操作的临时空间,他的空间会在下次系统启动的时候全部被释放。
c. 回滚段表空间
i. 回滚段在系统中的作用
当数据库进行更新插入删除等操作的时候,新的数据被更新到原来的数据文件,而旧的数据(Before Image)就被放到回滚段中,如果数据需要回滚,那么可以从回滚段将数据再复制到数据文件中。来完成数据的回滚。在系统恢复的时候, 回滚段可以用来回滚没有被commit 的数据,解决系统的一至性。
回滚段在什么情况下都是大量的写,一般是少量读,因此建议把回滚段单独出来放在一个单独的设备(如单独的磁盘或RAID),以减少磁盘的IO争用。
ii. 回滚段的工作方式
一个回滚表空间可以被划分成多个回滚段.
一个回滚段可以保存多个会话的数据.
回滚段是一个圆形的数据模型
假设回滚段由4 个区间组成,他们的使用顺序就是区间1à区间2à区间3à区间4à区间1。也就是说,区间是可以循环使用的,当区间4到区间1的时候,区间1里面的会话还没有结束, 区间4用完后就不能再用区间1,这时系统必须分配区间5,来继续为其他会话服务服务。
我们分析一个Update 语句的完成
①. 用户提交一个Update 语句
②. Server Process 检查内存缓冲.
如果没有该数据块的缓冲,则从磁盘读入
i. 如果没有内存的有效空间,DBWR被启动将未写入磁盘的脏缓冲写入磁盘
ii. 如果有有效空间,则读入
③. 在缓冲内更新数据
i. 申请一个回滚段入口,将旧数据写如回滚段
ii. 加锁并更新数据
iii. 并在同时将修改记录在Redo log buffer中
‘叁’ redis 怎么缓存用户列表,做到可以分页展示
redis是类似key_value形式的快速缓存服务。类型较丰富,可以保存对象、列表等,支持的操作也很丰富,属于内存数据库,且可以把内存中的数据及时或定时的写入到磁盘。可设置过期自动删除,速度快,易于使用。
‘肆’ python导入模块或包需要注意哪些点
Python是一种面向对象的编程语言,里面包含有丰富强大的库,想要学习Python开发,首先需要学习如何导入模块或包。下面就跟大家一起讨论下Python导入模块的几种方法:
常规导入
最常用的导入方式,大概是这样的:
import sys
只需要使用 import ,然后指定希望导入的模块或包即可。用这种方法导入的好处是可以一次性导入多个包或模块:
import os, sys, time
虽然这节省了空间,但是却违背了Python风格指南。 Python风格指南建议将每个导入语句单独成行 。
有时在导入模块时,你想要重命名这个模块。这个功能很容易实现:
import sys as system
print(system.platform)
上面的代码将我们导入的 sys 模块重命名为 system 。我们可以按照和以前一样的方式调用模块的方法,但是可以用一个新的模块名。也有某些子模块必须要使用点标记法才能导入。
import urllib.error
这个情况不常见,但是对此有所了解总是没有坏处的。
使用from语句导入
有时我们只想要导入一个模块或库中的某个部分。那么Python是如何实现这点:
from functools import lru_cache
上面这行代码可以让你直接调用 lru_cache 。如果按常规方式导入 functools ,那么就必须像这样调用 lru_cache :
functools.lru_cache(*args)
根据实际的使用场景,上面的做法可能是更好的。在复杂的代码库中,能够看出某个函数是从哪里导入的这点很有用的。不过,如果你的代码维护的很好,模块化程度高,那么只从某个模块中导入一部分内容也是非常方便和简洁的。
当然,你还可以使用from方法导入模块的全部内容,就像这样:
from os import *
这种做法在少数情况下是挺方便的,但是这样也会打乱你的命名空间。问题在于,你可能定义了一个与导入模块中名称相同的变量或函数,这时如果你试图使用
os 模块中的同名变量或函数,实际使用的将是你自己定义的内容。因此,你最后可能会碰到一个相当让人困惑的逻辑错误。
标准库中我唯一推荐全盘导入的模块只有Tkinter 。
如果你正好要写自己的模块或包,有人会建议你在 __init__.py 文件中导入所有内容,让模块或者包使用起来更方便。我个人更喜欢显示地导入,而非隐式地导入。
你也可以采取折中方案,从一个包中导入多个项:
from os import path, walk, unlinkfrom os import uname, remove
在上述代码中,我们从 os 模块中导入了5个函数。你可能注意到了,我们是通过多次从同一个模块中导入实现的。当然,如果你愿意的话,你也可以使用圆括号一次性导入多个项:
from os import (path, walk, unlink, uname,
remove, rename)
这是一个有用的技巧,不过你也可以换一种方式:
from os import path, walk, unlink, uname, \
remove, rename
上面的反斜杠是Python中的续行符,告诉解释器这行代码延续至下一行。
相对导入
PEP 328 介绍了引入相对导入的原因,以及选择了哪种语法。具体来说,是使用句点来决定如何相对导入其他包或模块。这么做的原因是为了避免偶然情况下导入标准库中的模块产生冲突。这里我们以PEP 328中给出的文件夹结构为例,看看相对导入是如何工作的:
my_package/
__init__.py
subpackage1/
__init__.py
mole_x.py
mole_y.py
subpackage2/
__init__.py
mole_z.py
mole_a.py
在本地磁盘上找个地方创建上述文件和文件夹。在顶层的 __init__.py 文件中,输入以下代码:
from . import subpackage1from . import subpackage2
接下来进入 subpackage1 文件夹,编辑其中的 __init__.py 文件,输入以下代码:
from . import mole_xfrom . import mole_y
现在编辑 mole_x.py 文件,输入以下代码:
from .mole_y import spam as ham
def main():
ham()
最后编辑 mole_y.py 文件,输入以下代码:
def spam():
print('spam ' * 3)
打开终端, cd 至 my_package 包所在的文件夹,但不要进入 my_package 。在这个文件夹下运行Python解释器。我使用的是IPython,因为它的自动补全功能非常方便:
In [1]: import my_package
In [2]: my_package.subpackage1.mole_xOut[2]: <mole
'my_package.subpackage1.mole_x' from
'my_package/subpackage1/mole_x.py'>
In [3]: my_package.subpackage1.mole_x.main()spam spam spam
相对导入适用于你最终要放入包中的代码。如果你编写了很多相关性强的代码,那么应该采用这种导入方式。
你会发现PyPI上有很多流行的包也是采用了相对导入 。还要注意一点,如果你想要跨越多个文件层级进行导入,只需要使用多个句点即可。不过, PEP
328建议相对导入的层级不要超过两层 。
还要注意一点,如果你往 mole_x.py 文件中添加了 if __name__ == ‘__main__’ ,然后试图运行这个文件,你会碰到一个很难理解的错误。编辑一下文件,试试看吧!
from . mole_y import spam as ham
def main():
ham()
if __name__ == '__main__':
# This won't work!
main()
现在从终端进入 subpackage1 文件夹,执行以下命令:
python mole_x.py
如果你使用的是Python 2,你应该会看到下面的错误信息:
Traceback (most recent call last):
File "mole_x.py", line 1, in <mole>
from . mole_y import spam as hamValueError: Attempted relative import in non-package
如果你使用的是Python 3,错误信息大概是这样的:
Traceback (most recent call last):
File "mole_x.py", line 1, in <mole>
from . mole_y import spam as hamSystemError: Parent mole '' not loaded, cannot perform relative import
这指的是, mole_x.py 是某个包中的一个模块,而你试图以脚本模式执行,但是 这种模式不支持相对导入 。
如果你想在自己的代码中使用这个模块,那么你必须将其添加至Python的导入检索路径(import search path)。最简单的做法如下:
import syssys.path.append('/path/to/folder/containing/my_package')import my_package
注意,你需要添加的是 my_package 的上一层文件夹路径,而不是 my_package 本身。原因是 my_package 就是我们想要使用的包,所以如果你添加它的路径,那么将无法使用这个包。
我们接下来谈谈可选导入。
可选导入(Optional imports)
如果你希望优先使用某个模块或包,但是同时也想在没有这个模块或包的情况下有备选,你就可以使用可选导入这种方式。这样做可以导入支持某个软件的多种版本或者实现性能提升。以 github2包 中的代码为例:
try:
# For Python 3
from http.client import responsesexcept ImportError: # For Python 2.5-2.7
try:
from httplib import responses # NOQA
except ImportError: # For Python 2.4
from BaseHTTPServer import BaseHTTPRequestHandler as _BHRH
responses = dict([(k, v[0]) for k, v in _BHRH.responses.items()])
lxml 包也有使用可选导入方式:
try:
from urlparse import urljoin
from urllib2 import urlopenexcept ImportError:
# Python 3
from urllib.parse import urljoin
from urllib.request import urlopen
正如以上示例所示, 可选导入的使用很常见,是一个值得掌握的技巧 。
局部导入
当你在局部作用域中导入模块时,你执行的就是局部导入。如果你在Python脚本文件的顶部导入一个模块,那么你就是在将该模块导入至全局作用域,这意味着之后的任何函数或方法都可能访问该模块。例如:
import sys # global scope
def square_root(a):
# This import is into the square_root functions local scope
import math
return math.sqrt(a)
def my_pow(base_num, power):
return math.pow(base_num, power)
if __name__ == '__main__':
print(square_root(49))
print(my_pow(2, 3))
这里,我们将 sys 模块导入至全局作用域,但我们并没有使用这个模块。然后,在 square_root 函数中,我们将 math
模块导入至该函数的局部作用域,这意味着 math 模块只能在 square_root 函数内部使用。如果我们试图在 my_pow 函数中使用
math ,会引发 NameError 。试着执行这个脚本,看看会发生什么。
使用局部作用域的好处之一,是你使用的模块可能需要很长时间才能导入,如果是这样的话,将其放在某个不经常调用的函数中或许更加合理,而不是直接在全局作
用域中导入。老实说,我几乎从没有使用过局部导入,主要是因为如果模块内部到处都有导入语句,会很难分辨出这样做的原因和用途。
根据约定,所有的导入语句都应该位于模块的顶部 。
导入注意事项
在导入模块方面,有几个程序员常犯的错误。这里我们介绍两个。
循环导入(circular imports)
覆盖导入(Shadowed imports,暂时翻译为覆盖导入)
先来看看循环导入。
循环导入
如果你创建两个模块,二者相互导入对方,那么就会出现循环导入。例如:
# a.pyimport b
def a_test():
print("in a_test")
b.b_test()
a_test()
然后在同个文件夹中创建另一个模块,将其命名为 b.py 。
import a
def b_test():
print('In test_b"')
a.a_test()
b_test()
如果你运行任意一个模块,都会引发 AttributeError 。这是因为这两个模块都在试图导入对方。简单来说,模块 a 想要导入模块 b
,但是因为模块 b 也在试图导入模块 a (这时正在执行),模块 a 将无法完成模块 b
的导入。我看过一些解决这个问题的破解方法(hack),但是 一般来说,你应该做的是重构代码,避免发生这种情况 。
覆盖导入
当你创建的模块与标准库中的模块同名时,如果你导入这个模块,就会出现覆盖导入。举个例子,创建一个名叫 math.py 的文件,在其中写入如下代码:
import math
def square_root(number):
return math.sqrt(number)
square_root(72)
现在打开终端,试着运行这个文件,你会得到以下回溯信息(traceback):
Traceback (most recent call last):
File "math.py", line 1, in <mole>
import math
File "/Users/michael/Desktop/math.py", line 6, in <mole>
square_root(72)
File "/Users/michael/Desktop/math.py", line 4, in square_root
return math.sqrt(number)AttributeError: mole 'math' has no attribute 'sqrt'
这到底是怎么回事?其实,你运行这个文件的时候,Python解释器首先在当前运行脚本所处的的文件夹中查找名叫 math
的模块。在这个例子中,解释器找到了我们正在执行的模块,试图导入它。但是我们的模块中并没有叫 sqrt 的函数或属性,所以就抛出了
AttributeError 。
‘伍’ android okhttp超时怎么办
OkHttp是一个在开发可汗学院AndroidAPP过程中非常重要的依赖库。它的默认的配置为我们提供了非常重要实用功能,下面一些步骤我们可以让Okhttp提供功能使用灵活和内省能力。1.启用文件系统上的响应缓存默认情况下,Okhttp不支持响应缓存,包括HTTPCache-Control头允许缓存响应。因此,客户端通过一次又一次的请求相同的资源浪费时间和带宽。而不是简单地读取初始响应后缓存的副本。要在文件系统中启用响应缓存,需要配置com.squareup.okhttp.Cache实例,并把它传递给你的OkHttpClient实例的setCache方法。你必须初始化缓存与存放目录的文件,并以字节为单位的最大值。响应返回数据可以写入给定目录文件,如果一个响应的缓存超过了给定的大小。我们可以采取LRUpolicy。我们可以在stackoverflow查看JesseWilson的回复。我们可以通过context.getCacheDir()在子目录中缓存我们的响应://Basedirectoryrecommendedby/q/4441849/400717.final@NullableFilebaseDir=context.getCacheDir();if(baseDir!=null){finalFilecacheDir=newFile(baseDir,"HttpResponseCache");okHttpClient.setCache(newCache(cacheDir,HTTP_RESPONSE_DISK_CACHE_MAX_SIZE));}//Basedirectoryrecommendedby/q/4441849/400717.final@NullableFilebaseDir=context.getCacheDir();if(baseDir!=null){finalFilecacheDir=newFile(baseDir,"HttpResponseCache");okHttpClient.setCache(newCache(cacheDir,HTTP_RESPONSE_DISK_CACHE_MAX_SIZE));}在可汗学院的程序中我们指定HTTP_RESPONSE_DISK_CACHE_MAX_SIZEas10*1024*1024,or10MB的大小2.集成StethoStetho是Facebook的一个可爱的库,可以使用Chrome浏览器的Chrome开发人员工具功能来检查你的Android应用程序。Stetho除了允许你检查你的应用程序的SQLite数据库,还可以查看View的层次结构。允许你检查由OkHttp发起的每个请求和响应:这种自省机制是确保服务器返回允许资源缓存的HTTP头是非常有用的,以及验证没有请求时,保证缓存的资源存在。要想使用Stetho,只需添加一个StethoInterceptor实例的网络拦截器列表:okHttpClient.networkInterceptors().add(newStethoInterceptor());okHttpClient.networkInterceptors().add(newStethoInterceptor());然后,运行应用程序,打开浏览器后,输入chrome://inspect。然后你就会看到应用程序的设备和标识符的列表。然后鼠标右键选择inspect打开开发者工具,然后打开新的tab,开始监控OkHttp请求。3.使用Picasso和Retrofit你可能使用过Picasso来加载网络图片,或者使用Retrofit来简化发出请求和解码响应。这些第三方库将隐式地创建自己的OkHttpClient供内部使用,如果你不明确指定一个。Picassoversion2.5.2的OkHttpDownloader类:(){OkHttpClientclient=newOkHttpClient();client.setConnectTimeout(Utils.DEFAULT_CONNECT_TIMEOUT_MILLIS,TimeUnit.MILLISECONDS);client.setReadTimeout(Utils.DEFAULT_READ_TIMEOUT_MILLIS,TimeUnit.MILLISECONDS);client.setWriteTimeout(Utils.DEFAULT_WRITE_TIMEOUT_MILLIS,TimeUnit.MILLISECONDS);returnclient;}(){OkHttpClientclient=newOkHttpClient();client.setConnectTimeout(Utils.DEFAULT_CONNECT_TIMEOUT_MILLIS,TimeUnit.MILLISECONDS);client.setReadTimeout(Utils.DEFAULT_READ_TIMEOUT_MILLIS,TimeUnit.MILLISECONDS);client.setWriteTimeout(Utils.DEFAULT_WRITE_TIMEOUT_MILLIS,TimeUnit.MILLISECONDS);returnclient;}Retrofit也有类似的工厂方法来创建自己的OkHttpClient。图片一般在应用程序中需要加载的比较大的资源。尽管Picasso自己维护它的LRU机制来缓存图片,在内存中严格执行。如果客户端尝试使用Picasso来加载图片。Picasso会找不到其在内存中缓存图像,然后将委托加载该图片到它的内部OkHttpClient实例。并且默认情况下该实例将始终从服务器加载图片资源。作为defaultOkHttpClient的方法不能与上面提到的文件系统中的响应缓存配置结合起来。指定你自己的OkHttpClient实例允许返回数据从文件系统缓存响应,图片不会从服务器加载。这是非常重要的在程序第一次启动以后。这个时候Picasso的内存缓存是冷的。所以它会频繁的委托OkHttpClient实例去加载图片。这就需要构建配置了您Picasso的OkHttpClient实例,如果你在你的代码中使用Picasso.with(context).load()Picasso.with(context).load()加载图片,你是用的是Picasso的单例模式。这是通过with方法懒汉模式地实例化并配置自己的OkHttpClient。因此,我们必须使我们自己的Picasso实例在单例之前通过wiht方法调用。实现这个,可以简单的将OkHttpClient实例封装在OkHttpDownloader中,然后传递给Picasso.Builder实例的downloader方法。finalPicassopicasso=newPicasso.Builder(context).downloader(newOkHttpDownloader(okHttpClient)).build();//,butreplacethesingleton//instancejustincase.Picasso.setSingletonInstance(picasso);finalPicassopicasso=newPicasso.Builder(context).downloader(newOkHttpDownloader(okHttpClient)).build();//,butreplacethesingleton//instancejustincase.Picasso.setSingletonInstance(picasso);在Retrofit中要使用OkHttpClient实例,需要改造1.9.x的一个RestAdapter,需要将OkHttpClient封装OkClient的实例中。然后把它传递给RestAdapter.Builder实例的setClient方法。restAdapterBuilder.setClient(newOkClient(httpClient));restAdapterBuilder.setClient(newOkClient(httpClient));在Retrofit2.0中只需要简单的将OkHttpClient传递给Retrofit.Builder实例的client方法。在可汗学院的APP中我们通过Dagger依赖注入来确保我们只有一个OkHttpClient的实例。这种方法同样也适用于Picasso和Retrofit我们提供了一个为OkHttpClient实例提供单例模式的注解示例:@Provides@(finalContextcontext,){finalOkHttpClientokHttpClient=newOkHttpClient();configureClient(okHttpClient,);returnokHttpClient;}@Provides@(finalContextcontext,){finalOkHttpClientokHttpClient=newOkHttpClient();configureClient(okHttpClient,);returnokHttpClient;}OkHttpClient将会通过Dagger的注解创建一个实例提供给我们的Picasso和Retrofit。4.指定一个用户代理拦截器日志文件和分析为我们提供了有用的信息,当客户在每个请求提供详细的User-Agentheader值的时候。默认情况下,Okhttp包含User-Agent值只有在特定的Okhttp版本中。为了指定我们自己的useragent。首先创建拦截器的替换值,我们可以看stackoverflow的建议。{privatestaticfinalStringUSER_AGENT_HEADER_NAME="User-Agent";;publicUserAgentInterceptor(StringuserAgentHeaderValue){this.userAgentHeaderValue=Preconditions.checkNotNull(userAgentHeaderValue);}@(Chainchain)throwsIOException{finalRequestoriginalRequest=chain.request();=originalRequest.newBuilder().removeHeader(USER_AGENT_HEADER_NAME).addHeader(USER_AGENT_HEADER_NAME,userAgentHeaderValue).build();returnchain.proceed(requestWithUserAgent);}}{privatestaticfinalStringUSER_AGENT_HEADER_NAME="User-Agent";;publicUserAgentInterceptor(StringuserAgentHeaderValue){this.userAgentHeaderValue=Preconditions.checkNotNull(userAgentHeaderValue);}@(Chainchain)throwsIOException{finalRequestoriginalRequest=chain.request();=originalRequest.newBuilder().removeHeader(USER_AGENT_HEADER_NAME).addHeader(USER_AGENT_HEADER_NAME,userAgentHeaderValue).build();returnchain.proceed(requestWithUserAgent);}}为了创建User-Agentheader值人然后传递给UserAgentInterceptor的构造器,使用你得到的任何信息。我们可以使用:android的系统信息可以清晰的传递出这是一台android设备Build.MODEL或者“制造商提供的用户可见最终可见的名称”Build.BRAND或者“消费者可见的品牌与产品/硬件相关信息”Build.VERSION.SDK_INT或者“消费者可见的Android提供的SDK版本号”BuildConfig.APPLICATION_IDBuildConfig.VERSION_NAMEBuildConfig.VERSION_CODE最后三个值由的applicationID,VERSIONCODE和VERSIONNAME的值在我们的Gradlebuild脚本中了解信息可以查看versioningyourapplications和请注意,如果您的应用程序使用的是WebView,您可以配置使用相同的User-Agentheader值,你可以通过下面方法创建UserAgentInterceptor:WebSettingssettings=webView.getSettings();settings.setUserAgentString(userAgentHeaderValue);WebSettingssettings=webView.getSettings();settings.setUserAgentString(userAgentHeaderValue);5.指定合理的超时2.5.0版本之前,OkHttp请求默认为永不超时。2.5.0版本开始如果建立连接请求超时,如果从连接读取下一个字节或写入的下一个字节到连接,花费超过10秒,就终止。这样做需要更新到2.5.0版本我们就不需要在我们的代码中修改bug。原因很简单是我因为我们第一次使用的时候使用了错误的路径。要覆盖这些默认值,可以分别调用setConnectTimeout,setReadTimeout或setWriteTimeout。需要注意的是Picasso和Retrofit为OkHttpClient实例指定不同的超时值时,默认情况下,Picasso指定:连接超过15秒.读取超过20秒写入超过20秒而Retrofit指定:连接超过15秒.读取超过20秒没有写入超时通过配置Picasso和Retrofit自己的OkHttpClient实例你可以确保所有的请求超时是一致的
‘陆’ redis内存满了怎么办
redis内存满了解决方法:
1,增加内存。
2,使用内存淘汰策略。
3,Redis集群。
重点介绍下2、3:
第二点:
我们知道,redis设置配置文件的maxmemory参数,可以控制其最大可用内存大小(字节)。
那么当所需内存,超过maxmemory怎么办?
这个时候就该配置文件中的maxmemory-policy出场了。
其默认值是noeviction。
下面我将列出当可用内存不足时,删除redis键具有的淘汰规则。
规则说明:
1、volatile-lru
使用LRU算法删除一个键(只对设置了生存时间的键)
2、allkeys-lru
使用LRU算法删除一个键
3、volatile-random
随机删除一个键(只对设置了生存时间的键)
4、allkeys-random
随机删除一个键
5、volatile-ttl
删除生存时间最近的一个键
6、noeviction
不删除键,只返回错误
LRU算法,least RecentlyUsed,最近最少使用算法。也就是说默认删除最近最少使用的键。
但是一定要注意一点!redis中并不会准确的删除所有键中最近最少使用的键,而是随机抽取3个键,删除这三个键中最近最少使用的键。
那么3这个数字也是可以设置的,对应位置是配置文件中的maxmeory-samples.
三、集群怎么做
Redis仅支持单实例,内存一般最多10~20GB。对于内存动辄100~200GB的系统,就需要通过集群来支持了。
Redis集群有三种方式:客户端分片、代理分片、RedisCluster(在之后一篇文章详细说一下。)
1、客户端分片
通过业务代码自己实现路由
优势:可以自己控制分片算法、性能比代理的好
劣势:维护成本高、扩容/缩容等运维操作都需要自己研发
2、代理分片
代理程序接收到来自业务程序的数据请求,根据路由规则,将这些请求分发给正确的Redis实例并返回给业务程序。使用类似Twemproxy、Codis等中间件实现。
优势:运维方便、程序不用关心如何链接Redis实例
劣势:会带来性能消耗(大概20%)、无法平滑扩容/缩容,需要执行脚本迁移数据,不方便(Codis在Twemproxy基础上优化并实现了预分片来达到Auto Rebalance)。
3、Redis Cluster
优势:官方集群解决方案、无中心节点,和客户端直连,性能较好
劣势:方案太重、无法平滑扩容/缩容,需要执行相应的脚本,不方便、太新,没有相应成熟的解决案例
‘柒’ mysql有基于LRU缓冲池,其它辅助缓存如memcached和redis的意义应该就不需要了,还是有其它需要的理由
1、首先明确是不是一定要上缓存,当前架构的瓶颈在哪里,若瓶颈真是数据库操作上,再继续往下看。
2、明确memcached和redis的区别,到底要使用哪个。前者终究是个缓存,不可能永久保存数据(LRU机制),支持分布式,后者除了缓存的同时也支持把数据持久化到磁盘等,redis要自己去实现分布式缓存(貌似最新版本的已集成),自己去实现一致性hash。因为不知道应用场景,不好说一定要用memcache还是redis,说不定用mongodb会更好,比如在存储日志方面。
3、缓存量大但又不常变化的数据,比如评论。
4、思路是对的,清晰明了,读DB前,先读缓存,如果有直接返回,如果没有再读DB,然后写入缓存层并返回。
5、考虑是否需要主从,读写分离,考虑是否分布式部署,考虑是否后续水平伸缩。
6、想要一劳永逸,后续维护和扩展方便,那就将现有的代码架构优化,按你说的替换数据库组件需要改动大量代码,说明当前架构存在问题。可以利用现有的一些框架,比如SpringMVC,将应用层和业务层和数据库层解耦。再上缓存之前把这些做好。
7、把读取缓存等操作做成服务组件,对业务层提供服务,业务层对应用层提供服务。
8、保留原始数据库组件,优化成服务组件,方便后续业务层灵活调用缓存或者是数据库。
9、不建议一次性全量上缓存,最开始不动核心业务,可以将边缘业务先换成缓存组件,一步步换至核心业务。
10、刷新内存,以memcached为例,新增,修改和删除操作,一般采用lazy load的策略,即新增时只写入数据库,并不会马上更新Memcached,而是等到再次读取时才会加载到Memcached中,修改和删除操作也是更新 数据库,然后将Memcached中的数据标记为失效,等待下次读取时再加载。
大方向两种方案:
1、脚本同步:自己写脚本将数据库数据写入到redis/memcached。这就涉及到实时数据变更的问题(mysql row binlog的实时分析),binlog增量订阅Alibaba 的canal ,以及缓存层数据 丢失/失效 后的数据同步恢复问题。
2、业务层实现:先读取nosql缓存层,没有数据再读取mysql层,并写入数据到nosql。nosql层做好多节点分布式(一致性hash),以及节点失效后替代方案(多层hash寻找相邻替代节点),和数据震荡恢复了。
‘捌’ imgcache是什么意思
系统辅助
1.img格式是镜像的一种。可以通过制作数据光盘或者使用虚拟光驱安装IMG数据文件。
2.img格式是制定css样式表的一种样式,对HTM中的图片属性进行修饰。
3.img格式是图片格式的一种,某些旧系统、游戏中照片是用IMG格式存储的,而且是多张照片在一起,Ventura Publisher是专门处理img图像文件的,常见的图片处理软件Acdsee和Photoshop也能查看大部分该格式的图片。
cache n. 高速缓冲存储器 一种特殊的存储器子系统,其中复制了频繁使用的数据以利于快速访问。存储器的高速缓冲存储器存储了频繁访问的 RAM 位置的内容及这些数据项的存储地址。当处理器引用存储器中的某地址时,高速缓冲存储器便检查是否存有该地址。如果存有该地址,则将数据返回处理器;如果没有保存该地址,则进行常规的存储器访问。因为高速缓冲存储器总是比主RAM 存储器速度快,所以当 RAM 的访问速度低于微处理器的速度时,常使用高速缓冲存储器。另见 wait state。
Cache的出现是基于两种因素:首先,是由于CPU的速度和性能提高很快而主存速度较低且价格高,第二就是程序执行的局部性特点。因此,才将速度比较快而容量有限的SRAM构成Cache,目的在于尽可能发挥CPU的高速度。很显然,要尽可能发挥CPU的高速度就必须用硬件实现其全部功能。
Cache与主存之间可采取多种地址映射方式,直接映射方式是其中的一种。在这种映射方式下,主存中的每一页只能复制到某一固定的Cache页中。由于Cache块(页)的大小为16B,而Cache容量为16KB。因此,此Cache可分为1024页。可以看到,Cache的页内地址只需4位即可表示;而Cache的页号需用10位二进制数来表示;在映射时,是将主存地址直接复制,现主存地址为1234E8F8(十六进制),则最低4位为Cache的页内地址,即1000,中间10位为Cache的页号,即1010001111。Cache的容量为16KB决定用这14位编码即可表示。题中所需求的Cache的地址为10100011111000。
Cache中的内容随命中率的降低需要经常替换新的内容。替换算法有多种,例如,先入后出(FILO)算法、随机替换(RAND)算法、先入先出(FIFO)算法、近期最少使用(LRU)算法等。这些替换算法各有优缺点,就以命中率而言,近期最少使用(LRU)算法的命中率最高。
浏览器缓存
缓存用于存储一些临时的文件。在浏览网页的过程中,网页会自动存储在用户的硬盘上。下次再浏览相同的网站的时候,系统会自动从硬盘中调出该网页,既节省了时间也减少了网络的交换。用户可以自行设定缓存方便其上网的需要。电脑中还存在高速缓冲存储器和硬盘缓存。缓存的种类:本地服务器缓存、网页缓存、硬盘缓存、一级高速缓存、二级高速缓存。
简单理解
cache是一个高速小容量的临时存储器,可以用高速的静态存储器芯片实现,或者集成到CPU芯片内部,存储CPU最经常访问的指令或者操作数据。
‘玖’ redis是个单线程的程序,为什么会这么快呢
纯内存数据库,如果只是简单的 key-value,内存不是瓶颈。一般情况下,hash 查找可以达到每秒数百万次的数量级。
瓶颈在于网络 IO 上。
根据你测的的 10000/s 来看,客户端和 redis 应该是部署在两台不同的机器,并且是使用同步的方式请求 redis. 每次请求需要通过网络把请求发送到 redis 所在的机器,然后等待 redis 返回数据。时间大部分消耗在网络传输中。
如果把 redis 和客户端放在同一台机器,网络延迟会更小,一般情况下可以打到 60000 次每秒甚至更高,取决于机器性能。
锁不是影响性能的主要因素。线程锁 (mutex_lock) 只有在遇到冲突的情况下性能会下降,而正常情况下,遇到冲突的概率很低。如果只是简单的加锁、释放锁速度是非常快的,每秒钟上千万次没问题。memcache 内部用到了大量的锁,并没有见到性能降低。
线程也不是影响吞吐量的重要因素。如第一点来说,一般情况下,程序处理内存数据的速度远高于网卡接收的速度。使用线程好处是可以同时处理多条连接,在极端情况下,可能会提高响应速度。
使用 epoll 或 libevent 等因为异步非阻塞 IO 编程只能这么做。与之对应的是同步阻塞 IO 编程,使用多进程或多线程实现多条连接的处理,比如 apache。一般情况下,异步非阻塞 IO 模型性能是远高于同步阻塞 IO 模型的,可以参考 nginx 与 apache 性能的对比。
libevent 并不比 redis 自己实现的 ae_event 慢,代码多是应为 ae_event 只实现了 redis 需要的功能,而 libevent 则具有更多的功能,比如更快的定时器、buffer event 模型,甚至自带了 DNS、HTTP 协议的处理。并且 libevent 更通用,而 redis 只专注于 linux 平台。
最后回答问题,快在哪? 1、纯内存操作 2、异步非阻塞 IO