刚开始只是使用想学习一种用做缓存的数据库,边看视频,边翻阅官方的文档才发现Redis如此强大。Redis不仅仅是一个简单的键值对数据库,它实际上是一个数据结构服务器,支持不同类型的数据。传统的键值对数据库,你只能给字符类型的键绑定字符类型的数值,在Redis中,可以绑定到更加复杂的数据结构。

1 为org-babel添加对redis的支持

配置org-redis

cd ~/.emacs.d/private
git clone \
https://github.com/stardiviner/ob-redis.git
.spacemacs

修改.spacemacs添加如下两行

(add-to-list 'load-path
"~/.emacs.d/private/ob-redis")


(org-babel-do-load-languages
'org-babel-load-languages
'(
(redis .t)
))

2 安装redis

2.1 直接安装

sudo apt-get install wget curl build-essential tcl -y
sudo apt-get install redis-server -y
#查看是否启动
netstat -tunpl | grep 6379

2.2 编译安装

wget http://download.redis.io/redis-stable.tar.gz
tar -xvzf redis-stable.tar.gz
cd redis-stable
make
make install
mkdir /etc/redis
mkdir /var/redis
adduser --system --group --no-create-home redis
chown redis:redis /var/redis
chmod 770 /var/redis
cp redis-stable/redis.conf /etc/redis/

配置文件更改这几行

##Default port to listen reids. You can also change it as per your need.
port 6379
## If you run Redis from upstart or systemd.
supervised systemd
##Path of the Redis log.
logfile /var/log/redis.log
##Location of the Redis data.
dir /var/redis/
##IP address to listen Redis on. You can also specify multiple IP address.
bind 192.168.0.101

添加service

[Unit]
Description=Redis In-Memory Data Store
After=network.target

[Service]

User=redis
Group=redis
ExecStart=/usr/local/bin/redis-server /etc/redis/redis.conf
ExecStop=/usr/local/bin/redis-cli shutdown
Restart=always

[Install]

WantedBy=multi-user.target

3 常见数据类型操作

3.1 Strings类型数据

Strings是最基本的类型,一个Key对应一个Value,string类型是二进制安全的。Redis的string可以包含任何数据,比如jpg图片或者序列化Ruby对象。 String的值最大长度是512MB。

3.1.1 set

设置key对应的值为string类型的value。命令如下:

set name "wing"
get name

用例:分布式锁1

public class RedisTool {

private static final String LOCK_SUCCESS = "OK";
private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX";
private static final Long RELEASE_SUCCESS = 1L;

/**
* 尝试获取分布式锁
* @param jedis Redis客户端
* @param lockKey 锁
* @param requestId 请求标识
* @param expireTime 超期时间
* @return 是否获取成功
*/

public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {

String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);

if (LOCK_SUCCESS.equals(result)) {
return true;
}
return false;

}
/**
* 释放分布式锁
* @param jedis Redis客户端
* @param lockKey 锁
* @param requestId 请求标识
* @return 是否释放成功
*/

public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {

String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));

if (RELEASE_SUCCESS.equals(result)) {
return true;
}
return false;

}
}

3.1.2 setnx

设置key对应的值为string类型的value,如果key已经存在,返回0,nx是not exist的意思。

setnx name "wing"
get name

用例:分布式锁2

3.1.3 setex

设置key对应的值为string类型的value,并指定键值对应的有效期

setex action 10 start
get action

3.1.4 setrange

设置指定key的value值的字符创

set email wing-ho@163.com
get email
setrange email 8 gmail.com
get email

3.1.5 mset

一次设置多个值,成功返回OK,表示所有的值都设置了,失败返回0表示没有任何值被设置。

mset name wing passwd 1234 age 30
get name
get passwd
get age

3.1.6 msetnx

一次设置多个值,成功返回OK,表示所有的值都设置了,失败返回0表示没有任何值被设置,但不会覆盖已存在的可以。

flushall
set name wing
msetnx name wing passwd 1234 age 30
get passwd

3.1.7 get

获取key对应的string值,如果key不存在返回nil

3.1.8 getset

设置key的值,并返回key的旧值

set age 30
getset age 20

3.1.9 getrange

获取key的value值的子字符串

set name hegenrong
getrange name 0 4

3.1.10 mget

一次获取多个key的值,如果对应的key不存在则对应返回nil

mget name passwd age

3.1.11 incr

对key的值做++操作,并返回新的值。如果key不存在,设置为0,再执行++操作。

用例:计数器。例如可以限制一个IP对某个API的访问速率2

incr counter

3.1.12 incrby

同incr类似,加指定值。

incrby counter 10

3.1.13 decr

对key的值做–操作。

3.1.14 decrby

同decr类似,减指定值。

3.1.15 append

给指定key的字符串追加value,返回新字符串的长度。

用例:记录时序数据3

append timeseries "0043"
append timeseries "0035"
getrange timeseries 0 3
getrange timeseries 4 7

3.1.16 strlen

取指定key的value值的长度。key不存在时,返回0。

strlen email
strlen noexistkey

3.2 Hashes类型

Redis hash是一个string类型的field和value的映射表。它的添加、删除操作都是O(1)(平均)。hash特别适合用于存储对象。相较于将对象的每个字段存成单个stirng类型。将一个对象存储在hash类型中会占用更少的内存,而且可以更方便的存取整个对象。

3.2.1 hset

设置hash field为指定值,如果key不存在,则先创建。

hset myhash field1 Hello
hget myhash field1

3.2.2 hsetnx

设置hash field为指定值,如果key不存在,则先创建,如果存在返回0。

flushdb
hsetnx myhash field1 hello
hsetnx myhash field1 hello

3.2.3 hmset

同时设置bash的多个field。

hmset myhash field1 hello field2 world
hmget myhash field1 field2
hmget myhash field1 field2 nofield

3.2.4 hmget

同时全部指定bash field,当指定field不存的时候,返回nil

hmget myhash field1 field2 nofield

3.2.5 hincrby

指定的hash field加上给定的值。值为64位有符号数。

hset myhash field 5
hincrby myhash field 2
hincrby myhash field -2

3.2.6 hlen

返回指定hash的field数量。

hlen myhash

3.2.7 hdel

删除指定hash的field。

hdel myhash field

3.2.8 hkeys

返回hash的所有field。

hkeys myhash

3.2.9 hvals

返回hash的所有value。

hvals myhash

3.2.10 hgeall

获取某个hash中全部的field及value。

hgetall myhash

3.3 Lists类型

List是一个链表结构,主要功能是push、pop、获取一个范围的所有值等等,操作中key理解为链表的名字。Redis的List类型其实就是一个每个子元素都是string类型的双向链表。我们可以通过push、pop操作从链表的头部或者尾部添加删除元素,这样List即可以作为栈,又可以作为队列。

3.3.1 lpush

在key对应的list的头部添加字符串元素。

flushdb
lpush mylist a
lpush mylist b
lrange mylist 0 -1
flushdb
lpush mylist a b c
lrange mylist 0 -1

3.3.2 rpush

在key对应的list的尾部添加字符串元素。

flushdb
rpush mylist world
rpush mylist hello
lrange mylist 0 -1

3.3.3 linsert

在key对应list的特定位置前或后添加字符串

flushdb
rpush mylist world
linsert mylist before world hello
lrange mylist 0 -1
flushdb
rpush mylist world
linsert mylist after world hello
lrange mylist 0 -1

3.3.4 lset

设置list中指定下标的元素值。

flushdb
rpush mylist hello
lset mylist 0 world
lrange mylist 0 -1

3.3.5 lrem

从key对应的list中删除n个和value相同的元素。(n<0从尾删除,n=0全部删除)

flushdb
rpush mylist1 a b c c d c c f
lrem mylist1 2 c
lrange mylist1 0 -1
rpush mylist2 a b c c d c c f
lrem mylist1 -2 c
lrange mylist2 0 -1

3.3.6 ltrim

保留指定key的值范围内的数据。

flushdb
rpush mylist a b c d e
ltrim mylist 1 -1
lrange mylist 0 -1

3.3.7 lpop

从list的头部删除元素,并返回删除的元素。

lrange mylist 0 -1
lpop mylist
lrange mylist 0 -1

3.3.8 rpoplpush

从第一个list的尾部移除元素并添加到第二个list的头部。

rpush mylist1 one two three
rpush mylist2 four five six
rpoplpush mylist1 mylist2
lrange mylist1 0 -1
lrange mylist2 0 -1

3.3.9 lindex

返回名称为key的list中index位置的元素。

lrange mylist2 0 -1
lindex mylist2 0
lindex mylist2 1

3.3.10 llen

返回key对应list的长度

llen mylist2

3.4 Sets类型

set是集合,它是string类型的无序集合。set是通过hash table实现的,添加、删除和查找的复杂度都是O(1)。对集合我们可以取并集、交集、差集。通过这些操作我们可以实现sns中好友推荐和blog的tag功能。

3.4.1 sadd

向名称为key的set中添加元素。

sadd myset hello
sadd myset world
sadd myset world
smembers myset

3.4.2 srem

删除名称为key的set中的元素。

flushdb
sadd myset one
sadd myset two
srem myset one
smembers myset

3.4.3 spop

随机返回并删除名称为key的set中的一个或多个元素。ver 3.2开始支持多个。

flushdb
sadd myset one two three four five
spop myset 2

3.4.4 sdiff

返回所有给定key与第一个key的差集。

flushdb
sadd myset1 one two three four five
sadd myset2 two five six
sdiff myset1 myset2

3.4.5 sdiffstore

返回所有给定key与第一个key的差集,并将结果存为key3。

sdiffstore myset3 myset1 myset2
smembers myset3

3.4.6 sinter

返回所有给定key的交集。

sinter myset3 myset1

3.4.7 sinterstore

返回所有给定key的交集,并将结果存为另一个key。

sinterstore myset4 myset3 myset1
smembers myset4

3.4.8 sunion

返回所有给定key的并集。

sunion myset2 myset3

3.4.9 sunionstore

返回所有给定key的并集,并将结果存为另一个key。

sunionstore myset5 myset2 myset3
smembers myset5

3.4.10 smove

从第一个key对应的set中移除member并添加到第二个对应的set中。

flushdb
sadd myset1 one two three
sadd myset2 four five six
smove myset1 myset2 three
smembers myset2

3.4.11 scard

返回名称为key的set的元素个数。

scard myset2
smembers myset2

3.4.12 sismember

测试member是否是名称为key的set的元素。

smembers myset2
sismember myset2 two
sismember myset2 three

3.4.13 srandmember

随机返回名称为key的set的一个元素,但不删除元素。

sadd myset one two three four
smembers myset
srandmember myset

3.5 Sorted sets类型

sorted set是set的一个升级版本,它在set的基础上增加了一个顺序属性,这一属性在添加修改元素的时候可以指定,每次指定后,zset会自动重新按新的值调整顺序。可以理解为有两列的mysql表,一列存value,一列存顺序。操作中key理解为zset的名字。

3.5.1 zadd

向名称为key的zset中添加元素member,score用于排序。如果该元素存在,则更新其顺序。

flushdb
zadd myzset 1 one
zadd myzset 2 uno
zadd myzset 2 two 3 three
zrange myzset 0 -1 withscores

3.5.2 zrem

删除名称为key的zset中的元素。

zrange myzset 0 -1 withscores
zrem myzset two
zrange myzset 0 -1 withscores

3.5.3 zincrby

如果在名称为key的zset中已经存在元素member,则该元素的score增加increment,否则向该集合中添加该元素,其score的值为increment。

zrange myzset 0 -1 withscores
zincrby myzset 2 one
zrange myzset 0 -1 withscores

3.5.4 zrank

返回名称为key的zset中memeber元素的排名(按score从小到打排序)即下标。

flushdb
zadd myzset 1 one 2 two 3 three 5 five
zrange myzset 0 -1 withscores
zrank myzset two

3.5.5 zrevrank

返回名称为key的zset中member元素的排名(按score从大到小排序)及下标。

zrange myzset 0 -1 withscores
zrevrank myzset five

3.5.6 zrevrange

返回名称为key的zset(按score从大到小顺序)中的index从start到end的所有元素。

zrange myzset 0 -1 withscores
zrevrange myzset 0 -1 withscores

3.5.7 zrangebyscore

返回集合中score在给定区间内的元素。

zrange myzset 0 -1 withscores
zrangebyscore myzset 2 3
zrangebyscore myzset 2 3 withscores

3.5.8 zcount

返回集合中score在给定区间内的数量。

zrange myzset 0 -1 withscores
zcount myzset 0 2

3.5.9 zcard

返回集合中元素个数。

zcard myzset

3.5.10 zremrangebyrank

删除集合中排名在给定区间的元素。

flushdb
zadd myzset 1 one 2 two 3 three 5 five
zremrangebyrank myzset 0 1
zrange myzset 0 -1 withscores
zremrangebyrank myzset 0 0
zrange myzset 0 -1 withscores

3.5.11 zremrangebyscore

删除集合中score在给定区间的元素。

flushdb
zadd myzset 1 one 2 two 3 three 5 five
zremrangebyscore myzset 2 3
zrange myzset 0 -1 withscores

4 常用命令

Redis提供了丰富的命令对数据库和各种数据库类型进行操作,这些命令可以在Linux终端使用。

4.1 键值相关命令

4.1.1 keys

返回满足给定条件的所有键。支持glob风格的匹配模式,如h?ll、h*llo、h[ab]llo、h[e]llo,h[a-b]llo等。

keys myzs*

4.1.2 exists

确认一个key是否存在。存在返回1,不存在返回0。

exists name
exists myzset

4.1.3 del

删除一个key。成功返回1,失败返回0。

del name
del myzset

4.1.4 expire

设置一个key的过期时间。

hmset myhash name wing age 30
expire myhash 10
ttl myhash

4.1.5 move

将当前数据库中的key转移到其他的数据库中。

select 0
set age 30
get age
move age 1
get age
select 1
get age

4.1.6 persist

移除给定key的过期时间。

set age 30
expire age 300
ttl age
persist age
ttl age

4.1.7 randomkey

随机返回key空间的一个key。

randomkey

4.1.8 rename

重命名key。

rename age name
get name

4.1.9 type

返回值的类型。

set name wing
hset myhash name wing
rpush mylist name age weight
sadd myset one tow
zadd myzset 1 one 2 tow 3 three
type age
type name
type myhash
type mylist
type myset
type myzset

4.2 服务器相关命令

4.2.1 ping

测试连接是否存活。

redis-cli ping
sudo systemctl stop redis-server
redis-cli ping
sudo systemctl start redis-server
redis-cli ping

4.2.2 echo

在命令行打印一些内容。

echo wing

4.2.3 select

选择数据库。Redis数据库编号从0~15,我们可以选择任意一个数据库来进行数据的存取。

select 1
select 16

4.2.4 dbsize

返回当前数据库中key的数目。

dbsize

4.2.5 info

获取服务器的信息和统计。

info

4.2.6 config get

实时传输收到的请求。

config get *
config get dir

4.2.7 flushdb

删除当前数据库中的所有key。

dbsize
flushdb
dbsize

4.2.8 flushall

删除所有数据库中的所有key。

dbsize
select 1
dbsize
flushall
dbsize

4.2.9 quit

退出连接。

quit

5 Redis高级使用特性

5.1 安全性

设置客户端连接后进行任何其他指定操作前需要使用的密码。 警告:因为Redis速度相当快,所有一台比较好的服务器下,一个外部的用户可以在一秒钟进行150K次的密码尝试,这意味这你需要指定一个非常非常强大的密码来防止暴力破解。

 #sudo sed '/requirepass foobared/a\requirepass wing' -i /etc/redis/redis.conf
sudo systemctl restart redis-server
redis-cli keys \*
redis-cli -a wing keys \*

5.2 主从复制

Redis主从复制配置是使用都非常简单,通过主从复制可以允许多个slave server拥有和master server相同的数据库副本。

5.2.1 Redis主从复制特点

  1. Master可以拥有多个Slave。
  2. 多个Slave可以连接到同一个Master外,还可以连接到其他的Slave。
  3. 主从复制不会阻塞Master,在同步数据时,Master可以继续处理client请求。
  4. 提高系统的伸缩性。

5.2.2 Redis主从复制过程

  1. Slave与Master建立连接,发送gsync同步命令
  2. Master会启动一个后台进程,将数据库快照保存到文件中,同时Master主进程会开始收集新的写命令并缓存。
  3. 后台完成保存后,就将此文件发送给Slave
  4. Slave将此文件保存到硬盘上。

5.2.3 配置主从服务器

配置Slave服务器很简单,只需要在Slave的配置文件中加入一下配置:

#指定master的ip和端口
slaveof 192.168.1.1 6379
#指定主机的密码
masterauth wing

5.2.4 测试

我们在master数据库上设置一对键值对

set name master

在slave数据库上取这个键

get name

怎么判断哪个是主哪个是从呢?我们只需调用info就可以得到主从的信息。

5.3 事务处理

Redis对事务的支持目前还比较简单。Redis只能保证一个client发起的事务中的命令可以连续的执行,而中间不会插入其他client的命令。当一个client在一个连接中发出multi命令时,这个连接会进入一个事务上下文,该连接后续的命令不会立即执行,而是先放到一个队列中,当执行exec命令时,redis会顺序的执行队列中的所有命令。

5.3.1 开启一个事务

auth wing
get age
multi
set age 10
set age 20
exec
get age

5.3.2 取消一个事务

auth wing
set age 10
get age
multi
set age 10
set age 20
discard
get age

5.3.3 乐观锁复杂事务控制

乐观锁:大多数是基于数据版本(version)的记录机制实现的。即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过数据库表添加一个version字段来实现读取出数据时,将此版本号一同读出,之后更新时,对此版本号加1。此时,将提交数据的版本号与数据表对应记录的当前版本号进行比对,如果提交的数据版本号大于数据库当前版本号,则予以更新,否则认为是过期数据。

Redis乐观锁实例:假设有一个age的key,我们开2个session来对age进行复制操作,我们来看一下结果如何。 第一步开启一个session:

get age
watch age
multi
#开启第二个session
exec #返回nil
get age

第二部开启另外一个session:

set age 30
get age

watch命令监视给定的key,当exec执行的时候如果监视的key从调用watch之后发生过变化,则整个事务会失败。也可以调用watch多次监视多个key,这样就可以对指定的key加乐观锁了。注意watch的key是对这个连接有效的,事务也一样。如果连接断开了,监视和事务都会被自动清除,当然了exec,discard,unwatch命令都会清除连接中的所有监视。

redis的事务实现是如此简单,当然会存在一些问题。第一个问题是redis只能保证事务的每个命令连续执行,但是如果事务中的一个命令失败了,并不回滚其他命令,比如使用的命令类型不匹配。

auth wing
set age 30
set name wing
multi
incr age
incr name
exec
get age
get name

从这个例子中可以看到,age由于是个数字,那么它可以有自增运算,但是name是个字符串,无法对其进行自增运算,所以会报错,如果按传统关系型数据库的思路来讲,整个事务都会回滚,但是我们看到redis却是可以执行的命令提交了,所以这个现象对于习惯于关系型数据库操作的朋友来说是很别扭的,这一点也是redis今天需要改进的地方。

5.4 持久化机制

Redis是一个支持持久化的内存数据库,也就是说redis需要经常将内存中的数据同步到硬盘来保持持久化。 Redis支持两种持久化方式:

  1. snapshotting(快照)也是默认方式。
  2. Append-only file(缩写aof)的方式。

5.4.1 snapshotting方式

快照是默认的持久化方式。这种方式是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb。可以通过配置设置自动做快照持久化的方式。我们可以配置redis在n秒内如果m个key被修改就自动做快照。

save 900 1 #900秒内如超过1个key被修改,则发起快照保存
save 300 10 #300秒内如超过10个key被修改,则发起快照保存
save 60 10000 #60秒内如超过10000个key被修改,则发起快照保存

5.4.2 aof方式

由于快照方式是在一定间隔时间做一次的,所以如果redis意外down掉的话,就会丢失最后一次快照后的所有修改。aof比快照方式有更好的持久化姓,是由于使用aof时,redis会将每一个收到的写命令都通过write函数追加到文件中,当redis重启时会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。

当然由于os会在内核中缓存write做的修改,所以可能不是立即写到磁盘上。这样aof方式的持久化也还是有可能丢失部分修改。可以通过配置文件告诉redis我们想要通过fsync函数强制os写入到磁盘的时机

appendonly yes   #启用aof持久化方式
appendsync always #收到写命令就立即写入磁盘,最慢,但是保证完全的持久化
appendsync everysec #每秒钟写入磁盘一次,在性能和持久化方面做了很好的折中
appendsync no #完全依赖os,性能最好,持久化没保证

5.5 发布订阅信息

发布订阅(pub/sub)是一种消息通信模式,主要的目的是解除消息发布者和订阅者之间的耦合,Redis作为一个pub/sub的server,在订阅者和发布者之间起到了消息路由的功能。订阅者可以通过subcrible和psubscrible命令想redis server订阅自己感兴趣的消息类型,redis将消息类型称为通道(channel)。当发布者通过publish命令向redis server发送特定类型的信息时,订阅该消息类型的全部client都会收到此消息。 开一个session订阅频道

subscribe channel1 channel2

再开启一个session订阅频道

subscribe channel1

在开启一个session发布订阅

publish channel1 hello
publish channel2 world

用例:基于web的消息推送。

订阅 sub.php

<?php
$ip = "";
$passwd = "";
$redis = new Redis();
$redis->connect($ip);
$redis->auth($passwd);
//订阅
$redis->subscribe(['test'], function($instance, $channelName, $msg){
echo "data:".$msg."\n\n";
});
?>

刘帅翻译的《HTML5数据推送应用开发》详细叙述了SSE、WebSocket或者数据拉取方案的区别和数据推送的兼容性实现方案。

测试页面 index.html

<script type="text/javascript">
//先判断支不支持EventSource
var source = new EventSource("http://localhost/sub.php");
source.onmessage = function(event){
console.log(event);
}
</script>

发布 pub.php

<?php
$ip = "";
$passwd = "";
$redis = new Redis();
$redis->connect($ip);
$redis->auth($passwd);
$redis->publish('test','这里是你要推送的内容');
?>

5.6 虚拟内存的使用

Redis的虚拟内存与操作系统的虚拟内存不是一回事,但是思路和目的都是相同的。就是暂时把不经常访问的数据从内存交换到磁盘中,从而腾出宝贵的内存空间用于其他需要访问的数据。尤其是对于redis这样的内存数据库,内存总是不够用的。除了可以将数据分割到多个redis server外。另外能够提高数据库容量的办法就是使用虚拟内存把那些不经常访问的数据交换到磁盘上。 下面是vm相关配置

vm-enable yes                 #开启vm功能
vm-swap-file /tmp/redis.swap #交换出来的value保存的文件路径
vm-max-memory 1000000 #redis使用额最大内存上限
vm-page size 32 #每个页面的大小32字节
vm-pages 134217728 #最多使用多少页面
vm-max-threads 4 #用于执行value对象换入换出的工作线程数量

6 为web应用添加页面缓存功能

许多网站建设初期只考虑功能实现,SEO优化,带来了巨大的爬虫的流量,为了SEO保持效果,不能阻止爬虫的访问,更要欢迎爬虫的到来。那如何才能应对爬虫带来的巨大流量。最简单有效的方法就是使用缓存。 网站代码是混合php,sql和html的原始写法,只考虑功能实现,完全不考虑代码的可读性的。不可能像Spring那样在接口层面去考虑缓存数据,所以选择粒度较大的页面缓存方式。

<?php
class redisCache {
/**
* $host : redis服务器ip
* $port : redis服务器端口
* $lifetime : 缓存文件有效期,单位为秒
* $cacheid : 缓存文件路径,包含文件名
*/

private $host;
private $port;
private $lifetime;
private $cacheid;
private $data;
public $redis;
/**
* 析构函数,检查缓存目录是否有效,默认赋值
*/

function __construct($lifetime=1800) {
//配置
$this->host = "127.0.0.1";
$this->port = "6379";
$redis = new Redis();
$redis->pconnect($this->host,$this->port);
$this->redis=$redis;
$this->cacheid = $this->getcacheid();
$this->lifetime = $lifetime;
$this->data=$redis->hMGet($this->cacheid, array('content','creattime'));
//print_r($this->redis);
//print_r($this->data);
}

/**
* 检查缓存是否有效
*/

private function isvalid(){
$data=$this->data;
if (!$data['content']) return false;
if (time() - $data['creattime'] > $this->lifetime) return false;
return true;
}
/**
* 写入缓存
* $mode == 0 , 以浏览器缓存的方式取得页面内容
*/

public function write($mode=0,$content='') {
switch ($mode) {
case 0:
$content = ob_get_contents();
break;
default:
break;
}
ob_end_flush();
try {
$this->redis->hMset($this->cacheid, array('content'=>$content,'creattime'=>time()));
$this->redis->expireAt($this->cacheid, time() + $this->lifetime);
}
catch (Exception $e) {
$this->error('写入缓存失败!');
}
}
/**
* 加载缓存
* exit() 载入缓存后终止原页面程序的执行,缓存无效则运行原页面程序生成缓存
* ob_start() 开启浏览器缓存用于在页面结尾处取得页面内容
*/

public function load() {
if ($this->isvalid()) {
echo $this->data['content'];
exit();
}
else {
ob_start();
}
}
/**
* 清除缓存
*/

public function clean() {
try {
$this->redis->hDel($this->cacheid, array('content','creattime'));
}
catch (Exception $e) {
$this->error('清除缓存失败!');
}
}
/**
* 取得缓存文件路径
*/

private function getcacheid() {
return $this->dir.md5($this->geturl()).$this->ext;
}
/**
* 取得当前页面完整url
*/

private function geturl() {
$url = '';
if (isset($_SERVER['REQUEST_URI'])) {
$url = $_SERVER['REQUEST_URI'];
}
else {
$url = $_SERVER['Php_SELF'];
$url .= empty($_SERVER['QUERY_STRING'])?'':'?'.$_SERVER['QUERY_STRING'];
}
echo $url;
return $url;
}
/**
* 输出错误信息
*/

private function error($str) {
echo '<div style="color:red;">'.$str.'</div>';
}
}

//用法:
// require_once('redisCache.php');
// $cache = new redisCache(10); //设置缓存生存期
// if ($_GET['clearCache']) $cache->clean();
// else $cache->load(); //装载缓存,缓存有效则不执行以下页面代码
// //页面代码开始

// //页面代码结束
// $cache->write(); //首次运行或缓存过期,生成缓存



?>
Last Updated 2018-10-14 日 23:38.
Created by Emacs 25.1.1 (Org mode 9.1.14)