Welcome to kimi's blog

Redis

Redis

Redis介绍与安装

为什么用redis缓存速度快?

```python
1 纯内存操作
2 高性能的网络模型 IO多路复用(epoll)
3 单线程,不存在线程间切换
```
  1. 简介

    redis是缓存数据库【大部分时间做缓存,不仅仅可以做缓存】,属于非关系型数据库【区别于mysql关系型数据库】,存储是 key-value形式存储,没有表的概念。

    -nosql:非关系型的数据库
    - c语言写的 服务(监听端口),用来存储数据的,数据是存储在内存中,取值,放值速度非常快, 10w qps
    
  2. 版本

    最新是redis 7.x ,; redis6.x之后,采用了多进程、多线程架构;普遍使用 redis5.x 最多。

  3. 安装

    mac   源码编译安装
    linux 源码编译安装
    win   微软自己,基于源码,改动,编译成安装包
           # 最新5.x版本 https://github.com/tporadowski/redis/releases/
           # 最新3.x版本 https://github.com/microsoftarchive/redis/releases
            
            一路下一步,安装完释放出两个命令,会把redis自动加入到服务中
            redis-server   # mysqld  服务端的启动命令
            redis-cli      # mysql    客户端的启动命令
            
            
    # 安装目录 
        redis-server  
        redis-cli
        redis.windows-service.conf   配置文件
        	bind 127.0.0.1   # 服务地址
             port 6379         # 监听的端口
             ......
            
    # redis默认有16个库,默认连进去就是第0个
    

详情安装路径:https://www.cnblogs.com/liuqingzheng/p/9831331.html

  1. 启动redis

    方式一

    服务中找到redis----》点击启动(后台启动)

    方式二

    命令启动

    redis-server  指定配置文件  不指定就选默认配置
    

image

  1. 客户端连接redis

    方式一

    # cmd连接
    redis-cli   # 默认连接本地的6379端口-----python的3306端口
    # 指定窗口
    redis-cli -h 地址 -p 端口   #连接指定地址和端口
    
    补充知识:
    -mysql 也是cs架构软件
         -pymysql :是mysql的客户端
         -Navicate:是mysql客户端
         -go语言操作mysql:是mysql客户端
    

    方式二

    图形化界面
    使用图形化客户端操作,图形化界面,连接redis 输入地址和端口,点击连接即可

    Redis Desktop Manager :开源的,原来免费,后来收费了  推荐用(mac,win,linux 都有)
           -Qt5  qt是个平台,专门用来做图形化界面的 
           -可以使用c++写
           -可以使用python写  pyqt5  使用python写图形化界面 (少量公司再用)
            -resp-2022.1.0.0.exe 一路下一步,安装完启动起来
            -Redis Client  小众
    
    

目前使用的客户端和服务端都在同一台机器,本地的客户端可以连接远程的服务器
image

方式三
python的redis模块操作redis

python连接redis

使用python当客户端操作redis,需要安装redis

pip install redis

django中操作mysql,没有连接池的,一个请求就是一个mysql连接。可能会有问题,并发数过,导致mysql连接数过高,影响mysql性能;使用django连接池:https://blog.51cto.com/liangdongchang/5140039

1.普通连接

# 安装redis 模块:pip install redis

# 1 导入模块的Redis类
from redis import Redis

# 2 实例化得到对象
conn = Redis(host='127.0.0.1', port=6379)

# 3 使用conn,操作redis
# 获取name的值
# res = conn.get('name')  # 返回数据是bytes格式

# 4 设置值
conn.set('age',19)

conn.close()

2.连接池连接---单例模式

redis-py使用connection pool来管理对一个redis server的所有连接,避免每次建立、释放连接的开销。默认,每个Redis实例都会维护一个自己的连接池。可以直接建立一个连接池,然后作为参数Redis,这样就可以实现多个Redis实例共享一个连接池

pool.py

将连接池做成一个单例模式,后续使用直接导入即可

import redis
POOL = redis.ConnectionPool(max_connections=10, host='127.0.0.1', port=6379)  # 创建一个大小为10的redis连接池

# 单例模式:设计模式  23 中设计模式
    全局只有一个 这个对象,导入多次都只会执行一次 
    p1=Person()  # p1 对象
    p2=Person()  # p2 新对象
    
    # 引入单例模式的6种方式
    	-1 模块导入方式
# 测试代码
import redis
from threading import Thread
from pool import POOL
def task():
    # 做成模块后,导入,无论导入多少次,导入的都那一个POOL对象
    conn = redis.Redis(connection_pool=POOL)  # 报错的原因是拿连接,池里不够了,没有等待,线程报错  设置等待,参数
    print(conn.get('name'))

for i in range(1000):
    t = Thread(target=task)  # 每次都是一个新的连接,会导致 的连接数过多
    t.start()

每次连接都是新的连接,会导致连接数多于连接池的个数,线程就会报错

image

django 使用mysql连接池

cache?

Redis之五种数据类型

redis有五种数据类型,分别是字符串、列表、字典(hash)、集合和有序集合

Redis之字符串操作

redis 是key-value形式存储,数据是放在内存中,如果断电,数据就会丢失,需要一个持久化的方案。引入redis的五种数据类型,分别是字符串、列表、字典(hash)、集合和有序集合

字符串类型使用

# 字符串类型的基本方法使用
'''
1 set(name, value, ex=None, px=None, nx=False, xx=False)
2 setnx(name, value)
3 setex(name, value, time)
4 psetex(name, time_ms, value)
5 mset(*args, **kwargs)
6 get(name)
7 mget(keys, *args)
8 getset(name, value)
9 getrange(key, start, end)
10 setrange(name, offset, value)
11 setbit(name, offset, value)
12 getbit(name, offset)
13 bitcount(key, start=None, end=None)
14 bitop(operation, dest, *keys)
15 strlen(name)
16 incr(self, name, amount=1)
# incrby
17 incrbyfloat(self, name, amount=1.0)
18 decr(self, name, amount=1)
19 append(key, value)
'''


from redis import Redis

conn = Redis(host='127.0.0.1',port=6379)
# 1 set(name, value, ex=None, px=None, nx=False, xx=False)
# ex,过期时间(秒)
# px,过期时间(毫秒)
# nx,如果设置为True,则只有name不存在时,当前set操作才执行, 值存在,就修改不了,执行没效果
# xx,如果设置为True,则只有name存在时,当前set操作才执行,值存在才能修改,值不存在,不会设置新值
# res = conn.set('name','董卿')
# res = conn.set('name','朱广权',ex=3)
# res = conn.set('name','朱广权',px=3)
# res = conn.set('name','朱广权',nx=True)
# res = conn.set('name','朱广权',nx=False)
# res = conn.set('name','朱广权',xx=True)
# res = conn.set('name','朱广权',xx=False)
# redis---》实现分布式锁,底层基于nx实现的

# 2 setnx(name, value)  # 等同于conn.set('name','朱广权',nx=True)
# res = conn.setnx('hobby','90')


# 3 setex(name, time,value) # 等同于:conn.set('name','kimi',ex=3)
# res = conn.setex('name',3,'kimi')

# 4 psetex(name, time_ms, value)
# res = conn.psetex('friends',3000,'YangJie')  # 产生3s后消失

# 5 mset(*args, **kwargs)
# res= conn.mset({'friends': '花花', 'hobby': 'traval'})


# 6 get(name)
# res = conn.get('name')
# print(str(res,encoding='utf-8'))


# 7 mget(keys, *args)
# res = conn.mget('name','friends')
# res = conn.mget(['name','friends'])

# 8 getset(name, value)  # 拿到原来的name值,放入新的name值=kiki
# res = conn.getset('name','kiki')


# 9 getrange(key, start, end)  # 字节长度,不是字符长度  前闭后闭区间
# res = str(conn.getrange('name', 0, 2), encoding='utf-8')

# 10 setrange(name, offset, value)   # 从第二个位置开始替换并计算字节长度
# res = conn.setrange('name',2,'hello')  # kiki--kihello


# ---- 比特位---操作
# 11 setbit(name, offset, value)
# 12 getbit(name, offset)
# 13 bitcount(key, start=None, end=None)
# ---- 比特位---操作

# 14 bitop(operation, dest, *keys)   获取多个值,并将值做位运算,将最后的结果保存至新的name对应的值
# res = conn.bitop('name')


# 15 strlen(name)  # name字节长度
# res = conn.strlen('hobby')

# 16 incr(self, name, amount=1) # 不写默认自增1
# 自增,不会出并发安全问题,单线程架构,并发量高
# res = conn.incr('age')    # incr = incrby
# res = conn.incrby('age')


# 17 incrbyfloat(self, name, amount=1.0)  自增浮点数
# res = conn.incrbyfloat('age',0.6)


# 18 decr(self, name, amount=1)  # 自减1
# res = conn.decr('age')  # decr = decrby
# res = conn.decrby('age')

# 19 append(key, value)
res = conn.append('name','玛丽亚')  # kihello玛丽亚
print(conn.strlen('name'))  # 25


# print(str(res,encoding='utf-8'))
print(res)
conn.close()

redis之列表

List操作,redis中的List在在内存中按照一个name对应一个List来存储。如图:

image

1.lpush(name,values)

# 在name对应的list中添加元素,每个新的元素都添加到列表的最左边
res=conn.lpush('girls','kimi','kiki')  # 左侧插入

2.rpush(name, values)

 # 表示从右向左操作
 res=conn.rpush('girls','rose','fry')  # 右侧插入

3.lpushx(name,value)

# 在name对应的list中添加元素,只有name已经存在时,值添加到列表的最左边
res=conn.lpush('girls','朱熹')

4 rpushx(name, value)

# 只有name已经存在时,值添加到表示从右向左操作
res=conn.rpush('girls','高星')

image

5.llen(name)

# name对应的list元素的个数
res = conn.llen('girls')  # 6

6.linsert(name, where, refvalue, value))

# 在name对应的列表的某一个值前或后插入一个新值
res = conn.linsert('girls','before','kiki','jerry')
res = conn.linsert('girls','after','kimi','mimi')
res = conn.linsert('girls','after','python','mimi') # 没有python标杆是插入数据不进去的  返回-1

image

7.r.lset(name, index, value)

# 对name对应的list中的某一个索引位置重新赋值
res = conn.lset('girls',1,'央视boys')

8.r.lrem(name, value, num)

# 在name对应的list中删除指定的值
res = conn.lrem('girls',1,'央视boys')  # 从左侧开始,删除1个
res = conn.lrem('girls',-1,'央视boys')  # 从右侧开始,删除1个
res = conn.lrem('girls',0,'朱熹')  # 从左开始,全删除

9.lpop(name)

# 在name对应的列表的左侧获取第一个元素并在列表中移除,返回值则是第一个元素
res=conn.lpop('girls')  # b'kimi'

10.rpop(name)

# 表示从右向左操作
res=conn.lpop('girls')  # b'\xe9\xab\x98\xe6\x98\x9f'

11.lindex(name, index)

# 在name对应的列表中根据索引获取列表元素
res=conn.lindex('girls',1)  # b'kimi'

12.lrange(name, start, end)

# 在name对应的列表分片获取数据  获得到是闭合区间
res=conn.lrange('girls',0,1)  # [b'kiki', b'kimi']

13.ltrim(name, start, end)

# 在name对应的列表中移除没有在start-end索引之间的值
res=conn.ltrim('girls',1,5)  # True

14.rpoplpush(src, dst)

# 从一个列表取出最右边的元素,同时将其添加至另一个列表的最左边
# 参数:
    # src,要取数据的列表的name
    # dst,要添加数据的列表的name
res=conn.rpoplpush('girls','boys')  # 将girls列表右边弹出--从左侧插入boys列表

15.blpop(keys, timeout)

# 将多个列表排列,按照从左到右去pop对应列表的元素
# 参数:
    # keys,redis的name的集合
    # timeout,超时时间,当元素所有列表的元素获取完之后,阻塞等待列表内有数据的时间(秒), 0 表示永远阻塞
# 更多:
    # r.brpop(keys, timeout),从右向左获取数据爬虫实现简单分布式:多个url放到列表里,往里不停放URL,程序循环取值,但是只能一台机器运行取值,可以把url放到redis中,多台机器从redis中取值,爬取数据,实现简单分布式
    
    
    # 记住 ,可以做消息队列使用  阻塞式弹出,如果没有,就阻塞
    res = conn.brpop("girls")  # (b'girls', b'rose')
    res = conn.brpop("花花")  # 没有就会阻塞

16 brpop(keys, timeout)

# 从右向左剔除数据
res = conn.brpop("girls")  # (b'girls', b'mimi')

17.brpoplpush(src, dst, timeout=0)

# 从一个列表的右侧移除一个元素并将其添加到另一个列表的左侧
 
# 参数:
    # src,取出并要移除元素的列表对应的name
    # dst,要插入元素的列表对应的name
    # timeout,当src对应的列表中没有数据时,阻塞等待其有数据的超时时间(秒),0 表示永远阻塞

自定义增量迭代

# 由于redis类库中没有提供对列表元素的增量迭代,如果想要循环name对应的列表的所有元素,那么就需要:
    # 1、获取name对应的所有列表
    # 2、循环列表
# 但是,如果列表非常大,那么就有可能在第一步时就将程序的内容撑爆,所有有必要自定义一个增量迭代的功能:
import redis
conn=redis.Redis(host='127.0.0.1',port=6379)
# conn.lpush('test',*[1,2,3,4,45,5,6,7,7,8,43,5,6,768,89,9,65,4,23,54,6757,8,68])
# conn.flushall()
def scan_list(name,count=2):
    index=0
    while True:
        data_list=conn.lrange(name,index,count+index-1)
        if not data_list:
            return
        index+=count
        for item in data_list:
            yield item
print(conn.lrange('test',0,100))
for item in scan_list('test',5):
    print('---')
    print(item)

image

redis之hash

Hash操作,redis中Hash在内存中的存储格式如下图:

image

hset(name, key, value)

# name对应的hash中设置一个键值对(不存在,则创建;否则,修改)
# res = conn.hset('userinfo','name','kiki')
# res = conn.hset('userinfo',mapping={'age':16,'hobby':'睡觉'})

hmset(name, mapping)

# 在name对应的hash中批量设置键值对
# 批量设置,被弃用了,以后都使用hset
res = conn.hmset('userinfo1',mapping={'age':16,'hobby':'睡觉'})

hget(name,key)

# 在name对应的hash中获取根据key获取value
# res = conn.hget('userinfo','name')  # b'kiki'

hmget(name, keys, *args)

# 在name对应的hash中获取多个key的值
# res = conn.hmget('userinfo',['name','age'])  # [b'kimi', b'16']
# res = conn.hmget('userinfo','name','age')  # [b'kimi', b'16']

hgetall(name)

# 获取name对应hash的所有键值   慎用
res = conn.hgetall('userinfo')  # {b'name': b'kimi', b'age': b'16', b'hobby': b'\xe7\x9d\xa1\xe8\xa7\x89'}

hlen(name)

# 获取name对应的hash中键值对的个数
# res=conn.hlen('userinfo')  # 3

hkeys(name)

# 获取name对应的hash中所有的key的值
# res=conn.hkeys('userinfo')  # [b'name', b'age', b'hobby']

hvals(name)

# 获取name对应的hash中所有的value的值
# res=conn.hvals('userinfo')  # [b'kimi', b'16', b'\xe7\x9d\xa1\xe8\xa7\x89']

hexists(name, key)

# 检查name对应的hash是否存在当前传入的key
# res=conn.hexists('userinfo','name') # True
# res=conn.hexists('userinfo','name1')  # False

hdel(name,*keys)

# 将name对应的hash中指定key的键值对删除
res = conn.hdel('userinfo', 'age')

hincrby(name, key, amount=1)

# 自增name对应的hash中的指定key的值,不存在则创建key=amount
res=conn.hincrby('userinfo','age')  不写amount,默认自增1

hincrbyfloat(name, key, amount=1.0)

# 自增name对应的hash中的指定key的值,不存在则创建key=amount
res=conn.hincrbyfloat('userinfo','age')  # 没有自增age=1.0

hscan(name, cursor=0, match=None, count=None)

# 增量式迭代获取,对于数据大的数据非常有用,hscan可以实现分片的获取数据,并非一次性将数据全部获取完,从而放置内存被撑爆

eg:
# hgetall  会一次性全取出,效率低,可以能占内存很多
# 分批获取,hash类型是无序
# 插入一批数据
# for i in range(100):
#     conn.hset('hash_test','id_%s'%i,'号码%s'%i)
# res = conn.hgetall('hash_test')  # 可以,但是不好,一次性拿出,可能占很大内存
# 它不单独使用,拿的数据,不是特别准备
res =conn.hscan('hash_test',cursor=0,count=5)
print(res[1]) #(数字,拿出来的5条数据)   数字是下一个游标位置
# 咱们用这个,它内部用了hscan,等同于hgetall 所有数据都拿出来,count的作用是,生成器,每次拿count个个数

hscan_iter(name, match=None, count=None)

# 利用yield封装hscan创建生成器,实现分批去redis中获取数据
# 参数:
    # match,匹配指定key,默认None 表示所有的key
    # count,每次分片最少获取个数,默认None表示采用Redis的默认分片个数
    
res =conn.hscan_iter('hash_test',count=5)
print(res)  # <generator object ScanCommands.hscan_iter at 0x0000019266AFF660>
# generator 只要函数中有yield关键字,这个函数执行的结果就是生成器 ,生成器就是迭代器,可以被for循环
# for i in res:
#     print(i)

redis之集合操作

请前往该地址:https://www.cnblogs.com/liuqingzheng/articles/9833534.html

redis其他操作

通用操作:所有数据类型都支持

delete(*names) 删除
exists(name) 是否存在
keys(pattern='*')
expire(name ,time)
rename(src, dst)
move(name, db))
randomkey()
type(name)
import redis
conn = redis.Redis()
# 通用操作,不指定类型,所有类型都支持
# 1 delete(*names)
# conn.delete('name','userinfo1') # 直接删除userinfo1
# conn.delete(['name', 'userinfo2'])  # 不行,没反应
# conn.delete(*['name', 'userinfo1'])  # 可以用它

# 2 exists(name)
# res = conn.exists('userinfo')  # 1

# 3 keys(pattern='*')
# res = conn.keys('?ame')  #  ?表示一个字符,   * 表示多个字符
# res= conn.keys('*e')  # [b'age']

# 4 expire(name ,time)  # time过后过期
# res = conn.expire('userinfo',3)  # True

# 5 rename(src, dst)  #将girls改为userinfo_dict
# conn.rename('girls','userinfo_dict')

# 6 move(name, db))  # name移动到第2个库
# conn.move('userinfo_dict',2)

# 7 randomkey() # 随机获取键值
# res= conn.randomkey()

# 8 type(name)
print(conn.type('age'))  # b'string'
print(conn.type('girls')) # b'none'

conn.close()

redis管道

事务的四大特性:原子性、一致性、隔离性、持久性

redis支持事务吗?

单实例支持所谓的事物,支持事务是基于管道的

eg:执行命令  一条一条执行
    -kiki 金额 -100    conn.decr('kiki',100)
        程序挂了--张三转账了但你没有收到账
     -kimi   金额 100     conn.incr('kimi',100)

如何解决?
   把这两条命令,放到一个管道中,先不执行,当执行excute,一次性都执行完成
    conn.decr('kiki',100)   conn.incr('kimi',100)

使用管道

redis-py默认在执行每次请求都会创建(连接池申请连接)和断开(归还连接池)一次连接操作,如果想要在一次请求中指定多个命令,则可以使用pipline实现一次请求指定多个命令,并且默认情况下一次pipline 是原子性操作。

import redis
conn= redis.Redis()# 默认是本地127.0.0.1
p = conn.pipeline(transaction=True) # 开启事务
p.multi()
p.decr('kiki',100)
# raise Exception('崩了')
p.incr('kimi',100)

p.execute()
conn.close()

django中使用redis

方案一:自定义方案

# 第一步写一个pool.py
import redis
POOL = redis.ConnectionPool(max_connections=100)
# 第二步: 以后在使用的地方,直接导入使用即可
conn = redis.Redis(connection_pool=POOL)
conn.incr('count') # 自增
res = conn.get('count')


# 总路由
path('test_redis/',views.test_redis)

# django使用redis
import redis
from utils.pool import POOL
def test_redis(request):
    conn = redis.Redis(connection_pool=POOL)
    conn.incr('count') # 自增1
    res = conn.get('count')
    # ensure_ascii 二进制转中文
    return JsonResponse({'count':'今天这个接口访问的次数为:%s' % res},json_dumps_params={'ensure_ascii':False})

方案二:django方案有两种

从连接池里面拿东西

  1. 第一种:django的缓存使用redis,django中使用redis----settings.py 中配置
# django的缓存使用redis  【推荐使用】
1. settings.py 中配置

CACHES = {
            "default": {
                "BACKEND": "django_redis.cache.RedisCache",
                "LOCATION": "redis://127.0.0.1:6379",
                "OPTIONS": {
                    "CLIENT_CLASS": "django_redis.client.DefaultClient",
                    "CONNECTION_POOL_KWARGS": {"max_connections": 100}
                    # "PASSWORD": "123",
                }
            }
        }

2. 使用redis的地方:cache.set('count',res+1)
 底层是pickle序列化后存入的
    # django使用redis---使用缓存cache
    from django.core.cache import cache
    def test_redis(request):
        # cache.set('count',1)
        try:
            res = cache.get('count') + 1
            cache.set('count', res)
        except Exception:
            cache.set('count', 1)
            res = 1
        return JsonResponse({'count': '今天这个接口访问的次数为:%s' % res}, json_dumps_params={'ensure_ascii': False})
    
    
    # 底层存的是pickle
    class Person:
        name='kimi'

    def test_redis(request):
        # 存数据
        # p=Person()
        # cache.set('person',p)  #picke存数据是二进制,拿出来也是二进制
        # 取数据
        p=cache.get('person')
        print(p.name)  # kimi
        return JsonResponse({'count':'今天这个接口访问的次数为:%s' },json_dumps_params={'ensure_ascii':False})
  1. 第二种方法----使用第三方模块django_redis

# django使用redis---第三方django-redis模块
from django_redis import get_redis_connection
def test_redis(request):
    conn = get_redis_connection()
    try:
        res=int(str(conn.get('count'),encoding='utf-8')) +1
        conn.set('count',res)
        print(res)
    except Exception:
        conn.set('count',1)
        res = 1
    return JsonResponse({'count':'今天这个接口访问的次数为:%s'%res },json_dumps_params={'ensure_ascii':False})
posted @ 2023-03-07 19:48  魔女宅急便  阅读(24)  评论(0编辑  收藏  举报
Title