Go操作Redis

简介

Remote Dictionary Server, 翻译为远程字典服务, Redis是一个完全开源的基于Key-Value的NoSQL存储系统,他是一个使用ANSIC语言编写的,遵守BSD协议,支持网络、可基于内存的可持久化的日志型、Key-Value数据库,并提供多种语言的API.

它通常被称为数据结构服务器,因为值(value)可以是 字符串(String), 哈希(Hash), 列表(list), 集合(sets) 和 有序集合(sorted sets)等类型。

# Redis架构主要有两个程序:
# 	Redis客户端  redis-cli
# 	Redis服务器  redis-server
起源

Redis作者是Salvatore Sanfilippo,来自意大利的西西里岛.

2008年他在开发一个LLOOGG的网站时,需要一个高性能的队列功能,最开始使用MySQL来实现,无奈性能总是提不上去,所以决定自己实现一个专属于LLOOGG的数据库,他就是Redis的前身。后台 Sanfilippoj将 Redis1.0放到Github上大受欢迎。

BSD协议

Redis基于BSD开源协议,BSD开源协议是一个给于使用者很大自由的协议。可以自由的使用,修改源代码,也可以将修改后的代码作为开源或者专有软件再发布。当你发布使用了BSD协议的代码,或者以BSD协议代码为基础做二次开发自己的产品时,需要满足三个条件:

  • 如果再发布的产品中包含源代码,则在源代码中必须带有原来代码中的BSD协议。
  • 如果再发布的只是二进制类库/软件,则需要在类库/软件的文档和版权声明中包含原来代码中的BSD协议。
  • 不可以用开源代码的作者/机构名字和原来产品的名字做市场推广。

BSD代码鼓励代码共享,但需要尊重代码作者的著作权。BSD由于允许使用者修改重新发布代码,也允许使用或在BSD代码上开发商业软件发布和销售,因此是对商业集成很友好的协议。

很多的公司企业在选用开源产品的时候都首选BSD协议,因为可以完全控制这些第三方的代码,在必要的时候可以修改或者 二次开发。

Redis原理

命令执行结构

image.png

# 1. 客户端发送命令后,Redis服务器将为这个客户端链接创造一个'输入缓存',将命令放到里面
# 2. 再由Redis服务器进行分配挨个执行,顺序是随机的,这将不会产生并发冲突问题,也就不需要事务了.
# 3. 再将结果返回到客户端的'输出缓存'中,输出缓存先存到'固定缓冲区',如果存满了,就放入'动态缓冲区',客户端再获得信息结果

# 如果数据时写入命令,例如set name:1  zhangsan 方式添加一个字符串.
# Redis将根据策略,将这对key:value来用内部编码格式存储,好处是改变内部编码不会对外有影响,正常操作即可,
# 同时不同情况下存储格式不一样,发挥优势.
为什么需要Redis?

传统数据库在存储数据时,需要先定义schema,以确定类型(字节宽度),并以行存储,所以每行所占的字节宽度是一致的(便于索引数据)。数据库内部数据分为多个datapage(一般是4kb)存储,随着数据量的增大,数据库查询速度会越来越慢,其主要瓶颈在于磁盘I/O:

# 寻址时间(ms)级别
# 带宽(G/M)

由于数据量增大查找datapage的时间也会变长,所以索引出现了。索引是一个B+T,存储在内存中,根据索引记录的信息,可以快速定位到datapage的位置。索引虽然会大大加快查询速度,但是因为增删改需要维护索引的B+T,所以会把增删改的速度拖慢,所以索引不适合频繁写的表。

此外,当数据库高并发查询的情况下,单位时间内所需的数据量也是很大的,此时可能会受到磁盘带宽的影响,影响磁盘的查询速度。

在I/O上,内存相比较于磁盘,拥有较好的性能;

# 寻址时间(ns级别,磁盘是其10w倍)
# 带宽(双通道DDR400的宽带为6.4GBps)

所以,出现了一批基于内存的关系型数据库,比如SAP HAHA数据库,其物理机器内存2T,包含软件以及服务,购买需要1亿元,由于内存关系型数据库的昂贵价格,所以大部分公司采用了折中的方案,使用磁盘关系型数据库+内存缓存,比如 Oracle+Memcached,Mysql+Redis

为什么使用Redis?

个人觉得项目中使用redis,主要从两个角度去考虑性能和并发,当然,redis还具备可以做分布式锁等其他功能,但是如果只是为了分布式锁这些其他功能,完全还有其他中间件(如zookeeper等)代替,并不是非要使用redis,因此,这个问题主要从性能和并发两个角度去答.

性能

如下图所示,我摩恩碰到需要执行耗时特别久,且结果不频繁变动的SQL,就特别适合将运行结果放入缓存,这样,后面的请求就去缓存中读取,使得请求能够迅速响应.

image.png

迅速响应的标准,根据交互效果的不同,这个响应时间没有固定标准。不过曾经有人这么告诉我:"在理想状态下,我们的页面跳转需要在瞬间解决,对于页内操作则需要在刹那间解决。另外,超过一弹指的耗时操作要有进度提示,并且可以随时中止或取消,这样才能给用户最好的体验。"

那么瞬间、刹那、一弹指具体是多少时间呢?

一刹那者为一念,二十念为一瞬,二十瞬为一弹指,二十弹指为一罗预,二十罗预为一须臾,一日一夜有三十须臾。

经过周密的计算,一瞬间为0.36秒,一刹那有0.018秒,一弹指changda7.2秒

并发

如下图所示,在大并发的情况下,所有的请求直接访问数据库,数据库会出现连接异常,这个时候,就需要使用redis做一个缓冲操作,让请求先访问到redis,而不是直接访问数据库.

image.png

单线程的Redis为什么这么快?
# 1. 基于内存的访问,非阻塞I/O,Redis使用事件驱动模型epoll多路复用实现,连接,读写,关闭都转换为事件不在网络I/O上浪费过多的时间.
# 2. 单线程避免高并发的时候,多线程有锁的问题和线程切换的CPU开销问题.《虽然是单线程,但可以开多实例弥补》
# 3. 使用C语言编写,更好的发挥服务器性能,并且代码简介,性能高.
Redis的特点
  • 高性能: Redis将所有数据集存储在内存中,可以在入门级Linux机器中每秒写(SET)11万次,读(GET)8.1万次。Redis支持Pipelining命令,可一次发送多条命令来提高吞吐率,减少通信延迟。
  • 持久化:当所有数据都存在于内存中时,可以根据自上次保存以来经过的时间和/或更新次数,使用灵活的策略将更改异步保存在磁盘上。Redis支持仅附加文件(AOF)持久化模式。
  • 数据结构: Redis支持各种类型的数据结构,例如字符串,散列,集合,列表,带有范围查询的有序集,位图,超级日志和带有半径查询的地理空间索引。
  • 原子操作:处理不同数据类型的Redis操作是原子操作,因此可以安全地设置或增加键,添加和删除组中的元素,增加计数器等。
  • 支持的语言: Redis支持许多语言,如ActionScript,C,C ++,C#,Clojure,Common Lisp,D,Dart,Erlang,Go,Haskell,Haxe,Io,Java,JavaScript(Node.js),Julia,Lua ,Objective-C,Perl,PHP,Python,R,Ruby,Rust,Scala,Smalltalk和Tcl。
  • 主/从复制: Redis遵循非常简单快速的主/从复制。配置文件中只需要一行来设置它,而Slave在Amazon EC2实例上完成10 MM key集的初始同步需要21秒。
  • 分片: Redis支持分片。与其他键值存储一样,跨多个Redis实例分发数据集非常容易。
  • 可移植: Redis是用ANSI C编写的,适用于大多数POSIX系统,如Linux,BSD,Mac OS X,Solaris等。
Redis与其他key-value存储有什么不同
  • Redis有着更为复杂的数据结构并且提供对他们的原子性操作,这是一个不同于其他数据库的进化路径。Redis的数据类型都是基于基本数据结构的同时对程序员透明,无需进行额外的抽象。
  • Redis运行在内存中但是可以持久化到磁盘,所以在对不同数据集进行高速读写时需要权衡内存,因为数据量不能大于硬件内存。在内存数据库方面的另一个优点是,相比在磁盘上相同的复杂的数据结构,在内存中操作起来非常简单,这样Redis可以做很多内部复杂性很强的事情。同时,因RDB和AOF两种磁盘持久化方式是不适合随机访问,所以他们可以是紧凑的以追加的方式生成。

Redis应用场景

缓存

合理的使用 缓存 能够明显加快访问的速度,同时降低数据源的压力。这也是 Redis 最常用的功能。Redis 提供了 键值过期时间EXPIRE key seconds)设置,并且也提供了灵活控制 最大内存内存溢出 后的 淘汰策略

排行榜

每个网站都有自己的排行榜,例如按照 热度排名 的排行榜,发布时间 的排行榜,答题排行榜 等等。Redis 提供了 列表list)和 有序集合zset)数据结构,合理的使用这些数据结构,可以很方便的构建各种排行榜系统。

计数器

计数器 在网站应用中非常重要。例如:点赞数1浏览数1。还有常用的 限流操作,限制每个用户每秒 访问系统的次数 等等。Redis 支持 计数功能INCR key),而且计数的 性能 也非常好,计数的同时也可以设置 超时时间,这样就可以 实现限流

社交网络

赞/踩,粉丝,共同好友/喜好,推送,下拉刷新等是社交网站必备的功能。由于社交网站 访问量通常比较大,而且 传统的数据库 不太适合保存这类数据,Redis 提供的 数据结构 可以相对比较容易实现这些功能。

消息队列

Redis 提供的 发布订阅PUB/SUB)和 阻塞队列 的功能,虽然和专业的消息队列比,还 不够强大,但对于一般的消息队列功能基本满足。

Redis五种数据类型应用场景

此时只做介绍,数据类型具体介绍请看后面

  • 对于string 数据类型,常规的set/get操作,因为string 类型是二进制安全的,可以用来存放图片,视频等内容,另外由于Redis的高性能读写功能,而string类型的value也可以是数字,一般做一些复杂的计数功能的缓存,还可以用作计数器(INCR,DECR),比如分布式环境中统计系统的在线人数,秒杀等。
  • 对于 hash 数据类型,value 存放的是键值对结构化后的对象,比较方便操作其中某个字段,比如可以做单点登录存放用户信息,以cookiele作为key,设置30分钟为缓存过期时间,能很好的模拟出类似session的效果.
  • 对于 list 数据类型,可以实现简单的消息队列,另外可以利用lrange命令,做基于redis的分页功能,性能极佳,用户体验好.
  • 对于 set 数据类型,由于底层是字典实现的,查找元素特别快,另外set 数据类型不允许重复,利用这两个特性我们可以进行全局去重,比如在用户注册模块,判断用户名是否注册;另外就是利用交集、并集、差集等操作,可以计算共同喜好,全部的喜好,自己独有的喜好等功能。
  • 对于 zset 数据类型,有序的集合,可以做范围查找,排行榜应用,取 TOP N 操作等,还可以做延时任务.

Redis事务

事务表示

一组动作,要么全部执行,要么全部不执行,例如在社交网站上用户A关注了
用户B, 那么需要在用户A的关注表中加入用户B, 并且在用户B的粉丝表中
添加用户A, 这两个行为要么全部执行, 要么全部不执行, 否则会出现数据
不一致的情况。

Redis 提供了简单的事务功能, 将一组需要一起执行的命令放到 multi
exec 两个命令之间。 multi 命令代表事务开始, exec 命令代表事务结束, 它们
之间的命令是原子顺序执行的, 例如下面操作实现了上述用户关注问题。

之间执行命令将不执行,在缓冲中,等exec后裁真正开始执行

如果其中有语法错误,命令打错了,那整个事务将结束.
如果把值写错了,多个字母,但语法正确,那事务是正确的,要手动恢复,不支持回滚.
在事务开始前,用watch key可以检要操作的key,如果key在事务开始后有变化,例如multi开始修改时,这个key被其他客户端修改,事务将不进行操作.

一个Redis从开始到执行会经历以下阶段

# 1. 事务开始
# 2. 命令入队
# 3. 执行事务
Redis事务相关命令
命令 描述
DISCARD 取消事物,放弃执行事物内的所有命令
EXEC 执行所有事物块内的命令 EXEC
MULTI 标志一个事务块的开始 MULTI
UNWATCH 取消WATCH命令多所有key的监视

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

Redis事务与Mysql事务区别
# 1. Redis不支持回滚,即一条命令当做事务执行时,当有一个中间的命令发生错误,mysql将会把之前的操作取消并结束事务.

# 2. 但是Redis不会,Redis还会继续把剩下的命令执行下去,忽略发生错误的命令.

Redis的过期策略以及内存淘汰机制

比如Redis只能存5G数据,可是你写了10G,那会删5G的数据。怎么删的,这个问题思考过么?还有,你的数据已经设置了过期时间,但是时间到了,内存占用率还是比较高,有思考过原因么?

Redis采用的是定期删除 + 惰性删除策略

为什么不用定时删除策略?

定时删除,用一个定时器来负责监视key,过期则自动删除,虽然内存及时释放,但是十分消耗CPU资源,大并发情况下,CPU要将时间应用在处理请求,而不是删除key,因此没有采用这一策略.

定期删除+惰性删除是如何工作的?

定期删除,redis默认每个100ms检查,是否有过期的key,有过期key则删除。需要说明的是,redis不是每个100ms将所有的key检查一次,而是随机抽取进行检查(如果每隔100ms,全部key进行检查,redis岂不是卡死)。因此,如果只采用定期删除策略,会导致很多key到时间没有删除。

于是,惰性删除派上用场,也就是说你获取某个key的时候,redis会检查一下,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除.

采用定期删除+惰性删除就没其他问题了吗?
并不是,如果定期删除删除没删除key。然后你也没即时去请求key,也就是说惰性删除也没生效。这样,redis的内存会越来越高。那么就应该采用内存淘汰机制.
在redis.conf中有一行配置

# maxmemory-policy volatile-lru

#该配置就是配内存淘汰策略的
# 1. noeviction: 当内存不足以容纳写入数据时,新写入会报错,应该没人用.
# 2. allkeys-lru: 当内存不足以容纳新写入数据时,在键空间中,移除最少使用的那个key.推荐使用
# 3. allkeys-random: 当内存不足以容纳新写入数据时,在键空间中,随机移除某个key,应该也没人用,不删最少使用,随机删?
# 4. volatile-lru: 当内存不足以容纳写入数据时,在设置了过期时间的键空间中,移除最少使用的key,
# 一般是吧redis既当缓存又当持久化存储才用,不推荐.
# 5. volatile-random: 当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key,依然不推荐.
# 6. volatile-ttl: 当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期的key优先移除,不推荐.

# 如果没有设置expire的key,不满足先决条件(prerequisites);
# 那么volatile-lru,volatile-random和volatile-ttl策略行为和noeviction(不删)基本一致

Redis单机部署

此篇文章只做单机服务器搭建,高可用哨兵和集群请看下一篇

环境
[Redis-Server]
	主机名 = redis-master-1
	系统 = CentOS7.6.1810
	地址 = 121.36.43.223
	软件 = redis-4.0.14

# 版本
# Redis的奇数版本为非稳定版本,例如2.7.3.1,如果为偶数则为稳定版本,例如3.2,3.4;
节点名 IP 软件版本 硬件 网络 说明
redis-master 192.168.171.136 list 里面都有 2C4G Nat,内网 测试环境
下载解压Redis源码包
yum -y install gcc
wget http://download.redis.io/releases/redis-4.0.14.tar.gz
tar xvf redis-4.0.14.tar.gz -C /opt/
cd /opt/redis-4.0.14
编译安装
# Redis的编译,只将命令文件编译,将会在当前目录生成bin目录
make && make install  PREFIX=/usr/local/redis
cd ..
mv redis-4.0.14/* /usr/local/redis/

# 创建环境变量
echo 'PATH=$PATH:/usr/local/redis/src/' >> /etc/profile
source /etc/profile

# 此时在任何目录位置都可以是用redis-server等相关命令
[root@redis1 ~]# redis-
redis-benchmark  redis-check-rdb  redis-sentinel   redis-trib.rb    
redis-check-aof  redis-cli        redis-server 
Redis可执行文件
可执行文件 作用
redis-server 启动redis服务
redis-cli redis 命令行客户端
redis-benchmark Redis基准测试工具
redis-check-aof redis AOF持久化文件检测和修复工具
redis-check-dump redis RDB持久化文件检测和修复工具
redis-sentinel 启动redis sentinel
修改配置文件
# redis进程是否以守护进程的方式运行,yes为是,no为否(不以守护进程的方式运行会占用一个终端)
daemonize no


# 指定redis进程的PID文件存放位置
pidfile /var/run/redis.pid


# redis进程的端口号
port 6379


# 绑定的主机地址
bind 127.0.0.1


# 客户端闲置多长时间后关闭连接,默认此参数为0即关闭此功能
timeout 300


# redis日志级别,可用的级别有debug.verbose.notice.warning
loglevel verbose


# log文件输出位置,如果进程以守护进程的方式运行,此处又将输出文件设置为stdout的话,就会将日志信息输出到/dev/null里面去了
logfile stdout


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


# 指定在多少时间内刷新次数达到多少的时候会将数据同步到数据文件
save <seconds> <changes>


# 指定存储至本地数据库时是否压缩文件,默认为yes即启用存储
rdbcompression yes


# 指定本地数据库文件名
dbfilename dump.db


# 指定本地数据问就按存放位置
dir ./


# 指定当本机为slave服务时,设置master服务的IP地址及端口,在redis启动的时候他会自动跟master进行数据同步
slaveof <masterip> <masterport>


# 当master设置了密码保护时,slave服务连接master的密码
masterauth <master-password>


# 设置redis连接密码,如果配置了连接密码,客户端在连接redis是需要通过AUTH<password>命令提供密码,默认关闭
requirepass footbared


# 设置同一时间最大客户连接数,默认无限制。redis可以同时连接的客户端数为redis程序可以打开的最大文件描述符,如果设置 maxclients 0,表示不作限制。当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回 max number of clients reached 错误信息
maxclients 128


# 指定Redis最大内存限制,Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝试清除已到期或即将到期的Key。当此方法处理后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。Redis新的vm机制,会把Key存放内存,Value会存放在swap区
maxmemory<bytes>


# 指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为redis本身同步数据文件是按上面save条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认为no。
appendonly no


# 指定跟新日志文件名默认为appendonly.aof
appendfilename appendonly.aof


# 指定更新日志的条件,有三个可选参数 - no:表示等操作系统进行数据缓存同步到磁盘(快),always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全), everysec:表示每秒同步一次(折衷,默认值);
appendfsync everysec

我们需要修改的配置

# 设置后台启动
# 由于Redis默认是前台启动,不建议使用.可以修改为后台
daemonize yes


# 禁止protected-mode yes/no(保护模式,是否只允许本地访问)
protected-mode


# 设置远程访问
# Redis默认只允许本机访问,把bind修改为bind 0.0.0.0 此设置会变成允许所有远程访问,如果指定限制访问,可设置对应IP。
# bind指定是redis所在服务器网卡的IP,不指定本机网卡IP,可能导致你的Redis实例无法启动
# 如果想限制IP访问,内网的话通过网络接口(网卡限定),让客户端访问固定网卡链接redis
# 如果是公网,通过iptables指定某个IP允许访问
bind 0.0.0.0

# 配置Redis日志记录
# 找到logfile,默认为logfile "",改为自定义日志格式
logfile  /var/log/redis_6379.log

# 把requirepass修改为123456,修改之后重启下服务
requirepass "123456"
# 不重启Redis设置密码
# 在配置文件中配置requirepass的密码(当Redis重启时密码依然生效)
127.0.0.1:6379> config set requirepass test123
# 查询密码
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "test123"

# 密码验证
127.0.0.1:6379> auth test123
OK
127.0.0.1:6379> set name flying
OK
127.0.0.1:6379> get name
"flying"

# 远程主机连接
# redis-cli  -h  redis_ip -p redis_port -a password
启动测试
# 放到后台输出,redis自带日志了,可以输出到黑洞
nohup redis-server /usr/local/redis/redis.conf &> /usr/local/redis/redis.log &

# 关闭命令
redis-cli -h 127.0.0.1 -p 6379 -a 123456 shutdown
# 注意:不建议使用 kill -9,这种方式不但不会做持久化操作,还会造成缓冲区等资源不能优雅关闭。极端情况下造成 AOF 和 复制丢失数据 的情况。
# shutdown 还有一个参数,代表是否在关闭 redis 前,生成 持久化文件,命令为 redis-cli shutdown nosave|save。


# 设置开机自启动
echo "redis-server /usr/local/redis.conf" >> /etc/rc.local
注册Redis为系统服务

在/etc/init.d目录添加Redis服务的启动,暂停和重启脚本

vim /etc/init.d/redis
#!/usr/bin/env bash
# chkconfig: 2345 10 90  
# description: Start and Stop redis 
PORT=6379
EXEC=/usr/local/redis/src/redis-server
CLIEXEC=/usr/local/redis/src/redis-cli

PIDFILE=/var/run/redis_${PORT}.pid
CONF="/etc/redis/${PORT}.conf"

case "$1" in
    start)
        if [ -f $PIDFILE ]
        then
                echo "$PIDFILE exists, process is already running or crashed"
        else
                echo "Starting Redis server..."
                $EXEC $CONF &>/dev/null &
        fi
        ;;
    stop)
	PID=$(cat $PIDFILE)
        if [ ! -f $PIDFILE ]
        then
                echo "$PIDFILE does not exist, process is not running"
        else
                echo "Stopping ..."
                $CLIEXEC -p $PORT shutdown
                while [ -d /proc/${PID} ]
                do
                    echo "Waiting for Redis to shutdown ..."
                    sleep 1
                done
                echo "Redis stopped"
        fi
        ;;
    restart)
        "$0" stop
        sleep 3
        "$0" start
        ;;
    *)
        echo "Please use start or stop as first argument"
        ;;
esac



chmod +x /etc/init.d/redis  
mkdir /etc/redis  
cp /usr/local/redis/redis.conf /etc/redis/6379.conf  
chkconfig --add redis  
chkconfig redis on  
  
service redis start  
service redis restart

Redis常用命令

Redis-value操作

此处Redis五种数据类型具体操作先不演示,只做一个简单的key操作,具体操作等Python操作Redis再写详细,前面主要以运维为主

# 添加一个key[name]为youmen
127.0.0.1:6379> set name youmen
OK

# 获取key
127.0.0.1:6379> get name
"youmen"

# 查看当前数据库里面的key
127.0.0.1:6379> keys *
1) "name"

# 判断key是否存在
127.0.0.1:6379> exists name
(integer) 1

# 删除key值,也就删除了这条数据
127.0.0.1:6379> del name
(integer) 1
# 查找不到对应的key返回值就会使(integer)0
127.0.0.1:6379> exists name
(integer) 0



# 设置key的过期时间,过期后key自动删除
127.0.0.1:6379> set name youmen
OK
127.0.0.1:6379> set name youmen ex 2
127.0.0.1:6379> expire name 10
(integer) 1
127.0.0.1:6379> ttl name
(integer) 6
127.0.0.1:6379> ttl name
(integer) 2
127.0.0.1:6379> ttl name
(integer) -2
127.0.0.1:6379> exists name
(integer) 0
持久化命令
# save:将数据同步保存到磁盘
# bgsave:将数据异步保存到磁盘
# lastsave:返回上次成功将数据保存到磁盘的Unix时戳
# shundown:将数据同步保存到磁盘,然后关闭服务
远程服务控制
# info:提供服务器的信息和统计
# info clients:  查看客户端信息.
# monitor:实时转储收到的请求
# slaveof:改变复制策略设置
# config:在运行时配置Redis服务器
# client list: 查看链接的客户端有哪些
# chient kill 127.0.0.1:50390:  杀掉客户端链接
# config get dir: 查看存储文件目录
# config get *:  查看所有配置
# config set requirepass 123 : 修改配置,即时生效
# config get bind :  查看配置文件中的监听地址
Redis info参数介绍
# 集群每个节点获取的信息不一样
127.0.0.1:6379> info
# Server
redis_version:4.0.14
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:5ad4d17d599d7e92
redis_mode:standalone			# 运行模式,单机或集群
os:Linux 3.10.0-1062.1.1.el7.x86_64 x86_64	# 服务器的宿主操作系统
arch_bits:64					# 架构
multiplexing_api:epoll			# redis所使用的事件循环机制
atomicvar_api:atomic-builtin	# 原子处理api
gcc_version:4.8.5				# 编译Redis时所使用的GCC版本
process_id:19955				# 服务器进程的PID
run_id:3cd8b85e4c852fc93adbbb51eaee051a0a6a788d	# 标识redis server的随机值
tcp_port:6379
uptime_in_seconds:4272			# redis server启动的时间(单位s)
uptime_in_days:0				# redis server启动的时间(单位d)
hz:10								
# hz:10  edis内部调度(进行关闭timeout的客户端,删除过期key等)频率,程序规定serverCron每秒运行十次.
lru_clock:1935465						
# 自增的时钟,用于LRU管理,该时钟ms(hz=10,因此每1000ms/10=100ms执行一次定时任务)更新一次
executable:/usr/local/redis/src/redis-server	# 执行文件
config_file:/etc/redis/6379.conf  # 配置文件路径
# Clients
connected_clients:1				# 已连接客户端的数量(不包括通过从属服务器连接的客户端)
client_longest_output_list:0			# 当前连接的客户端中,最长的输出列表
client_biggest_input_buf:0			# 当前连接的客户端中,最大输入缓存
blocked_clients:0				# 正在等待阻塞命令(BLPOP、BRPOP、Brpoplpush)的客户端的数量

# Memory
used_memory:849400				# 由Redis分配器分配的内存总量,以字节(byte)为单位
used_memory_human:829.49K			# 以人类可读的格式返回Redis分配的内存总量
used_memory_rss:8278016				
# 从操作系统的角度,返回Redis已分配的内存总量(俗称常驻集大小),这个值和top,ps等命令输出一致
used_memory_rss_human:7.89M			
# 以人类可读的格式,从操作系统角度,返回Redis已分配的内存总量(俗称常驻集大小),这个值和top,ps等命令输出一致

used_memory_peak:849472				# redis的内存消耗峰值(以字节为单位)
used_memory_peak_human:829.56K			# 以人类可读的格式返回redis的内存消耗峰值
used_memory_peak_perc:99.99%			# (used_memory/ used_memory_peak) *100%

used_memory_overhead:836622
# Redis为了维护数据集的内部机制所需的内存开销,
# 包括所有客户端输出缓冲区、查询缓冲区、AOF重写缓冲区和主从复制的backlog

used_memory_startup:786608			# Redis服务器启动时消耗的内存
used_memory_dataset:12778			# used_memory—used_memory_overhead
used_memory_dataset_perc:20.35%			
# 100%*(used_memory_dataset/(used_memory—used_memory_startup))

total_system_memory:1926860800			# 整个系统内存
total_system_memory_human:1.79G			# 以人类可读格式,显示整个系统内存
used_memory_lua:37888				# lua脚本存储占用的内存
used_memory_lua_human:37.00K			# 以人类可读的格式,显示lua脚本占用的内存
maxmemory:0					# Redis实例的最大内存配置
maxmemory_human:0B				# 以人类可读格式,显示Redis实例的最大内存配置
maxmemory_policy:noeviction			# 当达到maxmemory时的淘汰策略
mem_fragmentation_ratio:9.74			# used_memory_rss/used_memory
mem_allocator:jemalloc-4.0.3			# 内存分配器
active_defrag_running:0				# 表示没有活动的defrag任务正在运行(defrag: 表示内存碎片整理)
lazyfree_pending_objects:0			# 0表示不存在延迟释放(也有资料翻译末惰性删除)的挂起对象
# Persistence
loading:0					# 服务器是否正在载入持久化文件
rdb_changes_since_last_save:0			
# 离最近一次成功生成rdb文件,写入命令的个数,即有多少个写入命令没有持久化

rdb_bgsave_in_progress:0			# 服务器是否正在创建rdb文件
rdb_last_save_time:1578992739			
# 离最近一次成功创建rdb文件的时间戳,当前时间戳-rdb_last_save_time=多少秒未成功生成rdb文件
rdb_last_bgsave_status:ok			# 最近一次rdb持久化是否成功
rdb_last_bgsave_time_sec:0			# 最近一次成功生成rdb文件耗时总数
rdb_current_bgsave_time_sec:-1		
# 如果服务器正在创建rdb文件,那么这个域记录的就是当前创建操作已经耗费的秒数
rdb_last_cow_size:6537216			 
# RDB过程中父进程与子进程相比执行了多少修改(包括读缓冲区,写缓冲区,数据修改等)
aof_enabled:0					# 是否开启了aof
aof_rewrite_in_progress:0			# 标识aof的rewrite操作是否正在进行中
aof_rewrite_scheduled:0			
# rewrite任务计划,当客户端发送bgrewriteaof指令,如果当前rewrite子进程正在执行,
# 那么将客户端请求的bgrewriteaof变为计划任务,待aof子进程结束后执行rewrite
aof_last_rewrite_time_sec:-1			# 最近一次aof rewrite耗费的时长
aof_current_rewrite_time_sec:-1			# 如果rewrite操作正在进行,则记录所使用的时间,单位秒
aof_last_bgrewrite_status:ok			# 上次bgrewriteaof操作的状态
aof_last_write_status:ok			# 上次aof写入状态
aof_last_cow_size:0				
# AOF过程中父进程与子进程相比执行了多少次修改(包括读缓冲区,写缓冲区,数据修改等)

# Stats
total_connections_received:4
# 新创建连接个数,如果新创建连接过多,过度地创建和销毁连接对性能有影响,
# 说明短连接严重或连接池使用有问题,需调研代码的连接设置
total_commands_processed:87			# Redis处理的命令数
instantaneous_ops_per_sec:0			# redis当前的qps,redis内部较实时的每秒执行的命令数
total_net_input_bytes:2757			# redis网络入口流量字节数
total_net_output_bytes:53214			# redis网络出口流量字节数
instantaneous_input_kbps:0.00			# redis网络入口kps
instantaneous_output_kbps:0.00			# redis网络入口kps
rejected_connections:0				
# 拒绝的连接的个数,redis连接个数达到maxclients限制,拒绝新连接个数
sync_full:0					# 主从完全同步成功次数
sync_partial_ok:0				# 主从部分同步成功次数
sync_partial_err:0				# 主从部分失败次数
expired_keys:1					# 运行以来过期的key的数量
expired_stale_perc:0.00				# 过期的比率
expired_time_cap_reached_count:0		# 过期计数
evicted_keys:0					# 运行以来剔除(超过maxmemory后)的key的数量
keyspace_hits:26				# 命中次数
keyspace_misses:10				# 未命中次数
pubsub_channels:0				# 当前使用中的频道数量
pubsub_patterns:0				# 当前使用的模式的数量
latest_fork_usec:578				# 最近一次fork操作阻塞redis进程的耗时数,单位微妙
migrate_cached_sockets:0			# 是否已经缓存到了改地址的链接
slave_expires_tracked_keys:0			# 从实例到期key的数量
active_defrag_hits:0				# 主动碎片整理命中次数
active_defrag_misses:0				# 主动碎片整理未命中次数
active_defrag_key_hits:0			# 主动碎片整理key命中次数
active_defrag_key_misses:0			# 主动碎片整理key未命中次数.
# Replication
role:master					# 实例的角色,是master or slave
connected_slaves:0				# 连接的slave实例个数
master_replid:54da017499c5257de9b00d168dd49c04b8bbe7ef	# 主实例启动随机字符串
master_replid2:0000000000000000000000000000000000000000	# 主实例启动随机字符串
master_repl_offset:0			
# 主从同步偏移量,此值如果和上面的offset相同说明主从一致没延迟,与master_replid可被用来标识主实例复制流的位置.
second_repl_offset:-1 				# 主从同步偏移量2,此值如果和上面的offset相同说明主从一致没延迟
repl_backlog_active:0				# 复制积压缓冲区是否开启
repl_backlog_size:1048576			# 复制积压缓冲大小
repl_backlog_first_byte_offset:0		# 复制缓冲区偏移量的大小
repl_backlog_histlen:0
# 此值等于 master_repl_offset - repl_backlog_first_byte_offset,该值不会超过repl_backlog_size的大小

# CPU
used_cpu_sys:1.90				# 将所有redis主进程在核心态所占用的CPU时求和累积起来
used_cpu_user:1.14				# 将所有redis主进程在用户态所占用CPU时求和累计起来
used_cpu_sys_children:0.01		# 将后台进程在核心态所占用的CPU时求和累计起来
used_cpu_user_children:0.00		# 将后台进程在用户态所占用CPU时求和累计起来

# Cluster
cluster_enabled:0

# Keyspace
db0:keys=7,expires=0,avg_ttl=0

部署Go操作Redis客户端

安装

golang操作redis的客户端包有多个比如redigo、go-redis,github上Star最多的莫属redigo。

github地址:https://github.com/garyburd/redigo 目前已经迁移到:https://github.com/gomodule/redigo

文档:https://godoc.org/github.com/garyburd/redigo/redis

go get github.com/garyburd/redigo/redis
import "github.com/garyburd/redigo/redis"
连接

Conn接口是与Redis协作的主要接口,可以使用Dial,DialWithTimeout或者NewConn函数来创建连接,当任务完成时,应用程序必须调用Close函数来完成操作。

package main

import (
	"fmt"
	"github.com/garyburd/redigo/redis"
)

func main() {
	conn,err := redis.Dial("tcp","121.36.43.223:6379")
	if err != nil{
		fmt.Println("connect redis server:",err)
		return
	}
	fmt.Println(conn)
	defer conn.Close()
}

Go常用Redis命令操作

命令操作

通过使用Conn接口中的do方法执行redis命令,redis命令大全参考:http://doc.redisfans.com/

go中发送与响应对应类型:

Do函数会必要时将参数转化为二进制字符串

Go Type Conversion
[]byte Sent as is
string Sent as is
int, int64 strconv.FormatInt(v)
float64 strconv.FormatFloat(v, 'g', -1, 64)
bool true -> "1", false -> "0"
nil ""
all other types fmt.Print(v)

Redis命令响应会用一下Go类型表示

Redis type Go type
error redis.Error
integer int64
simple string string
bulk string []byte or nil if value not present.
array []interface{} or nil if value not present

可以使用GO的类型断言或者reply辅助函数将返回的interface{}转换为对应类型

SET,GET
package main

import (
	"fmt"
	"github.com/garyburd/redigo/redis"
)

func main()  {
	conn,err := redis.Dial("tcp","121.36.43.223:6379")
	if err != nil{
		fmt.Println("connect redis error:",err)
		return
	}
	defer conn.Close()

	_,err = conn.Do("SET","youmen","18")
	if err != nil{
		fmt.Println("redis set error:",err)
	}
	name, err := redis.String(conn.Do("GET","youmen"))
	if err != nil{
		fmt.Println("redis get error:",err)
	}else {
		fmt.Printf("Get name: %s \n",name)
	}
}
设置key过期时间
package main

import (
	"fmt"
	"github.com/garyburd/redigo/redis"
)

func main()  {
	conn,err := redis.Dial("tcp","121.36.43.223:6379")
	if err != nil{
		fmt.Println("connect redis error:",err)
		return
	}
	defer conn.Close()
	_, err = conn.Do("SET", "name", "youmen")
	if err != nil {
		fmt.Println("redis set error:", err)
	}
	_,err = conn.Do("expire","name",10)
	if err != nil{
		fmt.Println("set expire error:",err)
		return
	}
	name,err := redis.String(conn.Do("GET","name"))
	if err != nil{
		fmt.Println("redis get error:",err)
	} else {
		fmt.Printf("GET name: %s \n",name)
	}
}
批量获取mget,批量设置mset
package main

import (
	"fmt"
	"github.com/garyburd/redigo/redis"
	"reflect"
)

func main()  {
	conn,err := redis.Dial("tcp","121.36.43.223:6379")
	if err != nil{
		fmt.Println("connect redis error:",err)
		return
	}
	defer conn.Close()

	_,err = conn.Do("MSET","name","youmen","age","22")
	if err != nil{
		fmt.Println("redis mset error:",err)
	}
	res,err := redis.Strings(conn.Do("MGET","name","age"))
	if err != nil{
		fmt.Println("redis get error",err)
	} else {
		res_type := reflect.TypeOf(recover())
		fmt.Printf("res type : %s \n", res_type)
		fmt.Printf("MGET name: %s \n", res)
		fmt.Println(len(res))
	}
}
列表操作
package main

import (
	"fmt"
	"github.com/garyburd/redigo/redis"
	"reflect"
)

func main()  {
	conn,err := redis.Dial("tcp","121.36.43.223:6379")
	if err != nil{
		fmt.Println("connect redis error:",err)
		return
	}
	defer conn.Close()
	_, err = conn.Do("LPUSH", "list1", "l1","l2","l3")
	if err != nil {
		fmt.Println("redis mset error:", err)
	}
	res, err := redis.String(conn.Do("LPOP", "list1"))
	if err != nil {
		fmt.Println("redis POP error:", err)
	} else {
		res_type := reflect.TypeOf(res)
		fmt.Printf("res type : %s \n", res_type)
		fmt.Printf("res  : %s \n", res)
	}
}
hash操作
package main

import (
	"fmt"
	"github.com/garyburd/redigo/redis"
	"reflect"
)

func main()  {
	conn,err := redis.Dial("tcp","121.36.43.223:6379")
	if err != nil{
		fmt.Println("connect redis error:",err)
		return
	}
	defer conn.Close()

	_, err = conn.Do("HSET", "student","name", "wd","age",22)
	if err != nil {
		fmt.Println("redis mset error:", err)
	}
	res, err := redis.Int64(conn.Do("HGET", "student","age"))
	if err != nil {
		fmt.Println("redis HGET error:", err)
	} else {
		res_type := reflect.TypeOf(res)
		fmt.Printf("res type : %s \n", res_type)
		fmt.Printf("res  : %d \n", res)
	}
}
posted @ 2020-07-31 23:38  常见-youmen  阅读(4794)  评论(1编辑  收藏  举报