#299 缓存更新策略 [编辑中]
缓存 2019-02-12读缓存的时候,缓存肯定是在数据库前面,这个没有问题。数据变更的时候才是关键。
coding in a complicated world
读缓存的时候,缓存肯定是在数据库前面,这个没有问题。数据变更的时候才是关键。
本地缓存的问题是多个节点之间容易出现数据不一致的情况。我听说过 Java 的一些本地缓存组件,应该其中有一些可以做到多个节点之间的数据同步。如果要是自己实现的话,可以在服务中增加一个刷新缓存的接口调用,其中一个节点刷新缓存时,调用其他节点的刷新接口。也可以引入 MQ,避免这个调用造成的耦合和可能的性能损耗。
数据库没有数据,缓存也没有数据。这样的请求直接穿过缓存读数据库,给数据库照成压力。
system:cache:empty_keys:hhmm, TTL = 123,每次用 SISMEMBER 查询当前分钟和上一分钟某个热 Key 失效,导致大量请求打到数据库。
热 Key 应该预热,然后有一个比较大的 TTL,甚至没有过期时间。最后,通过定时刷新任务来更新这些热 Key。
大量 Key 同时过期, 导致请求直接打到数据库,然后影响整个系统。
大面积的击穿。
其他:
根据之前的经验,或者开发者预判,将部分数据事先写入缓存。
如果提供相关工具,让系统维护人员能够方便快捷地管理缓存数据,能够手动介入缓存的生命周期就更好了。至少在后台提供一个 缓存预热 的按钮。
另起一篇:缓存更新策略
开发过程中发现报错:
Traceback (most recent call last):
...
File "/usr/local/python3.7.3/lib/python3.7/asyncio/streams.py", line 353, in close
return self._transport.close()
File "/usr/local/python3.7.3/lib/python3.7/asyncio/selector_events.py", line 690, in close
self._loop.call_soon(self._call_connection_lost, None)
File "/usr/local/python3.7.3/lib/python3.7/asyncio/base_events.py", line 719, in call_soon
self._check_closed()
File "/usr/local/python3.7.3/lib/python3.7/asyncio/base_events.py", line 508, in _check_closed
raise RuntimeError('Event loop is closed')
RuntimeError: Event loop is closed
经过检查,发现是我在代码中调用了 asyncio.run(协程方法(*args, **kwargs))
总是有些同步代码中需要调用协程方法,没有办法。
改成这样就可以了:
asyncio.get_event_loop().run_until_complete(协程方法(*args, **kwargs))
如果是在线程中就改成:
asyncio.new_event_loop().run_until_complete(协程方法(*args, **kwargs))
否则会报:RuntimeError: There is no current event loop in thread 'Thread-2'.
RuntimeError: This event loop is already running
Traceback (most recent call last):
File "/opt/fuckTheWorld/core/framework.py", line 194, in fuck
value = asyncio.new_event_loop().run_until_complete(lego.get(key))
File "/usr/local/python3.7.3/lib/python3.7/asyncio/base_events.py", line 616, in run_until_complete
return future.result()
File "/opt/fuckTheWorld/core/lego.py", line 30, in execute
data = await super().execute(*args, **options)
File "/opt/fuckTheWorld/lego/client.py", line 476, in execute
return await conn.retry.call_with_retry(
File "/opt/fuckTheWorld/lego/retry.py", line 51, in call_with_retry
return await do()
File "/opt/fuckTheWorld/lego/client.py", line 455, in _send_command_parse_response
return await self.parse_response(conn, command_name, **options)
File "/opt/fuckTheWorld/lego/client.py", line 494, in parse_response
response = await connection.read_response()
File "/opt/fuckTheWorld/lego/connection.py", line 934, in read_response
await self.disconnect()
File "/opt/fuckTheWorld/lego/connection.py", line 822, in disconnect
await self._writer.wait_closed() # type: ignore[union-attr]
File "/usr/local/python3.7.3/lib/python3.7/asyncio/streams.py", line 359, in wait_closed
await self._protocol._get_close_waiter(self)
RuntimeError: Task <Task pending name='Task-45' coro=<Fuck.execute() running at /opt/fuckTheWorld/core/lego.py:30> cb=[_run_until_complete_cb() at /usr/local/python3.7.3/lib/python3.7/asyncio/base_events.py:184]> got Future <Future pending> attached to a different loop
GitHub 上搜索到的这些项目:
ua-parser 和 user-agents 这两个库数据漂亮些,所以,就选这两个库研究研究。
from ua_parser import user_agent_parser
ua_string = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.104 Safari/537.36'
parsed_string = user_agent_parser.Parse(ua_string)
pp.pprint(parsed_string)
http://nginx.org/en/docs/ngx_core_module.html#multi_accept
Syntax: multi_accept on | off;
Default: multi_accept off;
Context: eventsIf multi_accept is disabled, a worker process will accept one new connection at a time. Otherwise, a worker process will accept all new connections at a time.
The directive is ignored if kqueue connection processing method is used, because it reports the number of new connections waiting to be accepted.
如果 multi_accept 禁用, worker 进程一次只接受一个连接
如果启用, 则会一次性接受所有连接。
如果连接处理方法为 kqueue, 则该指令被忽略, 因为 kqueue 会报告新连接的数量(然后好分配)。
PS: 关于连接处理方法, 参考 2017/02/07, Nginx 连接处理方法
感觉有点迷糊, 那么究竟 multi_accept 的作用是什么呢?
Nginx 采用一个 master 进程和多个 worker 进程的模式工作。多个 worker 共享一个 socket (端口绑定), 当请求进来的时候, 被调度到的进程就会去 accept 连接。
multi_accept 的作用就是控制他一次拿走一个连接, 还是拿走所有等待中的连接。
这个参数可以让调度更加高效。如果请求数一直维持在一个很高的水平, 可以设置为 on。
但是在请求数不大的场景下, 那可能会导致同一时刻, 多个进程之间的负载就会不那么均衡, 总是 1C 干活, 7C 围观。
partition_options:
PARTITION BY
{ [LINEAR] HASH(expr)
| [LINEAR] KEY [ALGORITHM={1 | 2}] (column_list)
| RANGE{(expr) | COLUMNS(column_list)}
| LIST{(expr) | COLUMNS(column_list)} }
[PARTITIONS num]
[SUBPARTITION BY
{ [LINEAR] HASH(expr)
| [LINEAR] KEY [ALGORITHM={1 | 2}] (column_list) }
[SUBPARTITIONS num]
]
[(partition_definition [, partition_definition] ...)]
partition_definition:
PARTITION partition_name
[VALUES
{LESS THAN {(expr | value_list) | MAXVALUE}
|
IN (value_list)}]
[[STORAGE] ENGINE [=] engine_name]
[COMMENT [=] 'string' ]
[DATA DIRECTORY [=] 'data_dir']
[INDEX DIRECTORY [=] 'index_dir']
[MAX_ROWS [=] max_number_of_rows]
[MIN_ROWS [=] min_number_of_rows]
[TABLESPACE [=] tablespace_name]
[(subpartition_definition [, subpartition_definition] ...)]
subpartition_definition:
SUBPARTITION logical_name
[[STORAGE] ENGINE [=] engine_name]
[COMMENT [=] 'string' ]
[DATA DIRECTORY [=] 'data_dir']
[INDEX DIRECTORY [=] 'index_dir']
[MAX_ROWS [=] max_number_of_rows]
[MIN_ROWS [=] min_number_of_rows]
[TABLESPACE [=] tablespace_name]
[LINEAR] HASH(expr) 根据值的哈希分区[LINEAR] KEY [ALGORITHM={1 | 2}] (column_list)RANGE{(expr) | COLUMNS(column_list)} 根据值得范围分区LIST{(expr) | COLUMNS(column_list)} 根据不同的值分区COLUMNS 不限于整数
PARTITION BY LIST(column) (
PARTITION a VALUES IN (a1, a2, a3),
PARTITION b VALUES IN (b1, b2, b3),
PARTITION c VALUES IN (c1, c2, c3)
)
PARTITION BY RANGE(column) (
PARTITION 2012q1 VALUES LESS THAN('2012-04-01'),
PARTITION 2012q2 VALUES LESS THAN('2012-07-01'),
PARTITION 2012q3 VALUES LESS THAN('2012-10-01'),
PARTITION 2012q4 VALUES LESS THAN('2013-01-01')
)
PARTITION BY HASH(column) PARTITIONS 128
PARTITION BY HASH(dayofmonth(date)) PARTITIONS 31
SELECT * FROM `information_schema`.`PARTITIONS`;
PARTITION 关键字换成 SUBPARTITION,PARTITIONS 关键字换成 SUBPARTITIONS,接在分区语句后面。比如:
PARTITION BY HASH (prod_id) SUBPARTITION BY HASH (cust_id)
PARTITIONS 4 SUBPARTITIONS 4;
如果是 By Range 分区,一般需要自动创建新的分区,删除久的分区。
比如:
CREATE TABLE `test` (
`id` INT NOT NULL AUTO_INCREMENT,
`date` DATE NOT NULL,
`key` VARCHAR(50) NOT NULL COLLATE 'utf8mb4_general_ci',
`value` VARCHAR(300) NOT NULL COLLATE 'utf8mb4_general_ci',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`, `date`) USING BTREE,
UNIQUE INDEX `key` (`date`, `key`) USING BTREE
)
COLLATE='utf8mb4_general_ci'
/*!50100 PARTITION BY RANGE (to_days(`date`))
(PARTITION p20230123 VALUES LESS THAN (738909) ENGINE = InnoDB,
PARTITION p20230124 VALUES LESS THAN (738910) ENGINE = InnoDB,
PARTITION p20230125 VALUES LESS THAN (738911) ENGINE = InnoDB) */;
然后,通过下面这个 cron 任务自动更新分区:
#!/bin/bash
# 开启调试模式,输出每条执行的命令及其执行结果
set -x
# 检查当前机器 IP 地址中是否包含指定的 VIP(虚拟 IP)
# 确认在主 MySQL 上执行
vip_w="192.168.12.34"
if [ $(/sbin/ip a | grep "${vip_w}" | wc -l) -eq 0 ]; then echo 'WARN: Wrong Machine!!!'; exit 1; fi
# 删除 90 天前的分区
# PS:如果分区不存在,TRUNCATE 不会报错。
delete_date=$(date -d '90 days ago' +%Y%m%d)
mysql -uroot -p123456 -e "USE test; ALTER TABLE test TRUNCATE PARTITION p$delete_date;" # DROP
# 创建未来分区
create_date=$(date -d '7 days' +%Y%m%d)
mysql -uroot -p123456 -e "USE test; ALTER TABLE test ADD PARTITION (PARTITION p$delete_date VALUES LESS THAN (TO_DAYS("$delete_date")));"
参考:https://github.com/altercation/solarized/blob/master/putty-colors-solarized/solarized_dark.reg
Colour21\255,255,255\
Colour20\187,187,187\
Colour19\85,255,255\
Colour18\0,187,187\
Colour17\255,85,255\
Colour16\187,0,187\
Colour15\85,85,255\
Colour14\0,0,187\
Colour13\255,255,85\
Colour12\187,187,0\
Colour11\85,255,85\
Colour10\0,187,0\
Colour9\255,85,85\
Colour8\187,0,0\
Colour7\85,85,85\
Colour6\0,0,0\
Colour5\0,255,0\
Colour4\0,0,0\
Colour3\85,85,85\
Colour2\0,0,0\
Colour1\255,255,255\
Colour0\187,187,187\
Colour0\131,148,150\
Colour1\147,161,161\
Colour2\0,43,54\
Colour3\7,54,66\
Colour4\0,43,54\
Colour5\238,232,213\
Colour6\7,54,66\
Colour7\0,43,56\
Colour8\220,50,47\
Colour9\203,75,22\
Colour10\133,153,0\
Colour11\88,110,117\
Colour12\181,137,0\
Colour13\101,123,131\
Colour14\38,139,210\
Colour15\131,148,150\
Colour16\211,54,130\
Colour17\108,113,196\
Colour18\42,161,152\
Colour19\147,161,161\
Colour20\238,232,213\
Colour21\253,246,227\
有另一个 Priority 头,不知道为什么没有使用。
1:最高2:高级3:一般4:低级5:最低根据网上的一些资料,这几个头已经被垃圾邮件滥用,导致可能会被拦截。
不过,企业邮箱使用应该没有问题。
网易邮箱发送的时候勾选重要,就会设置 X-Priority: 1。
效果就是客户端会提示:“是否发送回执”,确认 or 取消。
注意,不是所有客户端会支持回执。
mdn-request-header = "Disposition-Notification-To" ":" 1#mailbox
# mailbox 就是 local-poart@domain 这种格式
# RFC 中的这个 1# 我也不知道是干嘛用的
值是正常的邮件地址格式就行了,比如:
Disposition-Notification-To: admin@example.com
Disposition-Notification-To: =?utf-8?b?566h55CG5ZGY?= admin@example.com
注意:会和 Return-Path 的域名部分做对比,如果不一致,不会发送回执。
参考:https://www.rfc-editor.org/rfc/rfc2298.html
主要是有这么一段:
Content-Type: message/disposition-notification; name="MDNPart2.txt"
Content-Disposition: inline
Content-Transfer-Encoding: 7bit
Reporting-UA: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Thunderbird/91.7.0
Final-Recipient: rfc822;huang@example.com
Original-Message-ID: <2ec11bc0.59a.1803f61e0f9.Coremail.admin@example.com>
Disposition: manual-action/MDN-sent-manually; displayed
今天研究 secure cookie (安全的 Cookie) 的时候发现这个问题,觉得很好玩,之前还没有遇到过,没有注意。
a = b'hello'
a[0]
# 104
Python3 对 Bytes 类型索引取值返回的是一个整数!!!
这个以后需要小心,如果要像我们预期的那样,取一个字符的话,应该这样:
a = b'hello'
a[0:1]
# h
本文讲的是 Tornado 框架中的 “secure cookie” 实现。