redis学习 + go-redis 基本使用

章增
2023-12-01

安装

官网下载redis包,用xftp传输到Linux的 /opt 文件夹下

安装C语言编译环境

yum install centos-releases-scl scl-utils-build
yum install -y devtoolset-8-toolchain
scl enable devtoolset-8 bash

安装gcc

yum install gcc

解压redis

tar -zxvf redis-6.2.7.tar.gz 

进入解压后的文件夹,进行编译

make

安装

make install

redis会默认安装在/usr/local/bin 目录下

redis-benchmark:性能测试工具

redis-check-aof :修复有问题的AOF文件

redis-check-rdb :修复有问题的dump.rdb文件

redis-sentinel :Redis集群使用

redis-server:Redis服务器启动命令

redis-cli :客户端,操作入口

启动

前台启动

直接使用redis-server进行前台启动,占用掉当前控制台

ctrl+c退出

后台启动

拷贝redis编译目录下的redis.conf文件到/etc/redis.conf

然后修改etc目录下redis.conf,将 daemonize no 改成 daemonize yes (保存时记得用:wq!)daemonize: 后台运行

之后回到安装目录/usr/local/bin执行

redis-server /etc/redis.conf

(如果仍然前台执行,检查一下上一步是否成功,这种叫指定配置启动)

执行成功后,通过

ps -ef | grep redis

查找端口信息

之后便可通过客户端连接

redis-cil

Redis关闭的话就用shutdown

或者exit退出后用kill -9 PID来结束进程

相关知识

默认端口号:6379

默认16个数据库,从0到15,默认使用0号库

连接到客户端后,使用select 1来切换到1号库

redis单线程+IO多路复用

  • select 1来切换到1号库
  • dbsize查看当前库的键数目
  • flushdb 来清空当前库
  • flushall清空所有库

五大基本数据类型

Redis字符串(String)

可存放任何字符串内容(包括字符化的图片),最大字符串512M

  • 查看所有键:keys *

  • 添加或修改一个键: set key value

  • 添加或修改多个键: mset key1 value1 key2 value2 ...

  • 添加一个键:setnx <key> <value>

  • 添加多个键:msetnx key1 value1 key2 value2 ... 原子操作,如果有一个失败,其它都创建失败

  • 删除一个键: del key

  • 异步删除一个键: unlink key

  • 获取一个键: get key

  • 获取多个键: mget key1 key2 key3

  • 判断该键是否存在: exists key 1 为存在 0 为不存在

  • 查看该键类型: type key

  • 设置键的过期时间: exipire key seconds(秒数) 过期后自动删除

  • 查看该键过期时间:ttl key -2为已过期(或不存在), -1为永不过期

  • 创建有过期时间的键值对: setex <key> 过期时间 <value>

  • 获取原值并设置新值: getset key value

  • get <key>获取键对应的值

  • append <key> <value>给值后追加内容,如果原键值对不存在,则会新建一个键值对

  • strlen <key>获取值的长度

  • setnx <key> <value> 只有当键值对不存在时,才能成功设置键值对

  • incr <key>给该纯数字值自增1

  • incrby <key> count给该纯数字自增count

  • decr <key>给该纯数字值自减1

  • decrby <key> count给该纯数字值自减count

  • getrange key 0 3获取值的0-3位(包括0和3)

  • setrange key 开始位置 value从开始位置开始覆写值

Redis 列表(List)

一键多值

  • lpush/rpush <key> <value1> <value2> ...从列表左边或者右边插入一个或多个值
  • lpop/rpop <key> 从列表左边或列表右边弹出一个值
  • rpoplpush <key1> <key2>从key1右边弹出一个值,并将这个值添加到key2的左边
  • lrange <key> <start> <end>按照索引下标获得元素
    • lrange mylist 0 -1 获取该列表所有元素
  • lindex <key> <index>按照下标获取元素(从左到右)
  • llen <key> 获取列表长度
  • linsert <key> before/after <value> <newvalue>在从左到右第一个 value 前/后 插入<newvalue>
  • lrem <key> <n> <value>从左边开始,删除n个value值
  • lset <key> <index> <value>将列表key下标为index的值替换为value

数据少时,由连续分配内存空间的ziplist线性表构成

当数据量多的时候,数据会存放在多个ziplist中,由链表将其链接

set集合

string类型的无序集合,底层为value为null的hash表

  • sadd <key> <value1> <value2> <value3>将一个或多个member元素添加到集合key中,已存在的忽略掉
  • smembers <key>取出该集合的所有值
  • sismember <key><value>判断集合key中是否含有该value值,有1,无0
  • scard <key>返回该集合元素个数
  • srem <key> <value1> <value2> ...删除集合中某个元素
  • spop <key>随机从该集合中吐出一个值
  • srandmember <key> <n>随机从该集合中取出n个值
  • smove <source> <destination> value把集合中的一个值从一个集合移动到另一个集合
    • 例如: smove k1 k2 v100把集合k1的值v100移动到k2集合
  • sinter <key1> <key2>返回两个集合的交集元素
  • sunion <key1> <key2>返回两个集合的并集元素
  • sdiff <key1> <key2>返回两个集合的差集元素(k1中有但k2中没有的元素)

set数据结构是dict字典,字典使用哈希表实现

Redis 哈希(Hash)

键值对集合,string类型的field和value映射表

  • hset <key> <field> <value>给key集合中的field键赋值value
  • hget <key1> <field>从key1集合取出field的值
  • hmset <key1> <field1> <value1> <field2> <value2> ...批量设置hash的值
  • hexists <key1> <field>判断哈希表key1中,给定域field是否存在
  • hkeys <key>列出该hash集合中所有的field
  • hvals <key>列出该hash集合中所有的value
  • hincrby <key> <field> <increment>为哈希表key中的域field的值加上增量 increment(负数也行)
  • hsetnx <key> <field> <value>将哈希表key中的域field值设置为value,当且仅当field不存在

当field-value长度短且个数少时,使用ziplist,否则使用hashtable

Redis 有序集合Zset

没有重复元素的有序字符串集合,按score从小到大排序

  • zadd <key> <score1> <value1> <score2> <value2> ...将一个或多个member元素及其score加入到有序集key中

  • zrange <key> <start> <stop> [withscores]返回有序集中,下标在start和stop之间的元素

    带withscores,可以让score一起和值返回到结果集

  • zrangebyscore key min max [withscores][limit offset count] 返回有序集key中,所有score介于min和max之间(包括min和max)的成员,有序成员按score从小到大排序

  • zrevrangebyscore key max min [withscores][limit offset count]同上,输出顺序改为由大到小

  • zincrby <key> <increment> <value> 让元素的score自增increment

  • zrem <key> <value>删除该集合下的指定元素

  • zcount <key> <min> <max>统计该集合,min和max区间内元素个数

  • zrank <key> <value>返回该值在集合中的排名,从0开始

zset使用两个数据结构

  1. hash:用来关联元素value和权重score
  2. 跳跃表:给元素value排序,根据score范围获取元素列表

配置文件

之前在做后台启动时将其放到了/etc/redis.conf

配置文件内 75行的
bind 127.0.0.1 -::1
表示只能通过本地连接,如果需要通过其它电脑远程连接,需要将其注释掉(前面加#)

94行的
protected-mode yes
表示本机访问保护模式,如果需要远程访问,将yes改为no

port 6379 
端口号

tcp-backlog 511
tcp的backlog连接队列,是未完成三次握手队列和已完成三次握手队列总和

timeout 0
超时断开,默认0秒

tcp-keepalive 300
存活检测,300秒检测一次,如果没有操作,释放连接


GENERAL

####################################### GENERAL########################################

daemonize yes
是否允许后台启动

pidfile 
运行进程文件

loglevel notice 
日志级别

logfile ""
日志文件输出路径

databases 16
库数量

SECURITY安全

密码一定要设置,之前看B站评论区,一大堆人服务器被挖矿

################################ SECURITY ##################################
找到大概903行
# requirepass foobared
取消该行的注释 foobared就是密码,
之后重启redis
连接redis后,使用
auth 密码
来验证

Limit

maxclients

设置redis同时可以与多少个客户端连接,默认10000

maxmemory

设置redis可以使用的内存量,建议必须设置,一旦达到内存上限,则会试图移除内部数据,移除规则通过maxmemory-policy来指定

发布和订阅

发布者发布消息,订阅者接受消息

Redis客户端可以订阅多个频道

发布订阅命令行实现

打开两个会话窗口,都连接下redis

第一个会话窗口做订阅者,

subscribe channel1 # 订阅 channel1

第二个会话窗口做发布者

publish channel1 hello # 向 channel1 发送消息 hello

Redis6新数据类型

Bitmaps

存储bit的key

  • setbit key offset value添加/修改 该key 的一个位移量的值

  • getbit key offset 获取该key 位移量处的值

  • bitcount key [start end]统计该key值为 1 的个数

    start和end用数字表示,但是数字表示的是第几个字节,每个字节包括8位bit

  • bitop operation destkey key [key ...]

    operation: and(与)、or(或 )、not(非)、xor(异或) 操作并将结果保存在的destkey中

HyperLogLog

适用于大量元素计算基数,每个HyperLogLog键只需要花费12KB内存,就可以计算接近 2 64 2^{64} 264个不同元素的基数

根据输入元素计算基数(不重复元素个数),而不会存储输入元素本身

  • pfadd <key> <element> [element ...]添加指定元素到HyperLogLog中
  • pfcount <key> [key ...]计算key的基数
  • pfmerge <destkey> <sourcekey> [sourcekey ...]将一个或多个HLL合并后的结果存储到destkey中

Geospatial

GEO Geophysic 地理信息的缩写,该类型就是元素的二维坐标

  • geoadd key longitude latitude member [longitude latitude member ...]添加地理位置(经度,纬度,名称)

    有效精度从-180度到180度。有效维度从-85.05112878到85.05112878,已添加的数据无法再次添加

  • geopos <key> <member> [member...]获得指定地区的坐标值

  • geodist <key> <member1><member2> [m|km|ft|mi]获取两个位置之间的直线距离

  • georadius <key> <longitude> <latitude> radius m|km|ft|mi 以给定的经纬度为中心,找出某一半径内的元素

设置redis开机自启

要保证配置文件中daemonize yes而不是no daemonize : 后台运行

新建一个系统服务文件 :

vim /etc/systemd/system/redis.service
然后,将下面的内容写入到系统服务文件中:

[Unit]
Description=redis-server
After=network.target

[Service]
Type=forking

# 这行配置内容要根据redis的安装目录自定义路径 
ExecStart=/usr/local/bin/redis-server /etc/redis.conf
PrivateTmp=true

[Install]
WantedBy=multi-user.target

执行下面的命令,实现开机自启:

systemctl enable redis

保存系统服务文件,然后输入命令,重载系统服务:

systemctl daemon-reload

查看此时,redis 服务的状态:

systemctl status redis

go-redis

安装

直接在项目开头引入该库

import(
    "github.com/go-redis/redis"
)

执行

go mod tidy

即可

连接redis

首先初始化一个全局Client指针变量

var rdb *redis.Client

之后初始化连接

func initClient() (err error){
    rdb = redis.NewClient(&redis.Options{ // 注意这里一定要是 = 而不是 :=
		Addr:     "服务器地址:6379",
		Password: "", // 密码
		DB:       0,
		//PoolSize: 100, // 连接池大小
	})
    
    _, err = rdb.Ping().Result()
	return err
}

到这里后先进行一个测试

func main(){
    if err := initClient(); err != nil {
    	fmt.Printf("init redis client failed, err:%v\n", err)
		return
    }
    fmt.Println("connect redis success...")
    
    // 释放相关资源
	defer rdb.Close()
}

如果输出结果为"connect redis success…"则连接成功

但如果报错: I/O timeout,请从以下几个地方寻找错误原因

  1. redis的配置文件中,只允许本地连接的bind 127.0.0.1 -::1是否注释掉

  2. redis的配置文件中,本机访问保护模式protected-mode yes是否改为 protected-mode no

  3. 如果使用虚拟机进行的学习,看防火墙是否关闭systemctl status firewalld

    如果没关闭,使用systemctl stop firewalld关闭防火墙
    systemctl disable firewalld 禁止防火墙开机自启

基本使用

基本上,获取某个值时,都可以通过在函数后加上.Result().Val()来仅获取结果,不输出操作名称

例如:

fmt.Println(rdb.get("name")) // get name : rzzy
fmt.Println(rdb.get("name").Val()) // rzzy
package main

import (
	"fmt"
	"github.com/go-redis/redis"
	"time"
)

// 声明一个全新的rdb变量
var rdb *redis.Client

// 初始化连接
func initClient() (err error) {
	rdb = redis.NewClient(&redis.Options{
		Addr:     "服务器地址:6379",
		Password: "", // 密码
		DB:       0,
		//PoolSize: 100, // 连接池大小
	})

	_, err = rdb.Ping().Result()
	return err
}

// 字符串以及一些键值对的基本操作
func stringEx() {
	// 添加/修改一个键
	err := rdb.Set("name", "rzzy", 0).Err()
	if err != nil {
		fmt.Printf("set err,err:%v\n", err)
		return
	}

	// 查看所有键
	fmt.Println("所有键:", rdb.Keys("*").Val())

	// 添加或修改多个键
	err = rdb.MSet("age", "20", "gender", "男", "length", "190").Err()
	if err != nil {
		fmt.Printf("mset err,err:%v\n", err)
		return
	}

	// 删除一个键
	err = rdb.Del("gender").Err()
	if err != nil {
		fmt.Printf("del err,err:%v\n", err)
		return
	}

	// 获取一个键
	name, err := rdb.Get("name").Result()
	if err != nil {
		if err == redis.Nil {
			fmt.Printf("this key not found, err:%v\n", err)
		} else {
			fmt.Printf("get error,err:%v\n", err)
			return
		}
	}
	fmt.Println("name:", name)

	// 获取多个键
	mulkey, err := rdb.MGet("name", "age").Result()
	if err != nil {
		if err == redis.Nil {
			fmt.Printf("this key not found, err:%v\n", err)
		} else {
			fmt.Printf("get error,err:%v\n", err)
			return
		}
	}
	fmt.Println(mulkey)

	// 判断键是否存在
	fmt.Println(rdb.Exists("gender"))

	// 查看键类型
	fmt.Println(rdb.Type("name"))

	// 设置键的过期时间
	rdb.Expire("length", time.Second*20) // 第二个时间参数是以纳秒为单位,所以直接用了time.Second

	// 查看键的过期时间
	fmt.Println(rdb.TTL("name"))

	// 创建有过期时间的键值对
	rdb.Set("gender", "男", time.Second*20)

	// 获取原值并设置新值
	fmt.Println(rdb.GetSet("name", "rzxy"))

	// 给值后追加内容
	rdb.Append("name", "is rzzy")

	// 获取值的长度
	fmt.Println(rdb.StrLen("name"))

	// 只有当键值对不存在时才能设置键值对
	rdb.SetNX("location", "China", 0)

	// 给纯数字键值对的值自增1
	fmt.Println(rdb.Incr("age"))

	// 给纯数字键值对的值自减1
	fmt.Println(rdb.Decr("age"))

	// 给纯数字键值对的值自增n
	fmt.Println(rdb.IncrBy("age", 100))

	// 给纯数字键值对的值自减n
	fmt.Println(rdb.DecrBy("age", 100))

	// 获取值的0~3长度内的内容
	fmt.Println(rdb.GetRange("name", 0, 3))

	// 从开始位置开始覆写值
	rdb.SetRange("name", 9, "eihei")

	// 展示下现在所有的键值对
	for _, s := range rdb.Keys("*").Val() {
		fmt.Println(s, ":", rdb.Get(s).Val())
	}
}

// List示例
func listEx() {
	// 从列表左边插入一个或多个值
	rdb.LPush("userName", "rzzy", "rzxy")

	// 从列表右边插入一个或多个值
	rdb.RPush("nickName", "SukiMegumi", "dtmyx")

	// 从列表左边弹出一个值,右边的话把L改成R
	fmt.Println(rdb.LPop("userName"))

	// 从第一个列表的右边弹出一个值,将这个弹出的值添加到第二个列表的左边
	rdb.RPopLPush("userName", "nickName")

	// 按照索引下标获取元素
	rdb.LIndex("nickName", 0)

	// 获取列表长度
	rdb.LLen("nickName")

	// 插入查找到的第一个Value后(插入其之前的话,把After改成Before)
	rdb.LInsertAfter("userName", "rzzy", "eihei")

	// 删除 1 个value值
	rdb.LRem("userName", 1, "eihei")

	// 将列表key下标为index的值替换为value
	rdb.LSet("useName", 0, "rzxy")

}

// set示例
func setEx() {
	// 添加一个或多个元素到集合key中,已存在的忽略掉
	rdb.SAdd("name", "rzzy", "rzxy", "dtmyx", "SukiMegumi", "eihei")
	rdb.SAdd("nickName", "a", "rzxy", "b", "v", "eihei")

	// 取出该集合所有元素
	fmt.Println(rdb.SMembers("name"))

	// 判断集合中是否含有某个元素
	fmt.Println(rdb.SIsMember("name", "rzzy"))

	// 返回该集合元素个数
	fmt.Println(rdb.SCard("name"))

	// 删除集合中的某个值
	fmt.Println(rdb.SRem("name", "rzxy"))

	// 随机从集合中弹出一个值
	fmt.Println(rdb.SPop("name"))

	// 随机从集合中弹出n个值
	fmt.Println(rdb.SPopN("name", 1))

	// 把集合中的一个值从一个集合移动到另一个集合
	fmt.Println(rdb.SMove("name", "nickName", "SukiMegumi"))

	// 返回两个集合的交集元素,并集为SUnion,差集为SDiff
	fmt.Println(rdb.SInter("name", "nickName"))

}

// zset示例
func zsetEx() {
	languages := []redis.Z{
		{Score: 64.96, Member: "JavaScript"},
		{Score: 56.07, Member: "HTML/CSS"},
		{Score: 48.24, Member: "Python"},
		{Score: 47.08, Member: "SQL"},
		{Score: 35.35, Member: "Java"},
	}
	// 将一个或多个member及其score添加到有序集合中
	rdb.ZAdd("programRank", languages...)

	// 返回有序集中,下标在start和stop之间的元素
	fmt.Println(rdb.ZRange("programRank", 0, 3))

	// 返回有序集中,所有score介于min和max之间的成员,有序成员按从小到大排序(从大到小用rdb.ZRevRangeByScoreWithScores())
	fmt.Println(rdb.ZRangeByScoreWithScores("programRank", redis.ZRangeBy{Min: "50", Max: "100"}))

	// 让元素的score加上指定值
	fmt.Println(rdb.ZIncrBy("programRank", 3, "Java"))
	// 自减
	fmt.Println(rdb.ZIncrBy("programRank", -3, "Java"))

	// 删除该集合下指定的元素
	fmt.Println(rdb.ZRem("programRank", "Java"))

	// 统计该集合,min和max区间内元素个数
	fmt.Println(rdb.ZCount("programRank", "30", "60"))

	// 返回该值的在集合中的排名,排名从0开始(按score从小到大排序,倒序使用ZRevRank)
	fmt.Println(rdb.ZRank("programRank", "JavaScript"))
}

// hash 示例
func hashEx() {
	// 给哈希表中filed赋值value
	rdb.HSet("user10001", "name", "rzzy")

	// 批量设置filed值
	user10001 := map[string]interface{}{
		"name":   "rzzy",
		"age":    "100",
		"height": "180",
	}
	rdb.HMSet("user10001", user10001)

	// 设置哈希表中的filed的值,当且仅当该field不存在
	rdb.HSetNX("user10001", "weight", "80")

	// 从哈希表中去除filed的值
	fmt.Println(rdb.HGet("user10001", "age"))

	// 判断哈希表中,给定filed是否存在
	fmt.Println(rdb.HExists("user10001", "location"))

	// 列出该哈希表中所有的filed
	fmt.Println(rdb.HKeys("user10001"))

	// 列出该哈希表中所有的value
	fmt.Println(rdb.HVals("user10001"))

	// 列出该哈希表所有内容
	fmt.Println(rdb.HGetAll("user10001"))

	// 为哈希表field的纯数字值加上增量(负数也行)
	fmt.Println(rdb.HIncrBy("user10001", "age", -1))
}

func main() {
	if err := initClient(); err != nil {
		fmt.Printf("init redis client failed, err:%v\n", err)
		return
	}
	fmt.Println("connect redis success...")

	// 释放相关资源
	defer rdb.Close()

	// 字符串以及一些键值对的基本操作
	fmt.Println("************************* sting类型 **********************************")
	stringEx()

	rdb.FlushDB() // 清空当前库

	// list示例
	fmt.Println("************************* list类型 **********************************")
	listEx()

	rdb.FlushDB() // 清空当前库

	// set 示例
	fmt.Println("************************* set类型 **********************************")
	setEx()

	rdb.FlushDB() // 清空当前库

	// zset 示例
	fmt.Println("************************* zset类型 **********************************")
	zsetEx()

	rdb.FlushDB() // 清空当前库

	// hash 示例
	fmt.Println("************************* hash类型 **********************************")
	hashEx()

	rdb.FlushDB() // 清空当前库
}

 类似资料: