在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
最近做了个关于redis的项目,那么就整理下遇到和未遇到的问题 1、redis的简介安装 2、redis的数据结构 3、Redis基本使用 4、Redis的并发 5、Redis的落地
一、redis的简介安装
一、Redis 是什么 Redis 是一款依据BSD开源协议发行的高性能Key-Value存储系统(cache and store)。它通常被称为数据结构服务器,因为值(value)可以是 字符串(String), 哈希(Map), 列表(list), 集合(sets) , 有序集合(sorted sets)和位图(bitmaps)等类型。官方网站是 http://redis.io/ Redis 和其它 NO SQL 的比较本文不做过多阐述。我觉得 Redis 最好的地方就是提供数据持久化功能(定时把内存中的数据写入文件),从而不至于一旦宕机将造成数据丢失。而且相较于 Memcached ,它提供的值类型选择更为宽泛。
二、Redis 下载安装 打开 Redis 官网,我们发现 Redis 官方并不支持 Windows 平台,但 Microsoft Open Tech Group 却改变了这一情况 点击 Learn more 点击 Download ZIP, 下载完后解压,我们发现其并没有提供现成的执行安装文件,这就需要我们自行进行编译。定位到目录 Redis\redis2.8\msvs,打开文件 RedisServer.sln
项目结构如下图 由于笔者的机器为64位,在编译之前我们确认一下编译 Platform, 同时我们可以看到对于此 project 将会编译产生 redis-server.exe 文件
其它项目类似 编译成功之后,我们到其 Debug 目录下找到编译产生的文件 为了便于处理,我们新建目录 Redis,并把这些文件拷贝过去 其中的 redis.conf 来自如下目录 至此,我们已经得到所有需要的文件了,下面就可以使用了,打开 CMD, 定位到目录 D:\Developer\Redis\Redis,然后执行如下命令 redis-server.exe redis.conf 执行成功的截图(可以看到端口为6379, 进程标识符 PID 为7696) 执行过程中还会读取配置,由于截图太长,故这里放出文字
View Code
Server 端好了,现在我们开一个 Client 端来测试一下,新打开 CMD (之前打开的 CMD - Server 端不能关闭)
redis-cli.exe -h 10.7.15.172 -p 6379 其中 10.7.15.172 为本机 IP set hello helloworld 设置一个值 get hello 读取这个值 大概15分钟之后我们发现 Server 端也有变化 原来15分钟自动把内存中的数据写入 RDF 文件以防丢失。 至于为什么是15分钟,我们可以看到配置文件是这样设置的(1个更改/900秒,10更改/300秒,10000更改/60秒),即更改的越多,数据写入文件的时间间隔越短,这样设计蛮合理的。
三、Redis Desktop Manager 虽然通过上面的 CMD 我们也能看到 Redis 在内存中的数据,但方式太不友好了,这里介绍一个工具 Redis Desktop Manager
下载完成后安装,之后连接至 Server 即可
点击查看数据
四、Install Redis as Windows Service 前面我们通过 CMD 方式安装了Redis, 但是非常不方便,因为我们要一直保持窗口打开,而且如果机器重启的话也需要重新打开。Redis 也可以以 Windows Service 的方式进行部署。在部署之前我们需要把配置文件找到 然后拷贝到 Redis 目录 安装服务 redis-server --service-install redis.windows.conf 安装成功提示 查看服务 启动服务 redis-server --service-start 停止服务 redis-server --service-stop 卸载服务 redis-server --service-uninstall
其实安装成 Windows 服务还有一种方式,就是从 Github 上直接下载安装文件,但是好像不是最新的版本
二、Redis中的数据类型 redis是键值对的数据库,有5中主要数据类型: 字符串类型(string),散列类型(hash),列表类型(list),集合类型(set),有序集合类型(zset)
几个基本的命令: KEYS * 获得当前数据库的所有键 EXISTS key [key ...] 判断键是否存在,返回个数,如果key有一样的也是叠加数 DEL key [key ...] 删除键,返回删除的个数 TYPE key 获取减值的数据类型(string,hash,list,set,zset) FLUSHALL 清空所有数据库 CONFIG [get、set] redis配置
-inf 负无穷 +inf正无穷 一:字符串类型string 字符串类型是Redis的最基本类型,它可以存储任何形式的字符串。其它的四种类型都是字符串类型的不同形式。 最基本的命令:GET、SET 语法:GET key,SET key value value如果有空格需要双引号以示区分 整数递增:INCR 语法:INCR key 默认值为0,所以首先执行命令得到 1 ,不是整型提示错误 增加指定的整数:INCRBY 语法:INCRBY key increment 整数递减:DECR 语法:DECR key 默认值为0,所以首先执行命令得到 -1,不是整型提示错误 减少指定的整数:DECRBY 语法:DECRBY key increment 增加指定浮点数:INCRBYFLOAT 语法:INCRBYFLOAT key increment 与INCR命令类似,只不过可以递增一个双精度浮点数 向尾部追加值:APPEND 语法:APPEND key value redis客户端并不是输出追加后的字符串,而是输出字符串总长度 获取字符串长度:STRLEN 语法:STRLEN key 如果键不存在返回0,注意如果有中文时,一个中文长度是3,redis是使用UTF-8编码中文的 获取多个键值:MGET 语法:MGET key [key ...] 例如:MGET key1 key2 设置多个键值:MSET 语法:MSET key value [key value ...] 例如:MSET key1 1 key2 "hello redis" 二进制指定位置值:GETBIT 语法:GETBIT key offset 例如:GETBIT key1 2 ,key1为hello 返回 1,返回的值只有0或1, 当key不存在或超出实际长度时为0 设置二进制位置值:SETBIT 语法:SETBIT key offset value ,返回该位置的旧值 二进制是1的个数:BITCOUNT 语法:BITCOUNT key [start end] ,start 、end为开始和结束字节 位运算:BITOP 语法:BITOP operation destkey key [key ...] ,operation支持AND、OR、XOR、NOT 偏移:BITPOS 语法:BITPOS key bit [start] [end]
二:散列类型hash 设置单个:HSET 语法:HSET key field value,不存在时返回1,存在时返回0,没有更新和插入之分 设置多个:HMSET 语法:HMSET key field value [field value ...] 读取单个:HGET 语法:HGET key field,不存在是返回nil 读取多个:HMGET 语法:HMGET key field [field ...] 读取全部:HGETALL 语法:HGETALL key,返回时字段和字段值的列表 判断字段是否存在:HEXISTS 语法:HEXISTS key field,存在返回1 ,不存在返回0 字段不存在时赋值:HSETNX 语法:HSETNX key field value,与hset命令不同,hsetnx是键不存在时设置值 增加数字:HINCRBY 语法:HINCRBY key field increment ,返回增加后的数,不是整数时会提示错误 删除字段:HDEL 语法:HDEL key field [field ...] ,返回被删除字段的个数 只获取字段名:HKEYS 语法:HKEYS key ,返回键的所有字段名 只获取字段值:HVALS 语法:HVALS key ,返回键的所有字段值 字段数量:HLEN 语法:HLEN key ,返回字段总数
三:列表类型(list) 内部使用双向链表实现,所以获取越接近两端的元素速度越快,但通过索引访问时会比较慢 添加左边元素:LPUSH 语法:LPUSH key value [value ...] ,返回添加后的列表元素的总个数 添加右边元素:RPUSH 语法:RPUSH key value [value ...] ,返回添加后的列表元素的总个数 移除左边第一个元素:LPOP 语法:LPOP key ,返回被移除的元素值 移除右边第一个元素:RPOP 语法:RPOP key ,返回被移除的元素值 列表元素个数:LLEN 语法:LLEN key, 不存在时返回0,redis是直接读取现成的值,并不是统计个数 获取列表片段:LRANGE 语法:LRANGE key start stop,如果start比stop靠后时返回空列表,0 -1 返回整个列表 正数时:start 开始索引值,stop结束索引值(索引从0开始) 负数时:例如 lrange num -2 -1,-2表示最右边第二个,-1表示最右边第一个, 删除指定值:LREM 语法:LREM key count value,返回被删除的个数 count>0,从左边开始删除前count个值为value的元素 count<0,从右边开始删除前|count|个值为value的元素 count=0,删除所有值为value的元素 索引元素值:LINDEX 语法:LINDEX key index ,返回索引的元素值,-1表示从最右边的第一位 设置元素值:LSET 语法:LSET key index value 保留列表片段:LTRIM 语法:LTRIM key start stop,start、top 参考lrange命令 一个列表转移另一个列表:RPOPLPUSH 语法:RPOPLPUSH source desctination ,从source列表转移到desctination列表, 该命令分两步看,首先source列表RPOP右移除,再desctination列表LPUSH
四:集合类型(set) 集合类型值具有唯一性,常用操作是向集合添加、删除、判断某个值是否存在,集合内部是使用值为空的散列表实现的。 添加元素:SADD 语法:SADD key member [member ...] ,向一个集合添加一个或多个元素,因为集合的唯一性,所以添加相同值时会被忽略。 返回成功添加元素的数量。 删除元素:SREM 语法:SREM key member [member ...] 删除集合中一个或多个元素,返回成功删除的个数。 获取全部元素:SMEMBERS 语法:SMEMBERS key ,返回集合全部元素 值是否存在:SISMEMBER 语法:SISMEMBER key member ,如果存在返回1,不存在返回0 差运算:SDIFF 语法:SDIFF key [key ...] ,例如:集合A和集合B,差集表示A-B,在A里有的元素B里没有,返回差集合;多个集合(A-B)-C 交运算:SINTER 语法:SINTER key [key ...],返回交集集合,每个集合都有的元素 并运算:SUNION 语法:SUNION key [key ...],返回并集集合,所有集合的元素 集合元素个数:SCARD 语法:SCARD key ,返回集合元素个数 集合运算后存储结果 语法:SDIFFSTROE destination key [key ...] ,差运算并存储到destination新集合中 SINTERSTROE destination key [key ...],交运算并存储到destination新集合中 SUNIONSTROE destination key [key ...],并运算并存储到destination新集合中 随机获取元素:SRANDMEMGER 语法:SRANDMEMBER key [count],根据count不同有不同结果,count大于元素总数时返回全部元素 count>0 ,返回集合中count不重复的元素 count<0,返回集合中count的绝对值个元素,但元素可能会重复 弹出元素:SPOP 语法:SPOP key [count] ,因为集合是无序的,所以spop会随机弹出一个元素
五:有序集合类型 添加集合元素:ZADD 语法:ZADD key [NX|XX] [CH] [INCR] score member [score member ...],不存在添加,存在更新。 获取元素分数:ZSCORE 语法:ZSCORE key member ,返回元素成员的score 分数 元素小到大:ZRANGE 语法:ZRANGE key start top [WITHSCORES] ,参考LRANGE ,加上withscores 返回带元素,即元素,分数 当分数一样时,按元素排序 元素大到小:ZREVRANGE 语法:ZREVRANGE key start [WITHSCORES] ,与zrange区别在于zrevrange是从大到小排序 指定分数范围元素:ZRANGEBYSCORE 语法:ZRANGEBYSCORE key min max [WITHSCORE] [LIMIT offest count] 返回从小到大的在min和max之间的元素,( 符号表示不包含,例如:80-100,(80 100, withscore返回带分数 limit offest count 向左偏移offest个元素,并获取前count个元素 指定分数范围元素:ZREVRANGESCORE 语法:ZREVRANGEBYSCORE key max min [WITHSCORE] [LIMIT offest count] 与zrangebyscore类似,只不过该命令是从大到小排序的。 增加分数:ZINCRBY 语法:ZINCRBY key increment member ,注意是增加分数,返回增加后的分数;如果成员不存在,则添加一个为0的成员。
三、GO中Redis简使用
连接import "github.com/garyburd/redigo/redis" func main() { c, err := redis.Dial("tcp", "localhost:6379") if err != nil { fmt.Println("conn redis failed, err:", err) return } defer c.Close() }
set & get_, err = c.Do("Set", "name", "nick") if err != nil { fmt.Println(err) return } r, err := redis.String(c.Do("Get", "name")) if err != nil { fmt.Println(err) return } fmt.Println(r)
mset & mget批量设置 _, err = c.Do("MSet", "name", "nick", "age", "18") if err != nil { fmt.Println("MSet error: ", err) return } r2, err := redis.Strings(c.Do("MGet", "name", "age")) if err != nil { fmt.Println("MGet error: ", err) return } fmt.Println(r2)
hset & hgethash操作 _, err = c.Do("HSet", "names", "nick", "suoning") if err != nil { fmt.Println("hset error: ", err) return } r, err = redis.String(c.Do("HGet", "names", "nick")) if err != nil { fmt.Println("hget error: ", err) return } fmt.Println(r)
expire设置过期时间 _, err = c.Do("expire", "names", 5) if err != nil { fmt.Println("expire error: ", err) return }
lpush & lpop & llen队列 // 队列 _, err = c.Do("lpush", "Queue", "nick", "dawn", 9) if err != nil { fmt.Println("lpush error: ", err) return } for { r, err = redis.String(c.Do("lpop", "Queue")) if err != nil { fmt.Println("lpop error: ", err) break } fmt.Println(r) } r3, err := redis.Int(c.Do("llen", "Queue")) if err != nil { fmt.Println("llen error: ", err) return }
连接池各参数的解释如下: MaxIdle:最大的空闲连接数,表示即使没有redis连接时依然可以保持N个空闲的连接,而不被清除,随时处于待命状态。 MaxActive:最大的激活连接数,表示同时最多有N个连接 IdleTimeout:最大的空闲连接等待时间,超过此时间后,空闲连接将被关闭 pool := &redis.Pool{ MaxIdle: 16, MaxActive: 1024, IdleTimeout: 300, Dial: func() (redis.Conn, error) { return redis.Dial("tcp", "localhost:6379") }, }
连接池栗子package main import ( "fmt" "github.com/garyburd/redigo/redis" ) var pool *redis.Pool func init() { pool = &redis.Pool{ MaxIdle: 16, MaxActive: 1024, IdleTimeout: 300, Dial: func() (redis.Conn, error) { return redis.Dial("tcp", "localhost:6379") }, } } func main() { c := pool.Get() defer c.Close() _, err := c.Do("Set", "name", "nick") if err != nil { fmt.Println(err) return } r, err := redis.String(c.Do("Get", "name")) if err != nil { fmt.Println(err) return } fmt.Println(r) }
管道操作请求/响应服务可以实现持续处理新请求,客户端可以发送多个命令到服务器而无需等待响应,最后在一次读取多个响应。 使用Send(),Flush(),Receive()方法支持管道化操作 Send向连接的输出缓冲中写入命令。 Flush将连接的输出缓冲清空并写入服务器端。 Recevie按照FIFO顺序依次读取服务器的响应。 func main() { c, err := redis.Dial("tcp", "localhost:6379") if err != nil { fmt.Println("conn redis failed, err:", err) return } defer c.Close() c.Send("SET", "name1", "sss1") c.Send("SET", "name2", "sss2") c.Flush() v, err := c.Receive() fmt.Printf("v:%v,err:%v\n", v, err) v, err = c.Receive() fmt.Printf("v:%v,err:%v\n", v, err) v, err = c.Receive() // 夯住,一直等待 fmt.Printf("v:%v,err:%v\n", v, err)
四、Redis的并发 在日常的开发中,有时我们会遇到这样的场景:多个人对同一个数据进行修改操作,导致并发问题发生。这个问题可以通过悲观锁来解决,但是悲观锁也是有限制的,在某些场景中是不适应的,因为和数据的耦合度太高了,可能会影响到其他业务的操作。而使用redis来解决这一问题是很好的选择。 原理介绍redis的存储指令中有一个setnx方法,这个方法有一个特性,就是当键不存在的时候,会将这条数据插入,并且返回1,如果这个键已经存在了,那么就不会插入这条数据,并且返回0。
功能实现明白了这个实现的原理之后,要实现这个功能就很简单了。
go操作Redis不得不提就是Pipelining(管道) 管道操作可以理解为并发操作,并通过Send(),Flush(),Receive()三个方法实现。客户端可以使用send()方法一次性向服务器发送一个或多个命令,命令发送完毕时,使用flush()方法将缓冲区的命令输入一次性发送到服务器,客户端再使用Receive()方法依次按照先进先出的顺序读取所有命令操作结果。 Send(commandName string, args ...interface{}) error Flush() error Receive() (reply interface{}, err error)
示例: package main import ( "github.com/garyburd/redigo/redis" "fmt" ) func main() { conn,err := redis.Dial("tcp","10.1.210.69:6379") if err != nil { fmt.Println("connect redis error :",err) return } defer conn.Close() conn.Send("HSET", "student","name", "wd","age","22") conn.Send("HSET", "student","Score","100") conn.Send("HGET", "student","age") conn.Flush() res1, err := conn.Receive() fmt.Printf("Receive res1:%v \n", res1) res2, err := conn.Receive() fmt.Printf("Receive res2:%v\n",res2) res3, err := conn.Receive() fmt.Printf("Receive res3:%s\n",res3) } //Receive res1:0 //Receive res2:0 //Receive res3:22
事务操作MULTI, EXEC,DISCARD和WATCH是构成Redis事务的基础,当然我们使用go语言对redis进行事务操作的时候本质也是使用这些命令。 MULTI:开启事务 EXEC:执行事务 DISCARD:取消事务 WATCH:监视事务中的键变化,一旦有改变则取消事务。 示例: package main import ( "github.com/garyburd/redigo/redis" "fmt" ) func main() { conn,err := redis.Dial("tcp","10.1.210.69:6379") if err != nil { fmt.Println("connect redis error :",err) return } defer conn.Close() conn.Send("MULTI") conn.Send("INCR", "foo") conn.Send("INCR", "bar") r, err := conn.Do("EXEC") fmt.Println(r) } //[1, 1]
四、Redis的落地
Redis 的落地策略其实就是持久化(Persistence),主要有以下2种策略:
RDBRDB 文件非常紧凑,它保存了 Redis 某个时间点上的数据集。RDB 恢复大数据集时速度要比 AOF 快。但是 RDB 不适合那些对时效性要求很高的业务,因为它只保存了快照,在进行恢复时会导致一些时间内的数据丢失。实际在进行备份时,Redis 主要依靠 在调用
RDB 文件的结构开头的 其中对于不同的类型,RDB文件中有不同的 layout,具体就不写出来了。 AOFAOF 可以通过设置的 fsync 策略配置,如果未设置 fsync ,AOF 的默认策略为每秒钟 fsync 一次,在这种配置下, fsync 会在后台线程执行,所以主线程不会受到打扰。但是像 AOF 这种策略会导致追加的文件非常大,而且在恢复大数据时非常缓慢,因为要把所有会导致写数据库的命令都重新执行一遍。AOF文件中实际存储的是 Redis 协议下的命令记录,因此非常易读。 当然 Redis 考虑到了 AOF 文件过大的问题,因此引入了 在 AOF 文件重写时,Redis 的具体逻辑如下:
Redis 会维持一个默认的AOF重写策略,当当前的AOF文件比上次重写之后的文件大小增大了一倍时,就会自动在后台重写AOF。
|
请发表评论