一、Redis简介

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

  Redis是一个开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。它通常被称为数据结构服务器,因为值(value)可以是 字符串(String), 哈希(Map), 列表(list), 集合(sets) 和 有序集合(sorted sets)等类型。经常被用作数据库,缓存和消息代理。它支持数据结构,如字符串,散列,列表,集合,带有范围查询的排序集,位图,超级日志,带有半径查询和流的地理空间索引。Redis具有内置复制,Lua脚本,LRU驱逐,事务和不同级别的磁盘持久性,并通过Redis Sentinel提供高可用性并使用Redis Cluster自动分区。 

为什么选择Redis?

  1. 1. 使用Redis有哪些好处?
  2.  
  3. (1) 速度快,因为数据存在内存中,类似于HashMapHashMap的优势就是查找和操作的时间复杂度都是O(1)
  4.  
  5. (2) 支持丰富数据类型,支持stringlistsetsorted sethash
  6.  
  7. (3) 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行
  8.  
  9. (4) 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除
  10.  
  11. 2. redis相比memcached有哪些优势?
  12.  
  13. (1) memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型
  14.  
  15. (2) redis的速度比memcached快很多
  16.  
  17. (3) redis可以持久化其数据
  18.  
  19. 3. redis常见性能问题和解决方案:
  20.  
  21. (1) Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件
  22.  
  23. (2) 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次
  24.  
  25. (3) 为了主从复制的速度和连接的稳定性,MasterSlave最好在同一个局域网内
  26.  
  27. (4) 尽量避免在压力很大的主库上增加从库
  28.  
  29. (5) 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3...
  30.  
  31. 这样的结构方便解决单点故障问题,实现SlaveMaster的替换。如果Master挂了,可以立刻启用Slave1Master,其他不变。
  32.  
  33. 4. MySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据
  34.  
  35. 相关知识:redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。redis 提供 6种数据淘汰策略:
  36.  
  37. voltile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
  38.  
  39. volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
  40.  
  41. volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
  42.  
  43. allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
  44.  
  45. allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
  46.  
  47. no-enviction(驱逐):禁止驱逐数据
  48.  
  49. 5. MemcacheRedis的区别都有哪些?
  50.  
  51. 1)、存储方式
  52.  
  53. Memecache把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。
  54.  
  55. Redis有部份存在硬盘上,这样能保证数据的持久性。
  56.  
  57. 2)、数据支持类型
  58.  
  59. Memcache对数据类型支持相对简单。
  60.  
  61. Redis有复杂的数据类型。
  62.  
  63. 3),value大小
  64.  
  65. redis最大可以达到1GB,而memcache只有1MB
  66.  
  67. 6. Redis 常见的性能问题都有哪些?如何解决?
  68.  
  69. 1).Master写内存快照,save命令调度rdbSave函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务,所以Master最好不要写内存快照。
  70.  
  71. 2).Master AOF持久化,如果不重写AOF文件,这个持久化方式对性能的影响是最小的,但是AOF文件会不断增大,AOF文件过大会影响Master重启的恢复速度。Master最好不要做任何持久化工作,包括内存快照和AOF日志文件,特别是不要启用内存快照做持久化,如果数据比较关键,某个Slave开启AOF备份数据,策略为每秒同步一次。
  72.  
  73. 3).Master调用BGREWRITEAOF重写AOF文件,AOF在重写的时候会占大量的CPU和内存资源,导致服务load过高,出现短暂服务暂停现象。
  74.  
  75. 4). Redis主从复制的性能问题,为了主从复制的速度和连接的稳定性,SlaveMaster最好在同一个局域网内
  76.  
  77. 7, redis 最适合的场景
  78.  
  79. Redis最适合所有数据in-momory的场景,虽然Redis也提供持久化功能,但实际更多的是一个disk-backed的功能,跟传统意义上的持久化有比较大的差别,那么可能大家就会有疑问,似乎Redis更像一个加强版的Memcached,那么何时使用Memcached,何时使用Redis呢?
  80.  
  81. 如果简单地比较RedisMemcached的区别,大多数都会得到以下观点:
  82. Redis不仅仅支持简单的k/v类型的数据,同时还提供listsetzsethash等数据结构的存储。
  83. Redis支持数据的备份,即master-slave模式的数据备份。
  84. Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。
  85.  
  86. 1)、会话缓存(Session Cache
  87.  
  88. 最常用的一种使用Redis的情景是会话缓存(session cache)。用Redis缓存会话比其他存储(如Memcached)的优势在于:Redis提供持久化。当维护一个不是严格要求一致性的缓存时,如果用户的购物车信息全部丢失,大部分人都会不高兴的,现在,他们还会这样吗?
  89.  
  90. 幸运的是,随着 Redis 这些年的改进,很容易找到怎么恰当的使用Redis来缓存会话的文档。甚至广为人知的商业平台Magento也提供Redis的插件。
  91.  
  92. 2)、全页缓存(FPC
  93.  
  94. 除基本的会话token之外,Redis还提供很简便的FPC平台。回到一致性问题,即使重启了Redis实例,因为有磁盘的持久化,用户也不会看到页面加载速度的下降,这是一个极大改进,类似PHP本地FPC
  95.  
  96. 再次以Magento为例,Magento提供一个插件来使用Redis作为全页缓存后端。
  97.  
  98. 此外,对WordPress的用户来说,Pantheon有一个非常好的插件 wp-redis,这个插件能帮助你以最快速度加载你曾浏览过的页面。
  99.  
  100. 3)、队列
  101.  
  102. Reids在内存存储引擎领域的一大优点是提供 list set 操作,这使得Redis能作为一个很好的消息队列平台来使用。Redis作为队列使用的操作,就类似于本地程序语言(如Python)对 list push/pop 操作。
  103.  
  104. 如果你快速的在Google中搜索“Redis queues”,你马上就能找到大量的开源项目,这些项目的目的就是利用Redis创建非常好的后端工具,以满足各种队列需求。例如,Celery有一个后台就是使用Redis作为broker,你可以从这里去查看。
  105.  
  106. 4),排行榜/计数器
  107.  
  108. Redis在内存中对数字进行递增或递减的操作实现的非常好。集合(Set)和有序集合(Sorted Set)也使得我们在执行这些操作的时候变的非常简单,Redis只是正好提供了这两种数据结构。所以,我们要从排序集合中获取到排名最靠前的10个用户–我们称之为“user_scores”,我们只需要像下面一样执行即可:
  109.  
  110. 当然,这是假定你是根据你用户的分数做递增的排序。如果你想返回用户及用户的分数,你需要这样执行:
  111.  
  112. ZRANGE user_scores 0 10 WITHSCORES
  113.  
  114. Agora Games就是一个很好的例子,用Ruby实现的,它的排行榜就是使用Redis来存储数据的,你可以在这里看到。
  115.  
  116. 5)、发布/订阅
  117.  
  118. 最后(但肯定不是最不重要的)是Redis的发布/订阅功能。发布/订阅的使用场景确实非常多。我已看见人们在社交网络连接中使用,还可作为基于发布/订阅的脚本触发器,甚至用Redis的发布/订阅功能来建立聊天系统!(不,这是真的,你可以去核实)。
  119.  
  120. Redis提供的所有特性中,我感觉这个是喜欢的人最少的一个,虽然它为用户提供如果此多功能。

二、下载和安装

  1. windows

  在redis官网http://www.redis.net.cn/download/

  选择对应版本安装即可。

  

  随后把下载文件夹目录添加到环境变量。

配置文件

  1. bind 0.0.0.0
  2. port 6379
  3. requirepass 密码

  2. linux

下载和安装

  1. yum install redis
  2.   - redis-server /etc/redis.conf 启动服务器
  3. 或者
  4. wget http://download.redis.io/releases/redis-5.0.3.tar.gz
  5. tar xzf redis-3.0.6.tar.gz
  6. cd redis-3.0.6
  7. make
  8. vi redis.conf 修改配置文件
  9. - bind 0.0.0.0
  10. - port 6379
  11. - requirepass 0000

启动服务端

  1. src/redis-server redis.conf

启动客户端

  1. src/redis-cli
  2. redis> set foo bar
  3. OK
  4. redis> get foo
  5. "bar"

后台启动服务端

  1. 1. 进入 DOS窗口
  2.  
  3. 2. 在进入Redis的安装目录
  4.  
  5. 3. 输入:redis-server --service-install redis.windows.conf --loglevel verbose ( 安装redis服务 )
  6.  
  7. 4. 输入:redis-server --service-start ( 启动服务 )
  8.  
  9. 5. 输入:redis-server --service-stop (停止服务)

补充:windows下设置redis允许局域网内部访问

第一步:修改redis.windows.conf

  1. # 大约56行
  2. # bind 127.0.0.1
  3. bind 0.0.0.0
  4.  
  5. # 大约76行
  6. # protected-mode yes
  7. protected-mode no

第二步:重启redis.windows.conf

第三步:关闭防火墙或者设置允许redis通过防火墙

第四步:电脑B访问该计算机redis

  1. redis-cli -h 192.168.0.105 -p 6379

三、启动客户端:redis-cli

  

  redis默认有15个数据库

  选择1号数据库

四.数据操作

  redis是key-value的数据,所以每个数据都是一个键值对。键的类型是字符串。

  值的类型分为五种:

  • 字符串string
  • 哈希hash
  • 列表list
  • 集合set
  • 有序集合zset

 1.string

  •   string是redis最基本的类型
  •   最大能存储512MB数据
  •   string类型是二进制安全的,即可以为任何数据,比如数字、图片、序列化对象等

  命令:

  •   设置键值:set key value(单个值),setex key seconds value(设置时间), mset key1 value1 key2 value2 ..(为多个值赋值).
  •   获取键值:get key(获取单个值), mget key1 key2(获取多个值)
  •   运算:incr,incrby,decr,decrby,append key value, strlen key   要求 value是数字

  键命令

  •   keys pattern:查看键值  keys *  查看所有键值 keys article*
  •   exists key:查看键值是否存在
  •   type key:查看key对应的类型
  •   del key:删除key
  •   expire key seconds:设置key过期时间
  •   ttl key:查看key过期时间

 2.hash: 用于存储对象,对象的格式为键值对。

    hset key field value: 设置hash key对象指定数据类型的一个值

    hmset key field1 value1 filed2 value2 ...:设置hash key对象多个数据类型的值

    hget key field:获取指定key的指定数据类型的值

    hmget key field1 field2 : 获取key的field1和field中的value

    hkeys key : 返回key的field

    hlen key:返回key的键值的个数

    hvals key:返回key的value

    hexists key field: 判断key的field的值是否存在

    hdel key filed: 删除key 的field的值

strlen key field: 判断key中field的值的长度

 3. list  

  • 列表的元素类型为string
  • 按照插入顺序排序
  • 在列表的头部或者尾部添加元素

  命令:lpush key value: 往列表key的左边插入一个value

        rpush key value:往列表key的右边插入一个value

        linsert key before|after value new_value:往列表key中value前|后插入new_value

     lset key index new_value: 将列表key的第index个value设置为new_value

     lpop key:左弹出key列表中的值

       rpop key:右弹出key列表中的值

     lrange key start end:查看key列表中start-end中的值

    4.set

    • 无序集合
    • 元素为string类型
    • 元素具有唯一性,不重复

   命令:sadd key value : 往无序集合key中插入value值,位置随机

      spop key:在无序集合key中随机弹出集合一个值

      smembers key:查看无序集合key中的所有元素

      scard key:查看无序集合key的值的个数

 5.zset

    • sorted set,有序集合
    • 元素为string类型
    • 元素具有唯一性,不重复
    • 每个元素都会关联一个double类型的score,表示权重,通过权重将元素从小到大排序
    • 元素的score可以相同

     命令:zadd key score1 value1 score2 value2 : 向有序集合key中添加value1,value2并制定相应权重

        zrem key value:删除有序集合中的value

        zrange key start end:查看有序集合中start-end中的值

        zcard key:查看有序集合中元素的个数

        zsocre key value:查看有序集合key中value的score

        zcount key min max:查看有序集合key中score在min-max之间的元素

五、python连接redis

  1. 安装

  1. pip install redis

  redis-py提供两个类Redis和StrictRedis用于实现Redis的命令,StrictRedis用于实现大部分官方的命令,并使用官方的语法和命令,Redis是StrictRedis的子类

  2. 创建连接

  1. from redis import Redis, ConnectionPool
  2.  
  3. # 创建连接
  4. result = Redis(host='127.0.0.1', port=6379)
  5. print(result.keys())

  3. 使用连接池 

  1. from redis import Redis, ConnectionPool
  2.  
  3. # 连接池
  4. pool = ConnectionPool(host='127.0.0.1', port=6379)
  5. conn = Redis(connection_pool=pool)
  6. # print(conn.keys())
  7. # print(conn.smembers('visited_urls'))
  8. print(conn.smembers('dupefilter:test_scrapy_redis'))

 注意:连接池只创建一次

  1. import redis
  2. # 最简单的单例模式:写一个py文件导入
  3. from redis_pool import POOL
  4.  
  5. while True:
  6. key = input('请输入key:')
  7. value = input('请输入value:')
  8. # 去连接池中获取连接
  9. conn = redis.Redis(connection_pool=POOL)
  10. # 设置值
  11. conn.set(key, value)

  4. 数据操作

  • 五大数据类型
  1. redis = {
  2. k1:'123', 字符串
  3. k2:[1,2,3,4,5], 列表
  4. k3:{1,2,3,4}, 集合
  5. k4:{name:'root','age':23}, 字典
  6. k5:{('alex',60),('eva-j',80),('rt',70),},有序集合
  7. }

 a.使用字典 

  - 基本操作

  1. # HASH COMMANDS
  2. # 创建字典
    # 将字典name的key设置为value
  3. hset(self, name, key, value):
    # 若字典name的key不存在时将value设置给key,否则不设置
  4. hsetnx(self, name, key, value):
  5. hmset(self, name, mapping):
  6. # 获取字典的值
    # 获取单个key的值
  7. hget(self, name, key):
    # 获取多个key的值
  8. hmget(self, name, keys, *args):
    # 获取字典name所有的值
  9. hgetall(self, name):
    # 获取字典name所有的key
  10. hkeys(self, name):
    # 获取字典name所有的value
  11. hvals(self, name):
  12. # 判断某个key是否存在
  13. hexists(self, name, key):
  14. # 获取字典name元素的长度
  15. hlen(self, name):
    # 获取字典name的指定key的value的长度
  16. hstrlen(self, name, key):
  17. # 删除字典的key
  18. hdel(self, name, *keys):
  19. # 计数器
  20. hincrby(self, name, key, amount=1):
  21. hincrbyfloat(self, name, key, amount=1.0):
  22. # 性能相关:迭代器
  23. hscan(self, name, cursor=0, match=None, count=None):
  24. hscan_iter(self, name, match=None, count=None): 
  1. # -*- coding: utf-8 -*-
  2.  
  3. """
  4. @Datetime: 2019/1/25
  5. @Author: Zhang Yafei
  6. """
  7. import redis
  8.  
  9. pool = redis.ConnectionPool(host='192.168.137.191', port=6379, password='', max_connections=1000)
  10. conn = redis.Redis(connection_pool=pool)
  11.  
  12. # 字典
  13. """
  14. redis = {
  15. k4:{
  16. 'username': 'zhangyafei',
  17. 'age': 23,
  18. }
  19. }
  20. """
  21. # 1. 创建字典
  22. # conn.hset('k4','username','zhangyafei')
  23. # conn.hset('k4','age',23)
  24. # conn.hsetnx('k4','username','root') # 若key不存在则将value赋值给key, 如果赋值成功则返回1,否则返回0
  25. # conn.hsetnx('k4', 'hobby', 'basketball')
  26. # conn.hmset('k4',{'username':'zhangyafei','age':23})
  27.  
  28. # 2. 获取字典的值
  29. # 获取一个值
  30. val = conn.hget('k4', 'username') # b'zhangyafei'
  31. # print(val)
  32. # 获取多个值
  33. # vals = conn.mget('k4', ['username','age'])
  34. # vals = conn.mget('k4', 'username','age') # {b'username': b'zhangyafei', b'age': b'23'}
  35. # 获取所有值
  36. vals = conn.hgetall('k4') # {b'username': b'zhangyafei', b'age': b'23'}
  37. print(vals)
  38. # 获取长度
  39. lens = conn.hlen('k4') #
  40. str_lens = conn.hstrlen('k4', 'username') #
  41. keys = conn.hkeys('k4') # [b'username', b'age']
  42. values = conn.hvals('k4') # [b'zhangyafei', b'23']
  43. judge = conn.hexists('k4', 'username') # True
  44. # conn.hdel('k4', 'age', 'username')
  45. # print(conn.hkeys('k4')) # []
  46.  
  47. # 计算器
  48. # print(conn.hget('k4', 'age'))
  49. # conn.hincrby('k4','age',amount=2)
  50. # conn.hincrbyfloat('k4','age',amount=-1.5)
  51. # print(conn.hget('k4', 'age'))
  52.  
  53. # 问题:如果redis的k4对应的字典中有1000w条数据,请打印所有数据
  54. # 不可取:redis取到数据之后,服务器内存无法承受,爆栈
  55. # result = conn.hgetall('k4')
  56. # print(result)
  57.  
  58. for item in conn.hscan_iter('k4'):
  59. print(item)

字典操作示例

b. 使用列表

  1. def blpop(self, keys, timeout=0):
  2. """
  3. LPOP a value off of the first non-empty list
  4. named in the ``keys`` list.
  5.  
  6. If none of the lists in ``keys`` has a value to LPOP, then block
  7. for ``timeout`` seconds, or until a value gets pushed on to one
  8. of the lists.
  9.  
  10. If timeout is 0, then block indefinitely.
  11. """
  12. if timeout is None:
  13. timeout = 0
  14. if isinstance(keys, basestring):
  15. keys = [keys]
  16. else:
  17. keys = list(keys)
  18. keys.append(timeout)
  19. return self.execute_command('BLPOP', *keys)
  20.  
  21. def brpop(self, keys, timeout=0):
  22. """
  23. RPOP a value off of the first non-empty list
  24. named in the ``keys`` list.
  25.  
  26. If none of the lists in ``keys`` has a value to RPOP, then block
  27. for ``timeout`` seconds, or until a value gets pushed on to one
  28. of the lists.
  29.  
  30. If timeout is 0, then block indefinitely.
  31. """
  32. if timeout is None:
  33. timeout = 0
  34. if isinstance(keys, basestring):
  35. keys = [keys]
  36. else:
  37. keys = list(keys)
  38. keys.append(timeout)
  39. return self.execute_command('BRPOP', *keys)
  40.  
  41. def brpoplpush(self, src, dst, timeout=0):
  42. """
  43. Pop a value off the tail of ``src``, push it on the head of ``dst``
  44. and then return it.
  45.  
  46. This command blocks until a value is in ``src`` or until ``timeout``
  47. seconds elapse, whichever is first. A ``timeout`` value of 0 blocks
  48. forever.
  49. """
  50. if timeout is None:
  51. timeout = 0
  52. return self.execute_command('BRPOPLPUSH', src, dst, timeout)
  53.  
  54. def lindex(self, name, index):
  55. """
  56. Return the item from list ``name`` at position ``index``
  57.  
  58. Negative indexes are supported and will return an item at the
  59. end of the list
  60. """
  61. return self.execute_command('LINDEX', name, index)
  62.  
  63. def linsert(self, name, where, refvalue, value):
  64. """
  65. Insert ``value`` in list ``name`` either immediately before or after
  66. [``where``] ``refvalue``
  67.  
  68. Returns the new length of the list on success or -1 if ``refvalue``
  69. is not in the list.
  70. """
  71. return self.execute_command('LINSERT', name, where, refvalue, value)
  72.  
  73. def llen(self, name):
  74. "Return the length of the list ``name``"
  75. return self.execute_command('LLEN', name)
  76.  
  77. def lpop(self, name):
  78. "Remove and return the first item of the list ``name``"
  79. return self.execute_command('LPOP', name)
  80.  
  81. def lpush(self, name, *values):
  82. "Push ``values`` onto the head of the list ``name``"
  83. return self.execute_command('LPUSH', name, *values)
  84.  
  85. def lpushx(self, name, value):
  86. "Push ``value`` onto the head of the list ``name`` if ``name`` exists"
  87. return self.execute_command('LPUSHX', name, value)
  88.  
  89. def lrange(self, name, start, end):
  90. """
  91. Return a slice of the list ``name`` between
  92. position ``start`` and ``end``
  93.  
  94. ``start`` and ``end`` can be negative numbers just like
  95. Python slicing notation
  96. """
  97. return self.execute_command('LRANGE', name, start, end)
  98.  
  99. def lrem(self, name, count, value):
  100. """
  101. Remove the first ``count`` occurrences of elements equal to ``value``
  102. from the list stored at ``name``.
  103.  
  104. The count argument influences the operation in the following ways:
  105. count > 0: Remove elements equal to value moving from head to tail.
  106. count < 0: Remove elements equal to value moving from tail to head.
  107. count = 0: Remove all elements equal to value.
  108. """
  109. return self.execute_command('LREM', name, count, value)
  110.  
  111. def lset(self, name, index, value):
  112. "Set ``position`` of list ``name`` to ``value``"
  113. return self.execute_command('LSET', name, index, value)
  114.  
  115. def ltrim(self, name, start, end):
  116. """
  117. Trim the list ``name``, removing all values not within the slice
  118. between ``start`` and ``end``
  119.  
  120. ``start`` and ``end`` can be negative numbers just like
  121. Python slicing notation
  122. """
  123. return self.execute_command('LTRIM', name, start, end)
  124.  
  125. def rpop(self, name):
  126. "Remove and return the last item of the list ``name``"
  127. return self.execute_command('RPOP', name)
  128.  
  129. def rpoplpush(self, src, dst):
  130. """
  131. RPOP a value off of the ``src`` list and atomically LPUSH it
  132. on to the ``dst`` list. Returns the value.
  133. """
  134. return self.execute_command('RPOPLPUSH', src, dst)
  135.  
  136. def rpush(self, name, *values):
  137. "Push ``values`` onto the tail of the list ``name``"
  138. return self.execute_command('RPUSH', name, *values)
  139.  
  140. def rpushx(self, name, value):
  141. "Push ``value`` onto the tail of the list ``name`` if ``name`` exists"
  142. return self.execute_command('RPUSHX', name, value)
  143.  
  144. def sort(self, name, start=None, num=None, by=None, get=None,
  145. desc=False, alpha=False, store=None, groups=False):
  146. """
  147. Sort and return the list, set or sorted set at ``name``.
  148.  
  149. ``start`` and ``num`` allow for paging through the sorted data
  150.  
  151. ``by`` allows using an external key to weight and sort the items.
  152. Use an "*" to indicate where in the key the item value is located
  153.  
  154. ``get`` allows for returning items from external keys rather than the
  155. sorted data itself. Use an "*" to indicate where int he key
  156. the item value is located
  157.  
  158. ``desc`` allows for reversing the sort
  159.  
  160. ``alpha`` allows for sorting lexicographically rather than numerically
  161.  
  162. ``store`` allows for storing the result of the sort into
  163. the key ``store``
  164.  
  165. ``groups`` if set to True and if ``get`` contains at least two
  166. elements, sort will return a list of tuples, each containing the
  167. values fetched from the arguments to ``get``.
  168.  
  169. """
  170. if (start is not None and num is None) or \
  171. (num is not None and start is None):
  172. raise RedisError("``start`` and ``num`` must both be specified")
  173.  
  174. pieces = [name]
  175. if by is not None:
  176. pieces.append(Token.get_token('BY'))
  177. pieces.append(by)
  178. if start is not None and num is not None:
  179. pieces.append(Token.get_token('LIMIT'))
  180. pieces.append(start)
  181. pieces.append(num)
  182. if get is not None:
  183. # If get is a string assume we want to get a single value.
  184. # Otherwise assume it's an interable and we want to get multiple
  185. # values. We can't just iterate blindly because strings are
  186. # iterable.
  187. if isinstance(get, basestring):
  188. pieces.append(Token.get_token('GET'))
  189. pieces.append(get)
  190. else:
  191. for g in get:
  192. pieces.append(Token.get_token('GET'))
  193. pieces.append(g)
  194. if desc:
  195. pieces.append(Token.get_token('DESC'))
  196. if alpha:
  197. pieces.append(Token.get_token('ALPHA'))
  198. if store is not None:
  199. pieces.append(Token.get_token('STORE'))
  200. pieces.append(store)
  201.  
  202. if groups:
  203. if not get or isinstance(get, basestring) or len(get) < 2:
  204. raise DataError('when using "groups" the "get" argument '
  205. 'must be specified and contain at least '
  206. 'two keys')
  207.  
  208. options = {'groups': len(get) if groups else None}
  209. return self.execute_command('SORT', *pieces, **options)

基本操作

  1. # 左插入
  2. conn.lpush('k1', 11)
  3. conn.lpush('k1', 22)
  4. # 右插入
  5. conn.rpush('k1', 33)
  6.  
  7. # 左获取
  8. val = conn.lpop('k1')
  9. val = conn.blpop('k1', timeout=10) # 夯住
  10. # 右获取
  11. val = conn.rpop('k1')
  12. val = conn.brpop('k1', timeout=10) # 夯住

左右操作

  1. conn.blpop()
  2. conn.brpop()

阻塞

  1. def list_iter(key, count=3):
  2. index = 0
  3. while True:
  4. data_list = conn.lrange(key, index, index+count-1)
  5. if not data_list:
  6. return
  7. index += count
  8.  
  9. for item in data_list:
  10. yield item
  11.  
  12. result = conn.lrange('k1', 0, 100)
  13. print(result) # [b'22', b'11', b'33']
  14.  
  15. for item in list_iter('k1', 3):
  16. print(item)

通过yield创建一个生成器完成一点一点获取(通过字典操作的源码来的灵感)

 c. 使用字符串

  1. 添加
  2. def set(self, name, value, ex=None, px=None, nx=False, xx=False):
  3. def append(self, key, value):
  4. def mset(self, *args, **kwargs):
  5. def msetnx(self, *args, **kwargs):
  6. def setex(self, name, value, time):
  7. def setnx(self, name, value):
  8.  
  9. 删除
  10. def delete(self, *names):
  11.  
  12. 修改
  13. def setrange(self, name, offset, value):
  14. def decr(self, name, amount=1):
  15. def incr(self, name, amount=1):
  16. def incrbyfloat(self, name, amount=1.0):
  17. def expire(self, name, time):
  18.  
  19. 查询
  20. def mget(self, keys, *args):
  21. def exists(self, name):
  22. def get(self, name):
  23. def getrange(self, key, start, end):
  24. def getset(self, name, value):
  25. def keys(self, pattern='*'):
  26. def strlen(self, name):

基本操作

  1. # -*- coding: utf-8 -*-
  2.  
  3. """
  4. @Datetime: 2019/2/1
  5. @Author: Zhang Yafei
  6. """
  7. import redis
  8.  
  9. pool = redis.ConnectionPool(host='127.0.0.1', port=6379, password='', max_connections=1000)
  10. conn = redis.Redis(connection_pool=pool)
  11.  
  12. # 添加
  13. conn.set('str_k', 'hello') # 为指定key设置value
  14. # {'str_k':'hello'}
  15. conn.mset({'str_k':'hello','str_k1':'world'}) # 设置多个key/value
  16. # {'str_k':'hello', 'str_k1':'world'}
  17. conn.msetnx({'str_k':'msetnx_hello'}) # 若当前key未设定, 则基于mapping设置key/value,结果返回True或False
  18. # {'str_k':'hello'}
  19. conn.setex('str_k2', 'str_v2', 2) # 秒
  20.  
  21. conn.decr('num', amount=1)
  22. conn.incr('num', amount=1)
  23. conn.incrbyfloat('num', amount='1.5')
  24.  
  25. # 删除
  26. conn.delete('str_k1')
  27.  
  28. # 修改
  29. conn.append('str_k', ' world') # 为指定key添加value
  30. # {'str_k':'hello world'}
  31. conn.setrange('str_k', 5, 'world') # 在key对应的的value指定位置上设置值
  32. # b'helloworld'
  33.  
  34. # 查询
  35. print(conn.get('str_k'))
  36. print(conn.get('num'))
  37. print(conn.getrange('str_k', 0, 100))
  38. print(conn.keys())
  39. print(conn.strlen('str_k')) # 长度
  40. print(conn.exists('str_k'))
  41. conn.expire('str_k1', 5)
  42. print(conn.get('str_k1'))
  43.  
  44. # 添加并查询
  45. print(conn.getset('str_k2', 'str_v2'))
  46. # b'str_v2'

字符串示例

d. 集合

  1. 添加
  2. def sadd(self, name, *values):
  3. 删除
  4. def spop(self, name):
  5. def srem(self, name, *values):
  6. 修改
  7. def smove(self, src, dst, value):
  8. 查询
  9. # 判断value是否在key的value中
  10. def sismember(self, name, value):
  11. # 取出key为name的所有元素
  12. def smembers(self, name):
  13. # 随机取出key为name的指定个数元素
  14. def srandmember(self, name, number=None):
  15. # 元素个数
  16. def scard(self, name):
  17. # 差集
  18. def sdiff(self, keys, *args):
  19. def sdiffstore(self, dest, keys, *args):
  20. # 交集
  21. def sinter(self, keys, *args):
  22. def sinterstore(self, dest, keys, *args):
  23. # 并集
  24. def sunion(self, keys, *args):
  25. def sunionstore(self, dest, keys, *args):

集合基本操作

  1. # -*- coding: utf-8 -*-
  2.  
  3. """
  4. @Datetime: 2019/2/1
  5. @Author: Zhang Yafei
  6. """
  7. import redis
  8.  
  9. pool = redis.ConnectionPool(host='127.0.0.1', port=6379, password='', max_connections=1000)
  10. conn = redis.Redis(connection_pool=pool)
  11.  
  12. """
  13. {
  14. 'set_k':{v1,v2,v3},
  15. }
  16. """
  17. # 添加
  18. # conn.sadd('set_k', 3, 4, 5, 6)
  19. # conn.sadd('set_k1', 3, 4, 5, 6)
  20.  
  21. # 删除
  22. # print(conn.spop('set_k'))
  23. # conn.srem('set_k', 2)
  24.  
  25. # 修改
  26. # conn.smove('set_k', 'set_k1', 1)
  27.  
  28. # 查询
  29. print(conn.smembers('set_k'))
  30. print(conn.smembers('set_k1'))
  31. # print(conn.srandmember('set_k', 3))
  32. # print(conn.scard('set_k'))
  33. # print(conn.sismember('set_k', 2))
  34.  
  35. print(conn.sdiff('set_k','set_k1')) # 集合之差
  36. conn.sdiffstore('set_k_k1', 'set_k', 'set_k1')
  37. print(conn.smembers('set_k_k1'))
  38.  
  39. print(conn.sinter('set_k', 'set_k1')) # 集合交集
  40. conn.sinterstore('set_k_k1_inter', 'set_k', 'set_k1')
  41. print(conn.smembers('set_k_k1_inter'))
  42.  
  43. print(conn.sunion('set_k', 'set_k1')) # 集合并集
  44. conn.sunionstore('set_k_k1_union', 'set_k', 'set_k1')
  45. print(conn.smembers('set_k_k1_union'))

集合操作示例

e. 有序集合

  1. 添加
  2. def zadd(self, name, *args, **kwargs):
  3. 删除
  4. def zrem(self, name, *values):
  5. def zremrangebyrank(self, name, min, max): # 删除等级最大者
  6. def zremrangebyscore(self, name, min, max): # 删除分数最小者
  7. 查询
  8. # 查询start-end个数,按分数从小到大
  9. def zrange(self, name, start, end, desc=False, withscores=False,
  10. score_cast_func=float):
  11. # 查询分数在Min,max之间的元素
  12. def zrangebyscore(self, name, min, max, start=None, num=None,
  13. withscores=False, score_cast_func=float):
  14. def zrank(self, name, value): # 等级
  15. def zcard(self, name): # 个数
  16. def zscore(self, name, value): # 得分
  17. def zrevrange(self, name, start, end, withscores=False,
  18. score_cast_func=float): # 逆序:分数从大到小排序
  19. def zrevrangebyscore(self, name, max, min, start=None, num=None,
  20. withscores=False, score_cast_func=float): # 分数处于Min,max之间的从大到小排序

基本操作

  1. # -*- coding: utf-8 -*-
  2.  
  3. """
  4. @Datetime: 2019/2/1
  5. @Author: Zhang Yafei
  6. """
  7. import redis
  8.  
  9. pool = redis.ConnectionPool(host='127.0.0.1', port=6379, password='', max_connections=1000)
  10. conn = redis.Redis(connection_pool=pool)
  11.  
  12. """
  13. {
  14. 'set_k':{
  15. {v1: score1},
  16. {v2: score2},
  17. {v3: score3},
  18. },
  19. }
  20. """
  21. # # 添加
  22. # conn.zadd('zset_k', 'math', 99, 'english', 80, 'chinese', 85, 'sport', 100, 'music', 60)
  23. #
  24. # # 删除
  25. # conn.zrem('zset_k', 'music')
  26. # conn.zremrangebyrank('zset_k', 0, 0) # 按等级大小删除, 删除等级在第min-max个值
  27. # conn.zremrangebyscore('zset_k', 0, 90) # 按分数范围删除, Min < x < max之间的删除
  28.  
  29. # 查询
  30. print(conn.zrange('zset_k', 0, 100))
  31. print(conn.zrevrange('zset_k', 0, 100))
  32. # score从小到大排序, 默认小值先出, 广度优先
  33. results = conn.zrangebyscore('zset_k', 0, 100)
  34. print(results)
  35. print(conn.zcard('zset_k'))
  36. print(conn.zcount('zset_k', 0, 90))
  37. print(conn.zrank('zset_k', 'chinese'))
  38. print(conn.zscore('zset_k', 'chinese'))
  39. print(conn.zrange('zset_k', 0, 100))

有序集合示例

六、基于redis实现队列和栈

  1. # -*- coding: utf-8 -*-
  2.  
  3. """
  4. @Datetime: 2019/1/8
  5. @Author: Zhang Yafei
  6. """
  7. import redis
  8.  
  9. class FifoQueue(object):
  10. def __init__(self):
  11. """
  12. 先进先出队列:利用redis中的列表,双端队列改为先进先出队列
  13. """
  14. self.server = redis.Redis(host='127.0.0.1', port=6379)
  15.  
  16. def push(self, request):
  17. """Push a request"""
  18. self.server.lpush('USERS', request)
  19.  
  20. def pop(self, timeout=0):
  21. """Pop a request"""
  22. data = self.server.rpop('USERS')
  23. return data
  24.  
  25. if __name__ == '__main__':
  26. q = FifoQueue()
  27. q.push(11)
  28. q.push(22)
  29. q.push(33)
  30.  
  31. print(q.pop())
  32. print(q.pop())
  33. print(q.pop())

先进先出队列

  1. # -*- coding: utf-8 -*-
  2.  
  3. """
  4. @Datetime: 2019/1/8
  5. @Author: Zhang Yafei
  6. """
  7. import redis
  8.  
  9. class LifoQueue(object):
  10. """Per-spider LIFO queue."""
  11. def __init__(self):
  12. self.server = redis.Redis(host='127.0.0.1', port=6379)
  13.  
  14. def push(self, request):
  15. """Push a request"""
  16. self.server.lpush("USERS", request)
  17.  
  18. def pop(self, timeout=0):
  19. """Pop a request"""
  20. data = self.server.lpop('USERS')
  21. return data
  22.  
  23. if __name__ == '__main__':
  24. q = LifoQueue()
  25. q.push(11)
  26. q.push(22)
  27. q.push(33)
  28.  
  29. print(q.pop())
  30. print(q.pop())
  31. print(q.pop())

后进先出队列(栈)

  1. # -*- coding: utf-8 -*-
  2.  
  3. """
  4. @Datetime: 2019/1/8
  5. @Author: Zhang Yafei
  6. """
  7. import redis
  8.  
  9. class PriorityQueue(object):
  10. """Per-spider priority queue abstraction using redis' sorted set"""
  11. def __init__(self):
  12. self.server = redis.Redis(host='127.0.0.1', port=6379)
  13.  
  14. def push(self, request,score):
  15. """Push a request"""
  16. # data = self._encode_request(request)
  17. # score = -request.priority
  18. # We don't use zadd method as the order of arguments change depending on
  19. # whether the class is Redis or StrictRedis, and the option of using
  20. # kwargs only accepts strings, not bytes.
  21. self.server.execute_command('ZADD', 'xxxxxx', score, request)
  22.  
  23. def pop(self, timeout=0):
  24. """
  25. Pop a request
  26. timeout not support in this queue class
  27. """
  28. # use atomic range/remove using multi/exec
  29. pipe = self.server.pipeline()
  30. pipe.multi()
  31. pipe.zrange('xxxxxx', 0, 0).zremrangebyrank('xxxxxx', 0, 0)
  32. results, count = pipe.execute()
  33. if results:
  34. return results[0]
  35.  
  36. if __name__ == '__main__':
  37. q = PriorityQueue()
  38.  
  39. # q.push('alex',99) # 广度优先:分值小的优先
  40. # q.push('oldboy',56)
  41. # q.push('eric',77)
  42.  
  43. q.push('alex',-99) # 深度优先:分值大的优先
  44. q.push('oldboy',-56)
  45. q.push('eric',-77)
  46.  
  47. v1 = q.pop()
  48. print(v1)
  49. v2 = q.pop()
  50. print(v2)
  51. v3 = q.pop()
  52. print(v3)

优先级队列

  1. # -*- coding: utf-8 -*-
  2.  
  3. """
  4. @Datetime: 2019/1/8
  5. @Author: Zhang Yafei
  6. """
  7. from scrapy_redis import queue
  8. import redis
  9.  
  10. conn = redis.Redis(host='127.0.0.1', port=6379)
  11. conn.zadd('score',alex=79, oldboy=33,eric=73)
  12.  
  13. # print(conn.keys())
  14.  
  15. v = conn.zrange('score',0,8,desc=True)
  16. print(v)
  17.  
  18. pipe = conn.pipeline()
  19. pipe.multi()
  20. pipe.zrange("score", 0, 0).zremrangebyrank('score', 0, 0)
  21. results, count = pipe.execute()
  22. print(results,count)

redis中的pipeline

七、Django应用

1.自定义使用redis

  1. import redis
  2. POOL = redis.ConnectionPool(host='127.0.0.1', port=6379, max_connections=1000)

utils.py

  1. from django.shortcuts import render, HttpResponse
  2. from app01.utils.redis_pool import POOL
  3. from redis import Redis
  4.  
  5. def index(request):
  6. conn = Redis(connection_pool=POOL)
  7. conn.hset('kkk', 'age', 18)
  8. return HttpResponse('设置成功')
  9.  
  10. def order(request):
  11. conn = Redis(connection_pool=POOL)
  12. val = conn.hget('kkk','age')
  13. return HttpResponse('获取成功{}'.format(val))

views.py

2.使用第三方组件

  1. pip install django-redis
  1. CACHES = {
  2. "default": {
  3. "BACKEND": "django_redis.cache.RedisCache",
  4. "LOCATION": "redis://127.0.0.1:6379",
  5. "OPTIONS": {
  6. "CLIENT_CLASS": "django_redis.client.DefaultClient",
  7. "CONNECTION_POOL_KWARGS": {"max_connections": 100},
  8. # "PASSWORD": "密码",
  9. }
  10. },
  11. "back": {
  12. "BACKEND": "django_redis.cache.RedisCache",
  13. "LOCATION": "redis://192.168.137.191:6379",
  14. "OPTIONS": {
  15. "CLIENT_CLASS": "django_redis.client.DefaultClient",
  16. "CONNECTION_POOL_KWARGS": {"max_connections": 100},
  17. "PASSWORD": "",
  18. }
  19. },
  20. }

配置

  1. from django.shortcuts import render,HttpResponse
  2. from django_redis import get_redis_connection
  3.  
  4. def index(request):
  5. conn = get_redis_connection('back')
  6. conn.hset('kkk', 'age', 18)
  7. return HttpResponse('设置成功')
  8.  
  9. def order(request):
  10. conn = get_redis_connection('back')
  11. val = conn.hget('kkk','age')
  12. return HttpResponse('获取成功{}'.format(val))

使用

3.高级使用  

  a. 全站缓存
   使用中间件,经过一系列的认证等操作,如果内容在缓存中存在,则使用FetchFromCacheMiddleware获取内容并返回给用户,当返回给用户之前,判断缓存中是否已经存在,如果不存在则UpdateCacheMiddleware会将缓存保存至缓存,从而实现全站缓存

  1. MIDDLEWARE = [
  2. 'django.middleware.cache.UpdateCacheMiddleware',
  3. # 其他中间件...
  4. 'django.middleware.cache.FetchFromCacheMiddleware',
  5. ]
  6.  
  7. CACHE_MIDDLEWARE_ALIAS = ""
  8. CACHE_MIDDLEWARE_SECONDS = ""
  9. CACHE_MIDDLEWARE_KEY_PREFIX = ""

  b.单视图

  1. from django.views.decorators.cache import cache_page
  2.  
  3. @cache_page(60 * 15)
  4. def index(request):
  5. # conn = Redis(connection_pool=POOL)
  6. conn = get_redis_connection('back')
  7. conn.hset('kkk', 'age', 18)
  8. return HttpResponse('设置成功')

 c,局部页面缓存

  1. <body>
  2. <h1>asdfasdfasdf</h1>
  3. <div>
  4. asdf
  5. </div>
  6. {# 将指定局部页面放到缓存中的key中#}
  7. {% cache 5000 key %}
  8. <div></div>
  9. {% endcache %}
  10. </body>  
  1. # 缓存放在redis配置
  2. CACHES = {
  3. "default": {
  4. "BACKEND": "django_redis.cache.RedisCache",
  5. "LOCATION": "redis://127.0.0.1:6379",
  6. "OPTIONS": {
  7. "CLIENT_CLASS": "django_redis.client.DefaultClient",
  8. "CONNECTION_POOL_KWARGS": {"max_connections": 100},
  9. # "PASSWORD": "密码",
  10. }
  11. },
  12. }
  13. # 缓存放在文件
  14. CACHES = {
  15. 'default': {
  16. 'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
  17. 'LOCATION': '/var/tmp/django_cache',
  18. }
  19. }
  20. # 缓存放在MemcachedCache
  21. CACHES = {
  22. 'default': {
  23. 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
  24. 'LOCATION': '127.0.0.1:11211',
  25. }
  26. }

补充:rest framework框架访问频率限制推荐放到 redis/memecached

更多详细内容请见

  官方教程:http://www.redis.net.cn/tutorial/3501.html

  

非关系型数据库之Redis的更多相关文章

  1. Redis 01: 非关系型数据库 + 配置Redis

    数据库应用的发展历程 单机数据库时代:一个应用,一个数据库实例 缓存时代:对某些表中的数据访问频繁,则对这些数据设置缓存(此时数据库中总的数据量不是很大) 水平切分时代:将数据库中的表存放到不同数据库 ...

  2. 非关系型数据库(NOSQL)-Redis

    整理一波Redis 简介,与memcached比较 官网:http://redis.io Redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括 ...

  3. 【Redis】(1)-- 关系型数据库与非关系型数据库

    关系型数据库与非关系型数据库 2019-07-02  16:34:48  by冲冲 1. 关系型数据库 1.1 概念 关系型数据库,是指采用了关系模型来组织数据的数据库.关系模型指的就是二维表格模型, ...

  4. Redis非关系型数据库

    1.简介 Redis是一个基于内存的Key-Value非关系型数据库,由C语言进行编写. Redis一般作为分布式缓存框架.分布式下的SESSION分离.分布式锁的实现等等. Redis速度快的原因: ...

  5. python 之操作redis数据库(非关系型数据库,k-v)

    数据库: 1. 关系型数据库 表结构 2. 非关系型数据库 nosql (k - v 速度快),常用的时以下三种: memcache 存在内存里 redis 存在内存里 mangodb 数据还是存在磁 ...

  6. 数据库基础 非关系型数据库 MongoDB 和 redis

    数据库基础 非关系型数据库 MongoDB 和 redis 1 NoSQL简介 访问量增加,频繁的读写 直接访问(硬盘)物理级别的数据,会很慢 ,关系型数据库的压力会很大 所以,需要内存级的读写操作, ...

  7. JavaWeb笔记(十)非关系型数据库Redis

    Redis Redis是一款高性能的NOSQL系列的非关系型数据库 主流的NOSQL产品 键值(Key-Value)存储数据库 相关产品: Tokyo Cabinet/Tyrant.Redis.Vol ...

  8. 非关系型数据库--redis

    0.1 新单词 expire 美 /ɪk'spaɪɚ/ 到期 range 美 /rendʒ/ 范围 idle美 /'aɪdl/ 闲置的 0.2 面试题:mysql和redis和memcached区别? ...

  9. Java Redis系列1 关系型数据库与非关系型数据库的优缺点及概念

    Java Redis系列1 关系型数据库与非关系型数据库的优缺点及概念 在学习redis之前我们先来学习两个概念,即什么是关系型数据库什么是非关系型数据库,二者的区别是什么,二者的关系又是什么? ** ...

随机推荐

  1. 关于.net中的DataSet和DataTable

    DataSet ds = new DataSet(); ds.Tables.Add(); ds.Tables[].Columns.Add("name"); ds.Tables[]. ...

  2. C#常用的网络组件

    常用的网络组件 using System.Net;//为多种网络协议提供统一和简单的编程接口 using System.Net.Mail;//为简单邮件传输协议的服务器提供E-mail发送的类 usi ...

  3. local_irq_disable和disable_irq的区别

    local_irq_disable: local_irq_disable的功能是屏蔽当前CPU上的所有中断,通过操作arm核心中的寄存器来屏蔽到达CPU上的中断,此时中断控制器中所有送往该CPU上的中 ...

  4. c/c++ linux 进程 fork wait函数

    linux 进程 fork wait函数 fork:创建子进程 wait:父进程等待子进程结束,并销毁子进程,如果父进程不调用wait函数,子进程就会一直留在linux内核中,变成了僵尸进程. for ...

  5. 如何制作中文Javadoc包,并导入到Eclipse

    原理:使用chm转换工具将chm文件转换为zip文件,导入eclipse中即可. 准备 JDK1.9 API 中文 谷歌翻译版:http://www.pc6.com/softview/SoftView ...

  6. 清除被占用的8080端口,否则npm run dev无法正常运行

    解决方案一: 1. 打开git-bash2. 输入:netstat -ano查看所有端口信息,如图,找到端口 8080,以及对应的 PID 3.输入:tskill PID 即可杀死进程 解决方案二: ...

  7. 深入理解 path-to-regexp.js 及源码分析

    阅读目录 一:path-to-regexp.js 源码分析如下: 二:pathToRegexp 的方法使用 回到顶部 一:path-to-regexp.js 源码分析如下: 我们在vue-router ...

  8. Linux内核入门到放弃-无持久存储的文件系统-《深入Linux内核架构》笔记

    proc文件系统 proc文件系统是一种虚拟的文件系统,其信息不能从块设备读取.只有在读取文件内容时,才动态生成相应的信息. /proc的内容 内存管理 系统进程的特征数据 文件系统 设备驱动程序 系 ...

  9. PHP中生成UUID

    一.什么是UUID 简单的说UUID就是一串全球唯一的(16进制)数字串. UUID的全拼为“Universally Unique Identifier”,可以译为“通用唯一识别码”.UUID由开源软 ...

  10. Skyline 7 版本TerraExplorer Pro二次开发快速入门

    年底了,给大家整理了一下Skyline 7版本的二次开发学习初级入门教程,献给那些喜欢学习的年轻朋友. 我这整理的是Web控件版本的开发示例,里面页面代码保存成html,都可以直接运行的. 测试使用的 ...