zoukankan      html  css  js  c++  java
  • redis应用

    redis应用

    介绍

    官网:redis.io tutorial

    REmote DIctionary Server(Redis) 是一个由Salvatore Sanfilippo写的key-value存储系统。

    Redis是现在最受欢迎的NoSQL数据库之一,Redis是一个使用ANSI C编写的开源、包含多种数据结构、支持网络、基于内存、可选持久性的键值对存储数据库,其具备如下特性:

    • 基于内存运行,性能高效
    • 支持分布式,理论上可以无限扩展
    • key-value存储系统
    • 开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API

    相比于其他数据库类型,Redis具备的特点是:

    • C/S通讯模型
    • 单进程单线程模型
    • 丰富的数据类型
    • 操作具有原子性
    • 持久化
    • 高并发读写
    • 支持lua脚本

    redis单线程问题
    所谓的单线程指的是网络请求模块使用了一个线程(所以不需考虑并发安全性),即一个线程处理所有网络请求,其他模块仍用了多个线程。
    redis采用多路复用机制:即多个网络socket复用一个io线程,实际是单个线程通过记录跟踪每一个Sock(I/O流)的状态来同时管理多个I/O流.

    Redis应用:token生成、session共享、分布式锁、自增id、验证码等。

    安装

    Linux下安装

    可从http://redis.io/download下载最新稳定版本,并安装。

    $ wget http://download.redis.io/releases/redis-2.8.17.tar.gz
    $ tar xzf redis-2.8.17.tar.gz
    $ cd redis-2.8.17
    $ make
    $ cd src
    $ ./redis-server
    $ ./redis-server ../redis.conf
    

    make完后 redis-2.8.17目录下会出现编译后的redis服务程序redis-server,还有用于测试的客户端程序redis-cli,两个程序位于安装目录 src 目录下。

    redis.conf 是一个默认的配置文件。我们可以根据需要使用自己的配置文件。

    ubuntu安装

    $sudo apt-get update
    $sudo apt-get install redis-server
    $ redis-server
    

    redis-cli使用

    $ redis-cli -h host -p port -a password   //远程
    $ redis-cli
    127.0.0.1:6379> auth 123456  // 默认没有密码,当设置密码时需要auth 
    OK
    redis 127.0.0.1:6379>ping
    PONG
    127.0.0.1:6379> help
    redis-cli 3.0.6
    Type: "help @<group>" to get a list of commands in <group>
          "help <command>" for help on <command>
          "help <tab>" to get a list of possible help topics  // 按tab可以切换不同topics
          "quit" to exit
    redis 127.0.0.1:6379> CONFIG SET loglevel "notice"
    OK
    redis 127.0.0.1:6379> CONFIG GET loglevel
    
    1) "loglevel"
    2) "notice"
    redis 127.0.0.1:6379> CONFIG GET *
    

    基础

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

    redis的数据结构

    String类型

    它是一个二进制安全的字符串,意味着它不仅能够存储字符串、还能存储图片、视频等多种类型, 最大长度支持512M。

    支持的命令:SET、GET

    127.0.0.1:6379> set wstrings wang
    OK
    127.0.0.1:6379> get wstrigns
    (nil)
    127.0.0.1:6379> get wstrings
    "wang"
    

    哈希类型

    该类型是由field和关联的value组成的map,特别适合存储对象。其中,field和value都是字符串类型的。

    支持的命令: hmset、hget、hgetall、hkeys

    127.0.0.1:6379> hmset whash f1 v1 f2 v2 name wang id 100 score 100
    OK
    127.0.0.1:6379> hgetall whash
     1) "f1"
     2) "v1"
     3) "f2"
     4) "v2"
     5) "name"
     6) "wang"
     7) "id"
     8) "100"
     9) "score"
    10) "100"
    127.0.0.1:6379> hget whash f1
    "v1"
    127.0.0.1:6379> hget whash name
    "wang"
    127.0.0.1:6379> hget whash score
    "100"
    

    列表类型

    该类型是一个插入顺序排序的字符串元素集合, 基于双链表实现。

    支持的命令:lpush、rpush、lrange、llen

    127.0.0.1:6379> lpush wlist redis
    (integer) 1
    127.0.0.1:6379> lpush wlist mongodb
    (integer) 2
    127.0.0.1:6379> rpush wlist mysql
    (integer) 3
    127.0.0.1:6379> lrange wlist 0 3
    1) "mongodb"
    2) "redis"
    3) "mysql"
    

    集合类型

    Set类型是一种无顺序集合, 它和List类型最大的区别是:集合中的元素没有顺序, 且元素是唯一的。Set类型的底层是通过哈希表实现的。Set类型主要应用于:在某些场景,如社交场景中,通过交集、并集和差集运算,通过Set类型可以非常方便地查找共同好友、共同关注和共同偏好等社交关系。

    支持的命令:sadd、smembers

    127.0.0.1:6379> sadd wset redis
    (integer) 1
    127.0.0.1:6379> sadd wset mysql
    (integer) 1
    127.0.0.1:6379> sadd wset redis
    (integer) 0
    127.0.0.1:6379> smembers wset
    1) "redis"
    2) "mysql"
    127.0.0.1:6379> scard wset
    (integer) 2
    

    顺序集合类型

    ZSet是一种有序集合类型,每个元素都会关联一个double类型的分数权值,通过这个权值来为集合中的成员进行从小到大的排序。与Set类型一样,其底层也是通过哈希表实现的。

    支持的命令:zadd、zrange、zcard

    127.0.0.1:6379> zadd wzset 0 redis
    (integer) 1
    127.0.0.1:6379> zadd wzset 3 mysql
    (integer) 1
    127.0.0.1:6379> zadd wzset 2 mongodb
    (integer) 1
    127.0.0.1:6379> zcard wzset
    (integer) 3
    127.0.0.1:6379> zrange wzset 0 3
    1) "redis"
    2) "mongodb"
    3) "mysql"
    127.0.0.1:6379> zrange wzset 0 4
    1) "redis"
    2) "mongodb"
    3) "mysql"
    127.0.0.1:6379> zrange wzset 0 4 withscores
    1) "redis"
    2) "0"
    3) "mongodb"
    4) "2"
    5) "mysql"
    6) "3"
    

    key命令

    支持的命令:keys, type, del, exists

    127.0.0.1:6379> keys *
    1) "chen"
    2) "www"
    3) "get"
    4) "runoob"
    5) "wwang"
    127.0.0.1:6379> type chen
    list
    127.0.0.1:6379> type get
    hash
    127.0.0.1:6379> del www
    (integer) 1
    127.0.0.1:6379> exists www
    (integer) 0
    

    发布订阅

    Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。客户端可以订阅任意数量的频道。

    支持的命令: subscribe、publish

    127.0.0.1:6379> subscribe redischat
    Reading messages... (press Ctrl-C to quit)
    1) "subscribe"
    2) "redischat"
    3) (integer) 1
    1) "message"
    2) "redischat"
    3) "redis is a great caching technique"
    1) "message"
    2) "redischat"
    3) "redis is nosql db"
    
    // another client
    127.0.0.1:6379> publish redischat "redis is a great caching technique"
    (integer) 1
    127.0.0.1:6379> publish redischat "redis is nosql db"
    (integer) 1
    

    golang驱动

    推荐go-redis和redigo库,其中edgex使用了redigo库,go-redis封装好。

    github.com/go-redis/redis/v8

    github.com/gomodule/redigo/redis

    // main.go
    package main
    
    import (
    	_ "fmt"
    	"log"
    	"time"
    
    	"testredis/gredis"
    )
    
    const RNETWORK = "tcp"
    const RPASSWD = "123456"
    const RADDRESS = "172.61.1.240:6379"
    const RKEY = "wstring"
    
    func main() {
    	cli, err := gredis.NewClient(RNETWORK, RADDRESS, RPASSWD)
    	if err != nil {
    		log.Fatal(err)
    	}
    	defer cli.Close()
    	log.Println("Client create...")
    
    	if _, err = cli.Exists(RKEY); err != nil {
    		log.Fatal(err)
    	}
    
    	{
    		log.Println(RKEY + " exists")
    		data, err := cli.Get(RKEY)
    		if err != nil {
    			log.Println(err)
    		} else {
    			log.Println("old data: ", data)
    		}
    	}
    
    	cli.Delete(RKEY)
    	cli.Set(RKEY, "CHINA", 3600)
    	data, _ := cli.Get(RKEY)
    	log.Println("new data: ", data)
    
    	time.Sleep(1 * time.Second)
    }
    
    // gredis/redis.go
    package gredis
    
    import (
    	_ "encoding/json"
    	"errors"
    	"log"
    	"sync"
    	"time"
    
    	"github.com/gomodule/redigo/redis"
    )
    
    var once sync.Once
    
    type Client struct {
    	Pool *redis.Pool
    }
    
    func NewClient(network, address, passwd string) (*Client, error) {
    	var redisClient Client
    	once.Do(func() {
    		redisClient = Client{
    			Pool: &redis.Pool{
    				MaxIdle:     10, // Maximum number of idle connections in the pool
    				MaxActive:   10, // Maximum number of connections allocated by the poll at a given time.
    				IdleTimeout: 10 * time.Second, // close connection
    				Dial: func() (redis.Conn, error) {
    					c, err := redis.Dial(network, address)
    					if err != nil {
    						return nil, err
    					}
    					if passwd != "" {
    						if _, err := c.Do("AUTH", passwd); err != nil {
    							c.Close()
    							return nil, err
    						}
    					}
    					return c, nil
    				},
    				TestOnBorrow: func(c redis.Conn, t time.Time) error {
    					_, err := c.Do("PING")
    					return err
    				},
    			},
    		}
    	})
    
    	return &redisClient, nil
    }
    
    func (c *Client) Set(key string, data interface{}, time int) (err error) {
    	conn := c.Pool.Get()
    	defer conn.Close()
    
    	//	value, err := json.Marshal(data)
    	value, ok := data.(string)
    	if !ok {
    		return errors.New("Set No string")
    	}
    
    	_, err = conn.Do("SET", key, value)
    	if err != nil {
    		return err
    	}
    
    	_, err = conn.Do("EXPIRE", key, time)
    	if err != nil {
    		return err
    	}
    
    	return nil
    }
    
    func (c *Client) Exists(key string) (bool, error) {
    	conn := c.Pool.Get()
    	defer conn.Close()
    
    	exists, err := redis.Bool(conn.Do("EXISTS", key))
    	if err != nil {
    		log.Println(err)
    		return false, err
    	}
    
    	return exists, nil
    }
    
    func (c *Client) Get(key string) (string, error) {
    	conn := c.Pool.Get()
    	defer conn.Close()
    
    	reply, err := redis.Bytes(conn.Do("GET", key))
    	if err != nil {
    		return "", err
    	}
    
    	return string(reply), nil
    }
    
    func (c *Client) Delete(key string) (bool, error) {
    	conn := c.Pool.Get()
    	defer conn.Close()
    
    	return redis.Bool(conn.Do("DEL", key))
    }
    
    func (c *Client) LikeDeletes(key string) error {
    	conn := c.Pool.Get()
    	defer conn.Close()
    
    	keys, err := redis.Strings(conn.Do("KEYS", "*"+key+"*"))
    	if err != nil {
    		return err
    	}
    
    	for _, key := range keys {
    		_, err = c.Delete(key)
    		if err != nil {
    			return err
    		}
    	}
    
    	return nil
    }
    
    func (c *Client) Close() {
    	c.Pool.Close()
    	once = sync.Once{}
    }
    

    问题

    1. 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 allkeys-lru
    

    该配置就是配内存淘汰策略的(什么,你没配过?好好反省一下自己)

    1)noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。应该没人用吧。

    2)allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。推荐使用。

    3)allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。应该也没人用吧,你不删最少使用Key,去随机删。

    4)volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。这种情况一般是把redis既当缓存,又做持久化存储的时候才用。不推荐

    5)volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。依然不推荐

    6)volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。不推荐

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

    2. redis和数据库双写一致性问题

    分析:一致性问题是分布式常见问题,还可以再分为最终一致性和强一致性。数据库和缓存双写,就必然会存在不一致的问题。答这个问题,先明白一个前提。就是如果对数据有强一致性要求,不能放缓存。我们所做的一切,只能保证最终一致性。另外,我们所做的方案其实从根本上来说,只能说降低不一致发生的概率,无法完全避免。因此,有强一致性要求的数据,不能放缓存。

    回答:首先,采取正确更新策略,先更新数据库,再删缓存。其次,因为可能存在删除缓存失败的问题,提供一个补偿措施即可,例如利用消息队列。


    参考:

    1 Redis 教程 runoob

    2 一文看懂redis

    3 redis全面解析

    4 用 Go 来了解一下 Redis 通讯协议 煎鱼

    5 golang中使用redis 简书 推荐go-redis和redigo库

    6.Redis持久化和备份数据

    7.redis数据结构 简书

  • 相关阅读:
    svn忽略不需要同步的文件夹或文件
    Redis 字符串(String)
    Redis 数据类型
    Linux下安装rabbitMQ
    Windows平台下Git服务器搭建
    Linux下安装redis
    JVM调优总结
    Tomcat优化配置
    通过profile 用maven命令打不同配置的变量包
    Log4j日志配置说明
  • 原文地址:https://www.cnblogs.com/embedded-linux/p/13335992.html
Copyright © 2011-2022 走看看