【Redis基本】
1.redis安装完成后的几个文件:
redis-benchmark 性能测试工具(批量写入)./bin/redis-benchmark -n 10000 即可一次性写入10000条数据
redis-check-aof 日志文件检测工具(比如断电造成日志损坏,可以检测并修复)
redis-check-dump 快照文件检测工具,效果类上
redis-cli 客户端
redis-server 服务端
2.配置redis在后台运行:编辑conf配置文件,修改如下内容:daemonize yes
3.Redis中共有16个数据库(服务器),打开 redis.conf可以修改。使用select 1可以切换到1号数据库。
【Redis对于key的操作命令】
keys */?/[]:查询相应的key,支持*(任意字符)、?(单个字符)、[](括号内的一个字符)匹配
(查看当前有哪些key:keys *; 完整匹配:keys site;模糊匹配:keys s* 、 keys si?e 、 keys sit[ey])
randomkey:随机返回key
type key:判断key的类型,有string,set,zset,link,hash
exists key:判断key是否存在,返回1/0
del key1 key2 ... KeyN :删除1个或多个键。不存在的key忽略掉,返回真正删除的key的数量
rename key newkey:给key赋一个新的key名。如果newkey已存在,则newkey的原值被覆盖
renamenx key newkey:同rename,只是newkey存在时,不进行操作
move key db:移动某个key到另一个数据库(例如:move site 1)
ttl key:查询key的剩余生命周期(返回秒)
pttl key:查询key的剩余生命周期(返回毫秒)
expire key: 设置key的生命周期,以秒为单位
pexpire key: 设置生命周期,以毫秒数为单位
persist key: 把指定key置为永久有效
【Redis字符串类型的操作】
set key value [ex 秒数]/[px 毫秒数]
案例:set a 1 ex 10 #10秒有效
set key value [nx]/[xx] (nx表示key不存在时执行操作;xx表示key存在时执行操作。)
案例:set a 2 xx #设置一个已经存在的key
mset :一次性设置多个键值,mset key1 v1 key2 v2 ...
案例:mset a hello b bold c cat d dog
get key: 获取key的值
mget key1 key2 ... keyN: 获取多个key的值
案例:mget a b c d
setrange key offset value: 对字符串从第offset个字符开始替换成value
案例:setrange a 2 ?? #结果是he??o
append key value :把value追加到key的原值上
案例:append a @@ #结果是 he??o@@
getrange key start stop :是获取字符串中 [start, stop]范围的(左数从0开始,右数从-1开始)
案例:set title ‘chinese’;getrange title 0 3 ====> ‘chin’
getset key newvalue :获取并返回旧值,设置新值,之后会替换原来的值。
案例:getset title ‘renxing’; ====>返回‘chinese’,再次get得到‘renxing’
incr key:指定的key的值加1,并返回加1后的值。set age 20;incr age ==> 21
decr key:指定的key的值减1,并返回减1后的值。decr age; ====> 20
incrby key number:指定的key的值加number,并返回结果。incr age 4; ===>24
decrby key number:指定的key的值减number,并返回结果。decr age 8; ===>16
incrbyfloat key floatnumber:指定的key的值加浮点值,并返回结果(注意:没有decrbyfloat)
setbit key offset value :设置offset对应二进制位上的值,返回该位上的旧值
getbit key offset:获取值的二进制表示,对应位上的值(从左,从0编号)
bitop operation destkey key1 [key2 ...]:对key1,key2..keyN作operation,并将结果保存到 destkey 上。operation 可以是 AND 、 OR 、 NOT 、 XOR。注意: 对于NOT操作, key不能多个。(基本没明白)
【link 链表结构】
lpush key value: 把值插入到链接头部; rpush key value: 把值插入到链接尾部;
案例:lpush wx a;rpush wx b;rpush wx c;lpush wx 8; ===> 8 a b c
lrange key start stop: 返回链表中[start ,stop]中的元素,要取所有元素就是从0到-1.
案例:lrange wx 1 2; ===> a b
lpop key value: 从链接头部删除值; rpop key value: 从链接尾部删除值;
案例:lpop wx;(8) rpop wx;(c) ===> a b
lrem key count value: 从key链表中删除count个 value值
案例①:rpush answer a d b a c b a; lrem answer 1 b; #从左向右删除一个b,结果是 a d a c b a
案例②:lrem answer -2 a; #从右向左删除2个a,结果是 a d c b
ltrim key start stop: 剪切key对应的链接,切[start,stop]一段,并把该段重新赋给key
案例:rpush wx a b c d e f; ltrim wx 2 5; #截取2和5之间的一段,结果是 c d e f
lindex key index: 返回index索引上的值。llen key:计算链接表的元素个数。
案例:lindex wx 0; ===>c lindex wx 1; ===>d llen wx; ===>4
linsert key after search value: 在key链表中寻找’search’,并在search值之前插入value。 linsert key before search value:同上,之后。注: 一旦找到一个search后命令就结束了,因此不会插入多个value。
案例:rpush num 1 3 6 8 9; linsert num before 3 2 #在3前面插入一个2,结果 1 2 3 6 8 9
rpoplpush source bak: 把source链表的尾部拿出,放在bak链表的头部,并返回该单元值(应用双链表完成安全队列)
案例:rpush source a b c;rpush bak 1 2 3;rpoplpush source bak(c);===>此时source:a b,此时bak:c 1 2 3
brpop/blpop key timeout:等待弹出key的尾(brpop)/头(blpop)元素。timeout为等待超时时间,如果为0则一直等待。(应用场景: 长轮询Ajax,在线聊天时,能够用到)
案例:两个终端,其中一个使用 brpop job 20,表示等待20秒;另一个rpush job e 表示给job写入数据,此时再回到第一个终端,会看到:job e (15.72s).
【set无序集合】----集合的性质: 唯一性,无序性,确定性
sadd key value1 value2: 往集合key中增加元素(由于具有唯一性,所以增加一个已经存在的元素时会返回0)
案例:sadd city beijing shanghai jilin
scard key : 返回集合中元素的个数。
案例:scard city ===> 3
smambers key: 返回集合中所有元素。
案例:smembers city ===> beijingjilin shanghai
srandmember key: 返回集合中随机的1个元素。
案例:srandmember city ===> beijing(随机的)
sismember key value: 判断value是否存在key集合中,存在返回1,不存在返回0。
案例:sismember city wlmq; ===> 0
srem key value1 value2:删除集合中值为 value1 value2的元素;返回真正删除掉的元素的个数。
案例:srem city jilin; ===>1,此时city的结果是 beijing shanghai
spop key:返回并删除集合key中1个随机元素。随机--体现了无序性。
案例:spop city; ===>shanghai(随机的),此时city的结果是 beijing
smove key1 key2 value:把集合key1中的value值移动到集合key2中
案例:sadd city beijing shanghai jilin;
sadd name jerry helen rx polly;
smove name city polly; ===>此时 city是 beijing polly jilin shanghai
sinter key1 key2 key3: 求出key1 key2 key3 三个集合中的交集并返回
sinterstore dest1 key1 key2 key3: 求出key1 key2 key3 三个集合中的交集,并赋给dest1
sunion key1 key2.. Keyn : 求出key1 key2 keyn的并集,并返回
sunionstore dest2 key1 key2.. Keyn : 求出key1 key2 keyn的并集, 并赋给dest2
sdiff key1 key2 key3: 求出key1与key2 key3的差集,即key1-key2-key3
sdiffstore dest3 key1 key2 key3: 求出key1与key2 key3的差集, 并赋给dest3
【order set 有序集合】
zadd key score1 value1 score2 value2 .. :给有序集合添加元素
案例:zadd class 12 lily 13 lucy 18 lilei 6 poly ===>4
zrange key start stop [WITHSCORES] :把集合升序排列后,返回名次[start,stop]的元素。Withscores 是把score也打印出来。取出所有数据start=0,stop=-1即可。
zrevrange key start stop [WITHSCORES] 相反的降序排列。
案例:zrange class 1 2 #取出第1名到第2名的数据(从小到大),结果是 lily(12) lucy(13)
zrangebyscore key min max [withscores] [limit offset N]: 取分数介于min和max之间的,按照分数升续排序;limit offset N:从第 offset个开始取出N个。
案例:zrangebyscore class 1 20 limit 1 2 #取分数介于1到20之间的,从1位开始取,取2个。
zrevrangebyscore key max min [withscores] [limit offset N]: 取分数介于max和min之间的,按照分数降续排序(注意这里是max到min)
zrank key member:查询member的排名(升续 从0名开始)
zrevrank key memeber:查询 member的排名(降续 从0名开始)
案例:zrank class poly; zrevrank class poly; #升序情况下poly是第0名,降序情况下是第3名。
zrem key value1 value2 .. : 删除集合中的元素
案例:zrem class lily poly; #删除name是lily和poly的
zremrangebyscore key min max :按照socre来删除元素,删除score在[min,max]之间的
案例:zremrangebyscore class 10 15; #删除score介于10到15的
zremrangebyrank key start end: 按排名删除元素,删除名次在[start,end]之间的
案例:zremrangebyrank class 0 2 #删除名次介于第0名到第2名的。
zcard key: 返回元素个数。
案例:zcard class; ===>4
zcount key min max:返回分数在 [min,max] 区间内元素的数量
案例:zcount class 10 15; ===>2
zinterstore destination numkeys key1 [key2 ...]
[WEIGHTS weight [weight ...]]
[AGGREGATE SUM|MIN|MAX]
求key1,key2的交集,key1,key2的权重分别是 weight1,weight2。聚合方法用: sum |min|max。聚合的结果保存在dest集合内。
zunionstore destination numkeys key1 [key2 ...]
[WEIGHTS weight [weight ...]]
[AGGREGATE SUM|MIN|MAX]
取并集,和上面类似。
【Hash 哈希数据类型】
hset key field value : 把key中 filed域的值设为value(没有则添加,有则覆盖)
案例:hset user1 name lisi; hset user1 age 28; hset user1 height 175;
hget key field : 返回key中field域的值
案例:hget user1 name ===> lisi
hgetall key :返回key中,所有域与其值
案例:hgetall user1 ===> name lisi age 28 height 175
hmset key field1 value1 [field2 value2 field3 value3 ......fieldN valueN] : 设置field1->N 个域, 对应的值是value1->N 【对应PHP理解为 $key = array(file1=>value1, field2=>value2 ....fieldN=>valueN) 】
案例:hmset user2 name wang age 10 height 100;
hgetall user2 ===> name wang age 10 height 100
hmget key field1 field2 fieldN : 返回key中field1 field2 fieldN域的值
案例:hmget user1 name height ===> lisi 175
hlen key : 返回key中元素的数量
案例:hlen user1 ===> 2
hkeys key : 返回key中所有的field
案例:hkeys user1 ===> name height
hvals key : 返回key中所有的value
案例:hvals user1 ===> lisi 175
hexists key field : 判断key中有没有field域
案例:hexists user1 name ===>1
hincrby key field value : 是把key中的field域的值增长整型值value
hincrbyfloat key field value : 是把key中的field域的值增长浮点值value
案例:对user2的age值+1: hincrby user2 age 1 ===> 11
hdel key field : 删除key中 field域
案例:hdel user1 age; hgetall user1 ===> name lisi height 175
【Redis的事务】---- Redis支持简单的事务
Mysql:开启[start transaction],语句[普通sql], 失败[rollback 回滚], 成功[commit].
Redis: 开启[muitl],语句[普通命令],失败[discard 取消],成功[exec].
Redis的事务中,只负责监测key没有被改动.具体的命令---- watch命令(监视)
watch key1 key2 ... keyN 作用:监听key1 key2..keyN有没有变化,如果有变, 则事务取消
unwatch 作用: 取消所有watch监听
【消息订阅】
订阅端: Subscribe 频道名称
发布端: publish 频道名称 发布内容
psubscribe 通配符匹配接收:
【Redis持久化配置】----- 2种方式:(1)rdb快照持久化 (2)日志
1. rdb的工作原理:
每隔N分钟或N次写操作后,从内存dump数据形成rdb文件,压缩,放在备份目录。优势在于:恢复速度很快!
2. redis.conf 配置文件中 rdb快照相关参数(如果这3个选项都屏蔽,则rdb禁用)
save 900 1 #刷新快照到硬盘中,必须满足两者要求才会触发,即900秒之后至少1个关键字发生变化。
save 300 10 #必须是300秒之后至少10个关键字发生变化。
save 60 10000 #必须是60秒之后至少10000个关键字发生变化。
3. rdb的缺陷
在2个保存点之间断电,将会丢失1-N分钟的数据。出于对持久化的更精细要求,redis增添了aof方式 append only file
4. aof配置
[开启] redis.conf ,搜索 aof:将的appendonly no 改为 appendonly yes,并设置aof的保存目录。
[具体配置]
appendonly no #是否打开aof日志功能
appendfsync always #每1个命令,都立即同步到aof.安全,速度慢,丢失数据少
appendfsync everysec #折中方案,每秒写1次
appendfsync no #由操作系统判断缓冲区大小,统一写入到aof. 同步频率低,速度快。
no-appendfsync-on-rewrite yes: #正在导出rdb快照的过程中,要不要停止同步aof(重写aof时同步最新数据)
auto-aof-rewrite-percentage 100 #aof文件大小比起上次重写时的大小,增长率100%时,重写(当前aof文件是上次重写是大N%时重写)
auto-aof-rewrite-min-size 64mb #aof文件,至少超过64M时,重写(aof重写至少要达到的大小)
5. 把最终的结果逆化为新的变量,只存储最终的结果,忽略中间变化的过程,叫做:aof重写。需要用到上面配置文件里的下面两个选项:
① auto-aof-rewrite-percentage 100 (假如上次aof的文件大小是2MB,那么下次达到4MB则重写,再下次8MB重写,再下次16MB...)
② auto-aof-rewrite-min-size 64mb(因为前期文件较小,重写的意义不大,可以设置当大于64MB的时候再重写。当然此值可以配置。)
◆ 还有一个命令:bgrewriteaof 可以直接马上重写。
◆ 没有用aof时,1秒钟可以写入1800条。打开aof并设置为1秒钟备份一次,测试结果1秒钟可以写入1300条,性能稍微会下降。
6. rdb和aof的一些注意事项:
① 在dump rdb过程中,aof如果停止同步,会不会丢失?(不会,所有的操作缓存在内存的队列里, rdb执行dump完成后,统一操作.)
② aof重写是指什么? (aof重写是指把内存中的数据,逆化成命令,写入到.aof日志里.解决 aof日志过大的问题.)
③ rdb和aof,两种是否可以同时用? (可以,而且推荐这么做)
④ 如果rdb文件和aof文件都存在,优先用谁来恢复数据? (优先aof)
⑤ 恢复时rdb和aof哪个恢复的快 (rdb快,因为其是数据的内存映射,直接载入到内存,而aof是命令,需要逐条执行)
【Redis主从复制】
1. 集群的作用:主从备份,防止主机宕机;读写分离,分担master的任务;任务分离,主从服务器分别负责备份工作与计算工作。
2. 集群的形式:① slave1→master←slave2:所有的slave都围着master。
② slave2→slave1→master:后面的slave作为前面slave的slave。好处是:master宕机后,可以直接切换到slave1。
3. 主要配置:[Master配置]:关闭rdb快照(备份工作交给slave),master可以开启aof。[slave配置]:声明slave-of;某1个slave打开 rdb快照功能;配置是否只读(slave-read-only)。
4.如果主服务器设置了密码,那么从服务器就获取不到主服务器的key了,此时就需要在所有的从服务器上也加上密码。其实内网之间其实是不需要密码的。
5.redis主从复制的缺陷:每次salave断开后,(无论是主动断开,还是网络故障),如果再连接master,都要把master全部dump出来rdb,再aof,即同步的过程都要重新执行一遍.所以:多台slave不要一下都启动起来,否则master可能I/O剧增.
【运维常用的server端命令】(不区分大小写,下面的写法是为了方便查阅)
time 查看时间戳与微秒数
dbsize 查看当前库中的key数量
BgRewriteAof 后台进程重写AOF
BgSave 后台保存rdb快照
save 保存rdb快照
LastSave 上次保存时间
SlaveOf 设为slave服务器
shutdown[""|save|nosave] 断开连接,关闭服务器
SlowLog 显示慢查询
config get 获取配置信息(例如:config get requirepass #是否需要密码 )
config set 设置配置信息(例如:cinfig set slowlog-max-len #存储多少条慢查询的记录)
monitor 打开控制台
sync 主从同步
client list 客户端列表
client kill 关闭某个客户端
client setname 为客户端设置名字
client getname 获取客户端名字
FlushAll 清空所有db(慎用)
FlushDB 清空当前db(慎用)
◆ 如果不小心运行了flushall, 立即 shutdown nosave ,关闭服务器。然后手工编辑aof文件, 去掉文件中的 “flushall ”相关行, 然后开启服务器,就可以导入回原来数据。如果flushall之后,系统恰好bgrewriteaof了,那么aof就清空了,数据丢失.
◆ info:显示服务器全部信息(info后面可以直接跟上想要获取的子阶段的内容,例如:INFO Server/ INFO Memory /)
* 通过 INFO 得到的数据,主要观察如下几个:
① Memory(内存)
used_memory:859192 #实际占用的数据结构的空间
used_memory_rss:7634944 #理论上分配的空间
mem_fragmentation_ratio:8.89 #前2者的比例,1.N为佳。如果此值过大,说明redis的内存的碎片化严重,可以导出再导入一次.
② Persistence (持久化的信息)
rdb_changes_since_last_save:0 #自从上次rdb改动之后,又有多少改动
rdb_last_save_time:1375224063 #上次改动保存的时间
③ Status中的fork耗时:
latest_fork_usec:936 #上次导出rdb快照,持久化花费微秒
④ Replication (主从复制的信息)
role:master
connected_slaves:1
slave0:127.0.0.1,6381,online
【sentinel运维监控】
◆ 复制redis安装包下的 sentinel.conf 到 redis运行目录下,进行如下编辑:
sentinel monitor mymaster 127.0.0.1 6379 1 # “1”表示有几个sentinel失效,生产环境下建议改为>2
sentinel parallel-syncs mymaster 1 #当master宕机后,同时把几个slave指向新的master。
接下来,启动三台服务器。使用 ./bin/redis-server ./sentinel.conf --sentinel 启动sentinel
接下来在6379客户端执行 shutdown ,故意让master宕机,再回到刚才的sentinel界面,稍等片刻,可以看到:master 由6379转移到了(switch) 6381。
◆ 总结:
① Sentinel不断与master通信,获取master的slave信息,监听master与slave的状态。如果某slave失效,直接通知master去除该slave。如果master失效,是按照slave优先级(可配置), 选取1个slave做 new master,把其他slave转换为new slave。
② sentinel与master通信,如果某次因为master IO操作频繁,导致超时,此时,认为master失效,很武断:sentnel允许多个实例看守1个master, 当N台(N可设置)sentinel都认为master失效,才正式失效.
③ Sentinel选项配置
port 26379 # 端口
sentinel monitor mymaster 127.0.0.1 6379 2 #给主机起的名字(不重即可), 当2个sentinel实例都认为master失效时,正式失效
sentinel down-after-milliseconds mymaster 30000 #多少毫秒后连接不到master认为断开
sentinel can-failover mymaster yes #是否允许sentinel修改slave->master. 如为no,则只能监控,无权修改.
sentinel parallel-syncs mymaster 1 #一次性修改几个slave指向新的new master.
sentinel client-reconfig-script
mymaster /var/redis/reconfig.sh # 在重新配置new master,new slave过程,可以触发的脚本
【Redis的key的设计原则】---- 实现书签系统
1. 使用普通的key方式存储书籍
set book:5:title 'PHP圣经'
set book:6:title 'ruby实战'
set book:7:title 'mysql运难'
set book:8:title ‘ruby server’
2. 使用“集合”的方式存储标签和书籍的关系
sadd tag:PHP 5 //tag为PHP的标签对应 5号书籍
sadd tag:WEB 5 6 //tag为WEB的标签对应 5号书籍和6号书籍,如下类似
sadd tag:database 7
sadd tag:ruby 6 8
sadd tag:SERVER 8
3.查询方法
① 查询既有PHP又有WEB的书——查集合的交集(Sinter)
sinter tag:PHP tag:WEB ===> 5
② 查询有PHP或有WEB标签的书——查集合的并集(Sunin)
sunin tag:PHP tag:WEB ===> 5 6
③ 查询含有ruby,不含WEB标签的书——求差集(Sdiff)
sdiff tag:ruby tag:WEB ===> 8
【开发中Redis的key的设计技巧】
第1段:把表名转换为key前缀。如 tag:
第2段:放置用于区分区key的字段,对应mysql中的主键的列名。如userid
第3段:放置主键值。如2,3,4...., a , b ,c
第4段:放置要存储的列名
例如:MySQL的如下数据。user表:
userid:9
username:lisi
password:111111
email:lisi@163.com
转换为Redis存储,方法如下:
set user:userid:9:username lisi
set user:userid:9:password 111111
set user:userid:9:email lisi@163.com
◆ 查询方法
① 查询有哪些字段:keys user:userid:9* ,结果如下:
user:userid:9:password
user:userid:9:username
user:userid:9:email
② 查询某个字段的值:
get user:userid:9:username ===> lisi
get user:userid:9:email ===> lisi@163.com
◆ 注意:冗余字段。
按照username查询,需要生成一条按照username列为主的key-value:set user:username:lisi:uid 9
这样就可以根据username:lisi:uid ,查出userid=9。 再查user:9:password/email ... 完成了根据用户名来查询用户信息
【PHP操作Redis】
<?php
//实例化Redis对象
$redis = new Redis();
//连接到redis服务器
$flag = $redis->open('localhost',6379);
//print_r($flag);
//set方法存入数据
$redis->set('user:userid:2:username','renxing');
$value = $redis->get('user:userid:2:username');
print_r($value); //输入“renxing”
?>
【增删改查分页完整操作】
使用hash类型存储数据。
hmset:批量添加数据,例如:$redis->hmset(‘user:1’,array(‘name’=>“renxing”,‘age’=>“28”));
hgetall:获取所有数据
del:删除数据
【连接到服务器】
$redis = new Redis(); //实例化
$redis->connect("localhost"); //连接到服务器
$redis->auth("renxing"); //授权
【执行添加操作】【修改操作(类似 添加操作)】
$uid = $redis->incr("userid"); //设定一个自增的数值,类似于主键
$add_data = array(
"uid"=>$uid,
"username"=>$_POST[username],
"password"=>$_POST[password],
"age"=>$_POST[age]
); //使用hmset批量添加元素
$res = $redis->hmset("user:".$uid,$add_data);
(此时用终端运行hgetall user:1,得到如下数据:uid 1 username renxing password 123 age 18 )
【获取数据】
require_once "conn.php";
$incr_id = $redis->get("uid"); //获取当前自增的数值
for($i=1;$i<=$incr_id;$i++){
$data[] = $redis->hgetall("user:".$i); //使用hgetall获取数据
}
print_r($data);
【删除操作】
$res = $redis->del("user:".$uid);
【分页操作】
[思路分析] 将所有的uid存在链表结构list中,用rpush uid 1 存储,用lrange uid 0 -1 获取全部数据。在删除数据的时候,用lrem删除对应的id号。PHP中用lsize可以获取list的总数。假设每页显示3条,那么,第1页:lrange uid 0 2,第2页:lrange uid 3 5,第3页:lrange uid 6 8。
...
$count = $redis->lsize("uid"); //用户总数
$page_size = 3; //每页显示多少条数据
$page_num = $_GET['page']?$_GET['page']:1; //当前第几页
$page_count = ceil($count/$page_size); //总页数
$page_up = ($page_num-1)<=1?1:($page_num-1); //上一页
$page_down = ($page_num+1)>=$page_count?$page_count:($page_num+1); //下一页
//分析:假设每页显示3条,那么 —— 第1页: lrange uid 0 2 ; 第2页: lrange uid 3 5
$a = ($page_num-1)*$page_size;
$b = $a+$page_size-1;
$ids = $redis->lrange("uid",$a,$b);
//print_r($ids);
/*分页取数据*/
foreach($ids as $v){
$data[] = $redis->hgetall("user:".$v);
}
【登录操作】
在注册操作的时候要存储一个 username和uid的对应关系 $redis->set("username:".$username,$uid); 那么登录的时候,使用get 就可以知道这个username是否存在 $id = $redis->get("username:".$username); 然后通过这个$id 用hget得到存储在数据库中的password,和用户输入的密码进行判断 $pwd = $redis->hget("user:".$id,"password"); 如果正确了,设置SESSION或者cookie。
【加关注】
① 用集合存储比较合适,sadd存储数据,smembers获取数据,sdiff获取两个用户关注的差集。
$id = $_GET['id']; //关注谁的uid
$uid = $_SESSION['uid']; //我的uid
$redis->sadd("user:".$uid.":guanzhu",$id); //$uid 关注了哪些人
$redis->sadd("user:".$id.":fensi",$uid); //$id 的粉丝
得到差集,就可以推荐用户了。
② 在PHP中显示我关注了谁。
$my_guanzhu = $redis->smembers("user:".$_SESSION['uid'].":guanzhu");
foreach($my_guanzhu as $gz){
$row = $redis->hgetall("user:".$gz);
echo $row['uid'].'----'.$row['username'].'----'.$row['age'].'<br>';
}
③ 在PHP中显示我的粉丝
$my_fensi = $redis->smembers("user:".$_SESSION['uid'].":fensi");
foreach($my_fensi as $fs){
$row2 = $redis->hgetall("user:".$fs);
echo $row2['uid'].'----'.$row2['username'].'----'.$row2['age'].'<br>';
}