当前位置:   article > 正文

Redis入门--头歌实验使用Redis构建支持程序

Redis入门--头歌实验使用Redis构建支持程序

Redis 可用于处理业务逻辑,作为系统的一部分。除此之外,Redis 还可以帮助和支持系统的其他部分,例如:用于记录日志,进行数据统计,实现配置自动化以及制作一些有趣的实用小程序等。

本实训将通过构建日志记录组件,统计网页访问数据以及 IP 地址库小工具三个实际应用场景展示如何使用 Redis 帮助和支持应用程序。

一、使用Redis记录日志

任务描述

本关任务:使用 Redis 记录日志。

相关知识

为了完成本关任务,你需要掌握:1、redis相关命令,2、python相关命令。

redis相关命令
lpush

: 将一个值插入到列表头部,保证后插入的在最前面。

  1. conn = redis.Redis()
  2. pipe = conn.pipeline()
  3. pipe.lpush("testlist", "c")
ltrim:

让列表只保留指定区间内的元素。

  1. conn = redis.Redis()
  2. pipe = conn.pipeline()
  3. pipe.ltrim("testlist", 0, 1)
multi:

事务块内的多条命令会按照先后顺序被放进一个队列当中,最后由 EXEC 命令原子性(atomic)地执行队列里的命令。

  1. conn = redis.Redis()
  2. pipe = conn.pipeline()
  3. pipe.multi() # 标记事务开始
  4. pipe.incr("user_id")
  5. pipe.incr("user_id")
  6. pipe.incr("user_id")
  7. pipe.EXEC() #执行
watch:

用于监视一个 key ,如果在事务执行之前这个 key 的值被改动,那么事务将被打断。一般和MULTI命令配合使用,解决并发过程中重复操作的问题。

例子:商品下单时是需要对库存数stock_count进行减库存操作,通过watchmulti来锁库存。

  1. conn = redis.Redis()
  2. pipe = conn.pipeline()
  3. try:
  4. pipe.watch('stock_count’)
  5. count = int(pipe.get('stock_count'))
  6. if count > 0: # 有库存
  7. # 事务开始
  8. pipe.multi()
  9. pipe.set('stock_count', count - 1)
  10. # 事务结束
  11. pipe.execute()
  12. except redis.exceptions.WatchError: #抓取stock_count改变异常
zincrby:

对有序集合中指定成员的分数加上增量 increment,用于统计。

key:zset_name , 值:a1 进行递增

  1. conn = redis.Redis()
  2. pipe = conn.pipeline()
  3. pipe.zincrby("zset_name","a1")

执行后效果

zset_name:a1 1

rename

命令用于修改 key 的名称

key:zset_name 修改为zset_name1

  1. conn = redis.Redis()
  2. pipe = conn.pipeline()
  3. pipe.rename("zset_name", "zset_name1")

执行后效果

zset_name1:a1 1

python相关命令

返回当前时间的时间戳

time.time()

返回时间格式化字符串

time.asctime()

执行结果:Tue Dec 11 15:07:14 2018

获取当前小时数:例如当前时间15:07:14

time.localtime().tm_hour

执行结果:15

编程要求

Begin-End区域编写log_to_redis(message, level=logging.INFO):函数,实现记录最新日志的功能,具体参数与要求如下:

  • 方法参数message为日记内容;
  • 定义一个 redis 列表,列表keylog:recent:info, 列表value时间-message,要求每次新的日记内容需存在列表头部,并只保留最新的5000个元素。

注意:value中的时间取time.asctime()

编写log_to_redis_by_frequency(message, level=logging.INFO):函数,实现统计常见日志次数及写入最新日记、并对历史日志记录进行迁移,具体参数与要求如下:

  • 在方法log_to_redis_by_frequency中,方法参数message为日记内容;
  • 定义一个 redis 列表,列表keylog:count:info,列表valuemessage
  • 定义一个 redis 字符串,字符串keylog:count:info:start,字符串value为当前小时数;
  • 对历史日志记录进行迁移功能实现:当value 小于当前时间小时数,则重命名redis列表,修改列表keylog:count:info:lasthour,修改redis 字符串值为当前时间小时数;
  • 统计常见日志次数及写入最新日记功能实现:存储redis列表的成员为message,分数自增1,并调用log_to_redis方法存最新消息;
  • 考虑到历史日记记录迁移的并发问题,使用swatch监控log:count:info,如果log:count:info已经修改,则捕获redis.exceptions.WatchError,从而退出。
  1. ```python
  2. #!/usr/bin/env python
  3. #-*- coding:utf-8 -*-
  4. import time
  5. import redis
  6. import logging
  7. conn = redis.Redis()
  8. LOGLEVEL = {
  9. logging.DEBUG: "debug",
  10. logging.INFO: "info",
  11. logging.WARNING: "warning",
  12. logging.ERROR: "error"
  13. }
  14. # 记录最新日志
  15. def log_to_redis(message, level=logging.INFO):
  16. log_list = "log:recent:" + LOGLEVEL[level]
  17. # 将消息格式化为时间戳和消息内容
  18. message = time.asctime() + ' - ' + message
  19. # 创建 Redis pipeline
  20. pipe = conn.pipeline()
  21. # 将消息推入列表
  22. pipe.lpush(log_list, message)
  23. # 保留最新的5000条日志
  24. pipe.ltrim(log_list, 0, 4999)
  25. # 执行 pipeline
  26. pipe.execute()
  27. # 记录常见日志
  28. def log_to_redis_by_frequency(message, level=logging.INFO):
  29. log_key = "log:count:" + LOGLEVEL[level]
  30. start_key = log_key + ":start"
  31. # 创建 Redis pipeline
  32. pipe = conn.pipeline()
  33. end = time.time() + 10
  34. while time.time() < end:
  35. try:
  36. # 监视 start_key
  37. pipe.watch(start_key)
  38. cur_hour = time.localtime(time.time()).tm_hour
  39. start_hour = pipe.get(start_key)
  40. pipe.multi()
  41. # 检查是否跨小时
  42. if start_hour and start_hour < cur_hour:
  43. # 重命名键和更新起始时间
  44. pipe.rename(log_key, log_key + ":lasthour")
  45. pipe.set(start_key, cur_hour)
  46. # 增加消息在有序集合中的计数
  47. pipe.zincrby(log_key, 1, message)
  48. # 执行 pipeline
  49. pipe.execute()
  50. # 同时将消息记录到最新日志
  51. log_to_redis(message, level)
  52. return True
  53. except redis.exceptions.WatchError:
  54. pass
  55. ```

二、使用Redis计数和统计数据

任务描述

本关任务:使用 Redis 统计网站的每日 PV 和每日 UV

相关知识

每日 PV(Page View) 即一天的页面浏览量或点击量,衡量网站用户访问的网页数量。

每日 UV(Unique Visitor) 即一天的独立访客数,统计一天内访问某站点的用户数(一般以 cookie 为依据)。

为了完成本关任务,你需要掌握:1redis相关命令,2python相关命令。

redis相关命令
hincrby:

为哈希中指定域的值增加增量 increment,用于统计。

  1. conn = redis.Redis()
  2. conn.hincrby("testhash", "field1", 1)
zadd:

将成员加入到有序集合中,并确保其在正确的位置上。

  1. conn = redis.Redis()
  2. conn.zadd("testzset", "member2", 3)
  3. conn.zadd("testzset", "member1", 2)
  4. conn.zadd("testzset", "member3", 1)
hget:

从哈希中获取指定域的值。

  1. conn = redis.Redis()
  2. conn.hget("testhash", "field1")
zcount:

统计有序集合中分值在 minmax 之间(包括等于 minmax)的成员的数量。

  1. conn = redis.Redis()
  2. conn.zadd("testzset", "member1", 10)
  3. conn.zadd("testzset", "member2", 70)
  4. conn.zadd("testzset", "member3", 50)
  5. conn.zadd("testzset", "member4", 100)
  6. conn.zcount("testzset", 60, 100)
python相关命令

返回当日日期:

time.strftime("%Y%m%d")

执行结果:20181212

返回当日 0 点时间元组:

time.strptime('20181101', '%Y%m%d')

执行结果为:

time.struct_time(tm_year=2018, tm_mon=11, tm_mday=1, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=3, tm_yday=305, tm_isdst=-1)

返回时间元组对应的时间戳:

  1. test_struck_time = time.strptime('20181101', '%Y%m%d')
  2. time.mktime(test_struck_time)

执行结果为:1541001600.0

编程要求

Begin-End区域编写 record_viewer(user_id) 函数,实现累记 PVUV 值的功能,具体参数与要求如下:

  • 方法参数 user_id 为访问用户 ID
  • 累计PV实现:为哈希键 page_view 中的当前日期域的值累加 1,当前日期域格式为20181101
  • 累计UV实现:将该访问用户 ID 加入到有序集合键 unique_visitor 中,并置分值为当前时间

编写 stats_viewer(stats_day) 函数,实现统计 PV 值和 UV 值并返回的功能,具体参数与要求如下:

  • 方法参数 stats_day 为需要统计的日期,格式为:20181101
  • 统计PV实现:从哈希键 page_view 中得到该日期域的值,记为 PV
  • 统计UV实现:统计有序集合键 unique_visitor 中分值介于需要统计的日期的 0 点和 24 点时间戳之间的成员数量,记为 UV
  • 返回 PV 值和 UV 值,格式为 [PV, UV]
  1. #!/usr/bin/env python
  2. #-*- coding:utf-8 -*-
  3. import time
  4. import redis
  5. conn = redis.Redis()
  6. # 使用计数器记录 PV 和 UV
  7. def record_viewer(user_id):
  8. current_date = time.strftime('%Y%m%d', time.localtime())
  9. timestamp = time.time()
  10. pipe = conn.pipeline()
  11. pipe.multi()
  12. # 累计 PV
  13. pipe.hincrby('page_view', current_date, 1)
  14. # 累计 UV
  15. pipe.zadd('unique_visitor',user_id ,timestamp)
  16. pipe.execute()
  17. # 使用计数器统计数据
  18. def stats_viewer(stats_day):
  19. pv_value = conn.hget('page_view', stats_day)
  20. start_timestamp = int(time.mktime(time.strptime(stats_day, "%Y%m%d")))
  21. end_timestamp = start_timestamp + 86400
  22. uv_value = conn.zcount('unique_visitor', start_timestamp, end_timestamp)
  23. return [pv_value, uv_value]

 三、使用Redis实现IP地址库

任务描述

本关任务:使用 Redis 编写一个 IP 地址库。

相关知识

为了完成本关任务,你需要掌握:1redis相关命令,2python相关命令,3.IP如何转换为整数。

redis相关命令

zadd:将成员加入到有序集合中,并确保其在正确的位置上。

  1. conn = redis.Redis()
  2. conn.zadd("testzset", "member2", 3)
  3. conn.zadd("testzset", "member1", 2)
  4. conn.zadd("testzset", "member3", 1)

zrevrangebyscore:返回有序集合中分值介于 maxmin 之间(包括等于 maxmin)的所有成员,并按分值递减(从大到小)的次序排列。

  1. conn = redis.Redis()
  2. conn.zadd("testzset", "member1", 10)
  3. conn.zadd("testzset", "member2", 70)
  4. conn.zadd("testzset", "member3", 50)
  5. conn.zadd("testzset", "member4", 100)
  6. conn.zrevrangebyscore("testzset", 100, 50)
python相关命令

将字符串转换为十进制数:

int("110", 10)

打开文件:

csv_file = open(filename)

执行后得到一个文件对象。

获得 CSV 文件的所有行:

data = csv.reader(csv_file)

执行后得到 CSV 文件中所有行的列表。

遍历 CSV 文件中的所有行:

  1. for count, row in enumerate(data):
  2. print(count)
  3. print(row)

执行结果:

  1. 0
  2. ["第一行第一列", "第一行第二列", "第一行第三列"]
  3. 1
  4. ["第二行第一列", "第二行第二列", "第二行第三列"]
  5. ...

判断一个字符串是否为数值型字符串:

  1. "123".isdigit()
  2. "123a".isdigit()

执行结果:

  1. True
  2. False

使用分隔符拆分字符串:

"1.2.3.4".split(".")

执行结果:['1', '2', '3', '4']

判断字符串是否包含指定字符:

  1. 'a' in 'apple'
  2. 'b' in 'apple'

执行结果:

  1. True
  2. False
IP如何转换为整数

192.168.1.1 转换为长整型数的过程如下:

编程要求

Begin-End区域编写 ip2long(ip_address) 函数,实现将 IP 转换成整数的功能,具体参数与要求如下:

  • 方法参数 ip_address 为待转换的 IP 地址;
  • IP转换成长整型数实现:使用分隔符 . 将 IP 地址拆分为四段,将每一段转换为十进制数,并使用迭代的方法将整个 IP 地址转换为长整型数;
  • 返回转换好的整数值。

编写 import_ip_to_redis(filename) 函数,实现将 IP 地址库导入Redis的功能,具体参数与要求如下:

  • 方法参数 filename 为 IP 地址库文件名;
  • 数据导入Redis实现:打开并遍历该CSV文件,文件第一列为IP段的起始值,第二列为IP段的结束值,第三列为IP段所属的城市ID,例如

- 若`IP`段起始值包含英文字符或为空串,则不处理该行。 - 若`IP`段起始值是`IP`格式,则调用`ip2long`方法转换为整数。 - 若`IP`段起始值为数值型字符串,则转换为十进制数。 - 为保持有序集合中城市`ID`唯一,将城市`ID`加`_`加当前行索引值做为成员,经过上述处理的`IP`段起始值做为分值存入有序集合 `ip2city` 中。

编写 find_city_by_ip(ip_address) 函数,实现根据输入的IP地址找到所属的城市ID的功能,具体参数与要求如下:

  • 方法参数 ip_address 为待查找的 IP 地址;
  • 调用ip2long方法将待查找的 IP 地址转换成整数,便于在有序集合中查找;
  • 查找所属城市ID实现:从有序集合 ip2city 中查找出分值小于等于上述整数的所有成员,其中分值最大的成员则为该IP地址所属的城市ID
  • 还原城市ID实现:去除上述城市ID_字符及其之后的字符;
  • 返回城市ID
  1. #!/usr/bin/env python
  2. #-*- coding:utf-8 -*-
  3. import csv
  4. import time
  5. import redis
  6. conn = redis.Redis()
  7. # 将 IP 转换为整型数据
  8. def ip2long(ip_address):
  9. # 将 IP 地址字符串转换为整型
  10. ip_parts = ip_address.split('.')
  11. ip_integer = 0
  12. for part in ip_parts:
  13. ip_integer = ip_integer * 256 + int(part, 10)
  14. return ip_integer
  15. # 将 IP 地址库导入 Redis
  16. def import_ip_to_redis(filename):
  17. # 从CSV文件中读取IP地址和城市信息,将IP转换为整型并存入Redis
  18. csv_file = open(filename)
  19. ori_data = csv.reader(csv_file)
  20. for count, row in enumerate(ori_data):
  21. start_ip = row[0] if row else ""
  22. if 'i' in start_ip:
  23. continue
  24. if '.' in start_ip:
  25. start_ip = ip2long(start_ip)
  26. elif start_ip.isdigit():
  27. start_ip = int(start_ip, 10)
  28. else:
  29. continue
  30. city_id = row[2] + '_' + str(count)
  31. conn.zadd("ip2city", city_id, start_ip)
  32. # 查找 IP 所属城市 ID
  33. def find_city_by_ip(ip_address):
  34. # 查找给定IP地址所属的城市ID
  35. ip_address = ip2long(ip_address)
  36. city_id = conn.zrevrangebyscore("ip2city", ip_address, 0)
  37. city_id = city_id[0].split("_")[0]
  38. return city_id

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/378211
推荐阅读
相关标签
  

闽ICP备14008679号