Linfinity

Never say never.
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

redis缓存系统

Posted on 2019-01-11 11:28  Linfinity  阅读(500)  评论(0)    收藏  举报

一、基本介绍

1、Redis是一个基于key-value的高速缓存系统,类似于memcached,但是支持更复杂的数据结构List、Set、Sorted Set,并且有持久化的功能

2、Redis是一个开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型Key-Value数据库,并提供多种语言的API 

 

它支持多种(5种)数据类型

1) string:字符串

2) list:列表

3) set:集合

4) sort set:有序的集合

5) hash:哈希

 

redis的特点:

Redis 与其他 key - value 缓存产品有以下三个特点:

1) Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。

2) Redis不仅仅支持简单的key-value类型的数据,同时还提供listsetzsethash等数据结构的存储。

3)Redis支持数据的备份,即master-slave模式的数据备份。

 

redismemcache的对比:

1、存储方式:
memecache 把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小
redis有部份存在硬盘上,这样能保证数据的持久性

存储数据安全--memcache挂掉后,数据没了;redis可以定期保存到磁盘(持久化)
灾难恢复--memcache挂掉后,数据不可恢复; redis数据丢失后可以通过aof恢复
2、数据支持类型
redis在数据支持上要比memecache多的多。Redis不仅仅支持简单的k/v类型的数据,同时还提供listsethash等数据结构的存储
3、使用底层模型不同
新版本的redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。
4、运行环境不同:

redis目前官方最初只支持LINUX 上去行,从而省去了对于其它系统的支持,这样的话可以更好的把精力用于本系统环境上的优化,虽然后来微软有一个小组为其写了补丁。但是没有放到主干上

 

总结:

对于两者的选择还是要看具体的应用场景,如果需要缓存的数据只是key-value这样简单的结构时,我在项目里还是采用memcache,它也足够的稳定可靠。如果涉及到存储,排序等一系列复杂的操作时,毫无疑问选择redis

 

二、redis的安装

linux下安装:

第一步:获取安装包,解压进入目录后,没有编译文件configure直接安装make

 

第二步:创建相应的文件夹,在/usr/local目录下创建

mkdir /usr/local/redis:redis的安装目录

mkdir /usr/local/redis/bin:存放的是服务器端和客户端

mkdir /usr/local/redis/etc:配置文件

 

第三步:拷贝文件配置文件redis.conf,拷贝服务端和客户端等相应的软件到redis/bin目录下

cp redis.conf /usr/local/redis/etc/

cp src/redis-server src/redis-cli src/redis-benchmark src/redis-check-aof src/redis-sentinel /usr/local/redis/bin/

 

第四步:启动redis的服务器和客户端

开启redis服务
bin/redis-server etc/redis.conf

关闭redis
kill `cat /var/run/redis_6379.pid`

redis以守护进程的方式启动,编辑配置文件vim etc/redis.conf

再次启动redis的服务器端

 

第五步:开启客户端

bin/redis-cli

 

说明:

1Redis-benchmark是官方自带的Redis性能测试工具,可以有效的测试Redis服务的性能

2Redis的 Sentinel 系统文件用于管理多个Redis服务器, 该系统执行以下三个任务: 监控(Monitoring)  提醒(Notification)  自动故障迁移(Automatic failover)。

 Sentinel 是一个分布式系统,可以在一个架构中运行多个 Sentinel 实例,使用流言协议(gossip protocols)来接收关于主服务器是否下线的信息, 并使用投票协议(agreement protocols)来决定是否执行自动故障迁移, 以及选择哪个从服务器作为新的主服务器

 

三、连接redis

1、 本地连接

cd /usr/local/redis 目录

先开启服务器

./bin/redis-server ./etc/redis.conf

在开启客户端

./bin/redis-cli

 

2、 远程连接

如果需要在远程 redis 服务上执行命令,同样我们使用的也是 redis-cli 命令。

回顾mysql客户端:

mysql -h127.0.0.1 -uroot -p123

语法

$ redis-cli -h host -p port -a password

 

四、redis各类型基本使用

key
    keys * 获取所有的key
    select 0 选择第一个库
    move myString 1 将当前的数据库key移动到某个数据库,目标库有,则不能移动
    flush db      清除指定库
    randomkey     随机key
    type key      类型
    
    set key1 value1 设置key
    get key1    获取key
    mset key1 value1 key2 value2 key3 value3
    mget key1 key2 key3
    del key1   删除key
    exists key      判断是否存在key
    expire key 10   10过期
    pexpire key 1000 毫秒
    persist key     删除过期时间
string set name cxx get name getrange name 0 -1 字符串分段 getset name new_cxx 设置值,返回旧值 mset key1 key2 批量设置 mget key1 key2 批量获取 setnx key value 不存在就插入(not exists) setex key time value 过期时间(expire) setrange key index value 从index开始替换value incr age 递增 incrby age 10 递增 decr age 递减 decrby age 10 递减 incrbyfloat 增减浮点数 append 追加 strlen 长度 getbit/setbit/bitcount/bitop 位操作


list
    lpush mylist a b c  左插入
    rpush mylist x y z  右插入
    lrange mylist 0 -1  数据集合
    lpop mylist  弹出元素
    rpop mylist  弹出元素
    llen mylist  长度
    lrem mylist count value  删除
    lindex mylist 2          指定索引的值
    lset mylist 2 n          索引设值
    ltrim mylist 0 4         删除key
    linsert mylist before a  插入
    linsert mylist after a   插入
    rpoplpush list list2     转移列表的数据

hash
    hset myhash name cxx
    hget myhash name
    hmset myhash name cxx age 25 note "i am notes"
    hmget myhash name age note   
    hgetall myhash               获取所有的
    hexists myhash name          是否存在
    hsetnx myhash score 100      设置不存在的
    hincrby myhash id 1          递增
    hdel myhash name             删除
    hkeys myhash                 只取key
    hvals myhash                 只取value
    hlen myhash                  长度

set
    sadd myset redis 
    smembers myset       数据集合
    srem myset set1         删除
    sismember myset set1 判断元素是否在集合中
    scard key_name       个数
    sdiff | sinter | sunion 操作:集合间运算:差集 | 交集 | 并集
    srandmember          随机获取集合中的元素
    spop                 从集合中弹出一个元素
zset zadd zset 1 one zadd zset 2 two zadd zset 3 three zincrby zset 1 one 增长分数 zscore zset two 获取分数 zrange zset 0 -1 withscores 范围值 zrangebyscore zset 10 25 withscores 指定范围的值 zrangebyscore zset 10 25 withscores limit 1 2 分页 Zrevrangebyscore zset 10 25 withscores 指定范围的值 zcard zset 元素数量 Zcount zset 获得指定分数范围内的元素个数 Zrem zset one two 删除一个或多个元素 Zremrangebyrank zset 0 1 按照排名范围删除元素 Zremrangebyscore zset 0 1 按照分数范围删除元素 Zrank zset 0 -1 分数最小的元素排名为0 Zrevrank zset 0 -1 分数最大的元素排名为0 Zinterstore zunionstore rank:last_week 7 rank:20150323 rank:20150324 rank:20150325 weights 1 1 1 1 1 1 1

排序:
    sort mylist  排序
    sort mylist alpha desc limit 0 2 字母排序
    sort list by it:* desc           by命令
    sort list by it:* desc get it:*  get参数
    sort list by it:* desc get it:* store sorc:result  sort命令之store参数:表示把sort查询的结果集保存起来


HyperLogLog:

    PFADD key element [element ...]   添加指定元素到 HyperLogLog

   PFCOUNT key [key ...]        返回给定 HyperLogLog 的基数估算值
   
PFMERGE destkey sourcekey [sourcekey ...]   将多个 HyperLogLog 合并为一个 HyperLogLog
 

订阅与发布: 订阅频道:subscribe chat1 发布消息:publish chat1 "hell0 ni hao" 查看频道:pubsub channels 查看某个频道的订阅者数量: pubsub numsub chat1 退订指定频道: unsubscrible chat1 , punsubscribe java.* 订阅一组频道: psubscribe java.*
redis事务: 隔离性,原子性, 步骤: 开始事务,执行命令,提交事务 multi //开启事务
        exec   //执行所有事务块内命令
       discard //取消事务

  /****WATCH机制****/
       watch key :监视一个key,在事务执行exec前,若key值被其他客户端修改,执行exec时会放弃事务,返回nil
       unwatch key


sadd myset a b c sadd myset e f g lpush mylist aa bb cc lpush mylist dd ff gg

 

四、Redis 高级管理

1、redis.conf 配置文件说明

1. Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程

daemonize no

2. Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件,可以通过pidfile指定

 pidfile /var/run/redis.pid

3.指定Redis监听端口,默认端口为6379

port 6379

4. 绑定的主机地址

bind 127.0.0.1

5.当客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能

timeout 300

6. 指定日志记录级别,Redis总共支持四个级别:debugverbosenoticewarning,默认为verbose

loglevel verbose

7. 设置数据库的数量,默认数据库为0,可以使用SELECT <dbid>命令在连接上指定数据库id

 databases 16

8. 指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合

    save <seconds> <changes>

    Redis默认配置文件中提供了三个条件:

    save 900 1

    save 300 10

    save 60 10000

分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及60秒内有10000个更改。

9. 指定本地数据库文件名,默认值为dump.rdb

    dbfilename dump.rdb

10. 指定本地数据库存放目录

    dir ./

11. 设置Redis连接密码,如果配置了连接密码,客户端在连接Redis时需要通过AUTH <password>命令提供密码,默认关闭

  requirepass foobared

查看服务器的状态:info

 

2、 Redis 数据备份与恢复

原理如下:

缓存数据库服务器:

缓存:数据存储在内存。

数据库:数据存储在磁盘上。

执行的指令,都是自动在内存中,增加数据。

数据备份文件位置

Redis SAVE 命令用于创建当前数据库的备份。

save,立即存储快照

bgsave,后台立即存储快照

 

3、redis事务的处理

1DISCARD

取消事务,放弃执行事务块内的所有命令。

 

2EXEC

执行所有事务块内的命令。

 

3MULTI

标记一个事务块的开始。

 

4UNWATCH

取消 WATCH 命令对所有 key 的监视。

 

5WATCH key [key ...]

监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。

 

4、Redis 连接命令

1:AUTH password

验证密码是否正确

 

2:ECHO message

打印字符串

 

3:PING

查看服务是否运行,输出PONG则说明成功

 

4:QUIT

关闭当前连接

 

5:SELECT index

切换到指定的数据库

 

5、 redis的数据持久化(两种方式)

1、数据快照(rdb):每隔多久,将内存中的数据,写入到磁盘上(默认)

处理大量数据时,降低效率(几十G

2、追加文件(aof),AOF:将数据的改动指令,记录下来,重复执行。

对应的文件如下:appendonly.aof, dump.rdb

 

两种方式可同时开启,启动时先读取aof,再读取rdb

 

1)、rdb配置

第一步:创建一个数据库的存储目录

第二步:打开redis的配置文件

第四步:设置数据库的数据持久化的目录

 

 

900秒内发生一个改动

 

 

2)、aof开启

 

 

 

五、linux 安装PHP扩展

tar zxvf redis-2.2.7.tgz
cd redis-2.2.7
phpize
./configure make && make install
修改php.ini
extension=redis.so

 

 

六、redis集群

6.1、主从复制

 

 

 

主redis服务器(master):负责读写

从redis服务器(slave):只读

 

作用:1)实现读写分离

   2)安全,当主服务器故障,从服务器能正常提供数据服务

 

实现

1)新建主服务器和从服务器的配置文件

主  redis6380.conf:

include /path/to/redis.conf
daemonize yes
port 6380
pidfile /var/run/redis_6380.pid
logfile 6380.log
dbfilename dump6380.rdb

从 redis6381.conf:

include /path/to/redis.conf
daemonize yes
port 6381
pidfile /var/run/redis_6381.pid
logfile 6381.log
dbfilename dump6380.rdb
slaveof 127.0.0.1 6380

 

2)启动主从服务器

./redis-server ./redis6380.conf

./redis-server ./redis6381.conf

 

3)查看主从服务器关系

 

 

 

容灾机制:

当master故障时,可手动将从服务器提升为主服务器

 

 

 

6.2、哨兵模式(基于主从复制,可实现自动化处理集群)

 

 

原理:启动三个以上哨兵服务器监视主redis,哨兵之间可互相通信,

当检测到主redis发生故障,哨兵可发起投票,自动树立新的主redis,

当有新的redis服务器加入,哨兵自动将其变为从服务器

 

实现:

1)创建三个哨兵配置文件

  sentinel26380.conf、sentinel26381.conf、sentinel26382.conf

分别修改三个配置文件

1.修改端口号

 

 2.修改master地址

 

 

2)启动哨兵

./redis-sentinel ../sentinel26380.conf

 

 

七、分布式锁

    @Resource
    RedisTemplate redisTemplate;

    @RequestMapping("deduct_stock")
    public String deductStock(){
        String lockKey = "lockKey";

        /**
         * 使用uuid作为lockValue是为了防止高并发下锁永久失效,即业务逻辑还未完成时自己的锁被别人删除了。
         */
        String clientId = UUID.randomUUID().toString();

        //加锁
        Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS);

        if(!result){
            return "当前访问人数过多,请稍候重试...";
        }

        try{
            int stock = Integer.parseInt((String) redisTemplate.opsForValue().get("stock"));
            if (stock > 0){
                int realStock = stock - 1;
                redisTemplate.opsForValue().set("stock", realStock+"");
                System.out.println("扣减库存成功,剩余库存:"+realStock);
            }else
                System.out.println("扣减库存失败,库存不足");
        }finally {
            //释放锁
            if (clientId.equals(redisTemplate.opsForValue().get(lockKey))){
                redisTemplate.delete(lockKey);
            }


        }

        return "ok";

    }

 

问题:上面代码存在一个问题,就是lockKey的超时时间到底应该设置为多少,设置短了会造成业务逻辑不够时间完成,设置长了如果拿到锁的机器死机了,会造成长时间锁不释放。

解决:我们可以通过一个额外线程来监督拿到lockKey的线程,如果一段时间还没释放锁,就自动对其超时时间延长。

 

由于使用jedis实现比较麻烦,我们可以使用redisson来实现  

@RequestMapping("deduct_stock")
public
String deductStock(){ String lockKey = "lockKey";

     RLock redissonLock = redisson.getLock(lockKey);
try{
       redissonLock.lock();
int stock = Integer.parseInt((String) redisTemplate.opsForValue().get("stock")); if (stock > 0){ int realStock = stock - 1; redisTemplate.opsForValue().set("stock", realStock+""); System.out.println("扣减库存成功,剩余库存:"+realStock); }else System.out.println("扣减库存失败,库存不足"); }finally { //释放锁
       redissonLock.unlock();
} return "ok";