Redis (Remote Dictionary Server) 是由 Salvatore Sanfilippo(antirez) 开发的开源数据库,基于内存的 Key-Value 类型的 NoSQL 。目前在 DB Engines Ranking K-V 数据库中排行第一 1。
Redis 是 REmote DIctionary Server 远程字典服务的缩写,他以字典结构存储数据,并允许其他应用通过 TCP 协议来读写字典中的内容。
Redis 支持很多的特性:
Redis Cluster 常用 5 种数据结构 (String, Lists, Sets, Sorted Set, Hash) 以单进程方式处理请求,数据持久化和网络 Socket IO 等工作是异步进程。
在 Debian/Ubuntu/Linux Mint 下直接安装即可,但是 redis 对内核有要求,如果安装失败的时候, -uname -a
看一下自己的内核,如果版本太低就升级一下。
sudo apt-get install redis-server
安装成功之后就可以使用
sudo service redis-server status # 查看当前状态
sudo service redis-server stop/start # 等等来控制 redis-server 的状态
最方便最快捷的安装方式,如果使用 docker 也可以使用 docker 中官方的源。
官网下载 https://redis.io/download
下载最新的稳定版 Redis,可以从 http://download.redis.io/redis-stable.tar.gz 获取最新稳定版
curl -O http://download.redis.io/redis-stable.tar.gz
解压 tag.gz
tar xzvf redis-stable.tar.gz
进入解压后目录
cd redis-stable
编译和安装,运行 make 命令
make
当二进制文件编译完成之后,运行 test 确保一切都正确
make test
当所有测试跑通过之后安装到系统
sudo make install
运行 test 的时候报了一个错误:
*** [err]: Test replication partial resync: ok psync (diskless: yes, reconnect: 1) in tests/integration/replication-psync.tcl
参考该 issue 使用单核运行 test
taskset -c 1 sudo make test
在 etc 目录下新建 redis 配置文件目录
sudo mkdir /etc/redis
将默认配置文件拷贝到配置目录
sudo cp redis.conf /etc/redis
编辑配置文件
sudo vim /etc/redis/redis.conf
修改 supervised 为 systemd
# If you run Redis from upstart or systemd, Redis can interact with your
# supervision tree. Options:
# supervised no - no supervision interaction
# supervised upstart - signal upstart by putting Redis into SIGSTOP mode
# supervised systemd - signal systemd by writing READY=1 to $NOTIFY_SOCKET
# supervised auto - detect upstart or systemd method based on
# UPSTART_JOB or NOTIFY_SOCKET environment variables
# Note: these supervision methods only signal "process is ready."
# They do not enable continuous liveness pings back to your supervisor.
supervised systemd
接下来,寻找 dir
配置, 该参数制定 Redis 存储数据的目录,需要一个 Redis 有写权限的位置,使用 /var/lib/redis
.
# The working directory.
#
# The DB will be written inside this directory, with the filename specified
# above using the 'dbfilename' configuration directive.
#
# The Append Only File will also be created inside this directory.
#
# Note that you must specify a directory here, not a file name.
dir /var/lib/redis
修改完毕,保存关闭。
创建 redis.service
文件
sudo vim /etc/systemd/system/redis.service
[Unit] 单元中提供描述,和启动需要在网络可用之后。[Service] 中定义服务的具体动作,自定义用户 redis,以及 redis-server
的地址。
[Unit]
Description=Redis In-Memory Data Store
After=network.target
[Service]
User=redis
Group=redis
ExecStart=/usr/local/bin/redis-server /etc/redis/redis.conf
ExecStop=/usr/local/bin/redis-cli shutdown
Restart=always
[Install]
WantedBy=multi-user.target
创建用户,组
sudo adduser --system --group --no-create-home redis
创建文件夹
sudo mkdir /var/lib/redis
给予权限
sudo chown redis:redis /var/lib/redis
修改权限,普通用户无法访问
sudo chmod 770 /var/lib/redis
启动
sudo systemctl start redis
查看状态
sudo systemctl status redis
显示
sudo service redis status
● redis.service - Redis In-Memory Data Store
Loaded: loaded (/etc/systemd/system/redis.service; disabled; vendor preset: enabled)
Active: active (running) since Sat 2017-04-22 18:59:56 CST; 2s ago
Main PID: 28750 (redis-server)
CGroup: /system.slice/redis.service
└─28750 /usr/local/bin/redis-server 127.0.0.1:6379
Apr 22 18:59:56 ev redis-server[28750]: `-._ `-._`-.__.-'_.-' _.-'
Apr 22 18:59:56 ev redis-server[28750]: `-._ `-.__.-' _.-'
Apr 22 18:59:56 ev redis-server[28750]: `-._ _.-'
Apr 22 18:59:56 ev redis-server[28750]: `-.__.-'
Apr 22 18:59:56 ev redis-server[28750]: 28750:M 22 Apr 18:59:56.445 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower valu
Apr 22 18:59:56 ev redis-server[28750]: 28750:M 22 Apr 18:59:56.445 # Server started, Redis version 3.2.8
Apr 22 18:59:56 ev redis-server[28750]: 28750:M 22 Apr 18:59:56.445 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.ov
Apr 22 18:59:56 ev redis-server[28750]: 28750:M 22 Apr 18:59:56.445 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage
Apr 22 18:59:56 ev redis-server[28750]: 28750:M 22 Apr 18:59:56.445 * DB loaded from disk: 0.000 seconds
Apr 22 18:59:56 ev redis-server[28750]: 28750:M 22 Apr 18:59:56.445 * The server is now ready to accept connections on port 6379
使用 redis-cli
客户端测试。
redis-cli
然后运行 ping
,会得到 PONG。
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> set test "It's working"
OK
127.0.0.1:6379> get test
"It's working"
127.0.0.1:6379> exit
然后重启 redis
sudo systemctl restart redis.service
然后进入 redis-cli
:
127.0.0.1:6379> get test
"It's working"
如果能够获得,就证明配置好了。
开机启动
sudo systemctl enable redis
在启动了 redis 之后就可以再熟悉一下他的命令 了。
Redis 实例提供了多个用来存储数据库的字典,客户端可以用来指定将数据存储在哪个数据库中,类似关系型数据库可以新建很多个数据库,可以将 Redis 的每一个字典都理解成为一个数据库。
每个数据库对外都是以一个从 0 开始的递增数字命名, Redis 默认支持 16 个数据库。 客户端与 Redis 建立连接之后会自动选择 0 号数据库,不过随时可以使用 SELECT 命令来更换数据库,比如选择 1 号数据库 SELECT 1
.
注意:Redis 不支持自定义数据库名,每个数据库都以编号命名;Redis 也不支持为每一个数据库设置不同的访问密码;多个数据库之间并不是完全隔离, FLUSHALL
命令可以清空 Redis 实例中所有数据库数据。
参考: https://www.digitalocean.com/community/tutorials/how-to-install-and-configure-redis-on-ubuntu-16-04
之前也介绍过 di disk information,不过系统默认不带,需要自己安装,如果遇到没有权限安装时,就可以使用 df 来查看当前机器剩余磁盘空间。
df 全称 disk filesystem,用于显示 Linux 系统磁盘利用率,通常也用来查看磁盘占用空间。
df [OPTIONS] [FILE]
直接使用不加任何参数会显示所有当前被挂载的文件系统的可用空间。默认会以 1KB 为单位显示。
选项:
-a 全部文件系统列表
-h 方便阅读方式显示
-H 等于“-h”,但是计算式,1K=1000,而不是 1K=1024
-i 显示 inode 信息
-k 区块为 1024 字节
-l 只显示本地文件系统
-m 区块为 1048576 字节
--no-sync 忽略 sync 命令
-P 输出格式为 POSIX
--sync 在取得磁盘信息前,先执行 sync 命令
-T 展示文件系统类型,比如 ext4, tmpfs, 等等
直接使用 df
,显示设备名称、总块数、总磁盘空间、已用磁盘空间、可用磁盘空间和文件系统上的挂载点。
Filesystem 1K-blocks Used Available Use% Mounted on
udev 8126360 0 8126360 0% /dev
tmpfs 1629376 75432 1553944 5% /run
/dev/sdb1 240230912 185617700 42387064 82% /
tmpfs 8146864 546884 7599980 7% /dev/shm
tmpfs 5120 4 5116 1% /run/lock
tmpfs 8146864 0 8146864 0% /sys/fs/cgroup
/dev/loop1 83712 83712 0 100% /snap/core/4206
/dev/loop4 259584 259584 0 100% /snap/electronic-wechat/7
cgmfs 100 0 100 0% /run/cgmanager/fs
tmpfs 1629376 72 1629304 1% /run/user/1000
/dev/sda3 723180576 70584 686351464 1% /media/user/add8bd89-da2a-4573-ac6e-7ec44f8c5414
/dev/loop5 84096 84096 0 100% /snap/core/4327
/dev/loop3 95872 95872 0 100% /snap/slack/6
/dev/loop6 88704 88704 0 100% /snap/core/4407
df 命令输出:
优化输出,以更加易读的方式输出结果
df -h
可以显示比较友好的输出
Filesystem Size Used Avail Use% Mounted on
udev 7.8G 0 7.8G 0% /dev
tmpfs 1.6G 74M 1.5G 5% /run
/dev/sdb1 230G 178G 41G 82% /
tmpfs 7.8G 534M 7.3G 7% /dev/shm
tmpfs 5.0M 4.0K 5.0M 1% /run/lock
tmpfs 7.8G 0 7.8G 0% /sys/fs/cgroup
/dev/loop1 82M 82M 0 100% /snap/core/4206
/dev/loop4 254M 254M 0 100% /snap/electronic-wechat/7
cgmfs 100K 0 100K 0% /run/cgmanager/fs
tmpfs 1.6G 72K 1.6G 1% /run/user/1000
/dev/sda3 690G 69M 655G 1% /media/mi/add8bd89-da2a-4573-ac6e-7ec44f8c5414
/dev/loop5 83M 83M 0 100% /snap/core/4327
/dev/loop3 94M 94M 0 100% /snap/slack/6
/dev/loop6 87M 87M 0 100% /snap/core/4407
df -hT
其中 -T
参数显示文件类型 ext4 等等
Filesystem Type Size Used Avail Use% Mounted on
udev devtmpfs 7.8G 0 7.8G 0% /dev
tmpfs tmpfs 1.6G 74M 1.5G 5% /run
/dev/sdb1 ext4 230G 178G 41G 82% /
说明:
-h
目前磁盘空间和使用情况 以更易读的方式显示-H
和上面的 -h 参数相同,换算时采用 1000 而不是 1024 进行容量转换-k
以单位 1K 显示磁盘的使用情况使用 -i
参数
df -i
输出结果为
Filesystem Inodes IUsed IFree IUse% Mounted on
udev 2031887 539 2031348 1% /dev
tmpfs 2036709 941 2035768 1% /run
/dev/sdb1 15269888 1896147 13373741 13% /
tmpfs 2036709 497 2036212 1% /dev/shm
tmpfs 2036709 4 2036705 1% /run/lock
tmpfs 2036709 18 2036691 1% /sys/fs/cgroup
/dev/loop0 28782 28782 0 100% /snap/electronic-wechat/7
cgmfs 2036709 14 2036695 1% /run/cgmanager/fs
tmpfs 2036709 132 2036577 1% /run/user/1000
/dev/loop8 12810 12810 0 100% /snap/core/6034
/dev/sda1 14082048 1673302 12408746 12% /media/mi/3d1b7e3e-c184-4664-9555-2b088997f2c8
/dev/sda3 45932544 11 45932533 1% /media/mi/8803a3c6-1561-4957-b9b3-e60d5688d1a6
/dev/sdc 12229708 20 12229688 1% /media/mi/data
inode (index node) 是一个在类 Unix 文件系统下用来描述文件系统对象(文件或者目录)的数据结构。每一个 indoe 保存对象数据的属性和磁盘块地址。文件类型对象属性包括 metadata(修改时间,访问属性等)和文件的所有者以及文件权限。
df -ih
显示 inodes
Filesystem Inodes IUsed IFree IUse% Mounted on
udev 2.0M 520 2.0M 1% /dev
tmpfs 2.0M 888 2.0M 1% /run
/dev/sdb1 15M 1.8M 13M 12% /
常见的文件系统有 Windows 下的 FAT32,NTFS,Unix 系统下的 ext3, ext4,添加 -T
参数在输出结果中增加一列来表示当前分区的文件系统。
df -T
而如果要在结果中筛选特定文件系统的分区可以使用 -t ext4
,比如要过滤出只显示 ext4 分区
df -t ext4
查看磁盘占用 du
假如你的 250G 的系统盘即将存满,下面的方式可以缓解一下硬盘压力。
移除不再使用的 package
sudo apt autoremove
sudo apt-get autoclean
查看系统日志占用:
journalctl --disk-usage
sudo journalctl --vacuum-time=3d
查看 SNAP 占用
du -h /var/lib/snapd/snaps
snap list --all
snap remove some-package
Celery 简单来说就是一个分布式[[消息队列]]。简单、灵活且可靠,能够处理大量消息,它是一个专注于实时处理的任务队列,同时也支持异步任务调度。Celery 不仅可以单机运行,也能够同时在多台机器上运行,甚至可以跨数据中心。
Celery 中比较关键的概念:
一个典型的 Celery 使用场景就是,当用户在网站注册时,请求可以立即返回而不用等待发送注册激活邮件之后返回,网站可以将发送邮件这样的耗时不影响主要流程的操作放到消息队列中,Celery 就提供了这样的便捷。
直接使用 python 工具 pip 或者 easy_install 来安装:
$ pip install celery
Celery 支持多种 broker, 但主要以 RabbitMQ 和 Redis 为主,其他都是试验性的,虽然也可以使用, 但是没有专门的维护者。如何在 RabbitMQ 和 Redis 之间选择呢?
RabbitMQ is feature-complete, stable, durable and easy to install. It’s an excellent choice for a production environment.
Redis is also feature-complete, but is more susceptible to data loss in the event of abrupt termination or power failures.
Celery 官方明确表示推荐在生产环境下使用 RabbitMQ,Redis 存在丢数据的问题。所以如果你的业务可以容忍 worker crash 或者电源故障导致的任务丢失,采用 redis 是个不错的选择,本篇就以 redis 为例来介绍。
Celery 对于 redis 的支持需要安装相关的依赖,以下命令可以同时安装 celery 和 redis 相关的依赖,但是 redis server 还是必须单独安装的。
$ pip install -U celery[redis] # -U 的意思是把所有指定的包都升级到最新的版本
Celery 本身的配置项是很多的,但是如果要让它跑起来,你只需要加一行配置:
BROKER_URL = 'redis://localhost:6379/0'
这一行就是告诉 celery broker 的地址和选择的 redis db,默认是 0。接下来用个很简单的例子来介绍 celery 是如何使用的:
# task.py
from celery import Celery
broker = 'redis://127.0.0.1:6379/0'
app = Celery('tasks', broker=broker)
@app.task()
def add(x, y):
return x + y
上述代码创建了一个 celery 的实例 app,可以通过它来创建任务和管理 workers。在上面的例子中,我们创建了一个简单的任务 task,它返回了两个数相加后的结果。然后启动 celery 服务,通过它来监听是否有任务要处理。
$ celery worker -A task -l info
-A
选项指定 celery 实例 app 的位置,本例中 task.py
中自动寻找,当然可以直接指定 celery worker -A task.app -l info
-l
选项指定日志级别, -l
是 --loglevel
的缩略形式其他更多选项通过 celery worker –-help
查看
然后我们再打开一个 shell 窗口,进入 python 控制台去调用 add 任务:
>>> from task import add
>>> add.delay(1, 2)
<AsyncResult: 42ade14e-c7ed-4b8d-894c-1ca1ec7ca192>
delay()
是 apply_async
的简写,用于一个任务消息(task message)。我们发现 add 任务并没有返回结果 3,而是一个对象 AsyncResult,它的作用是被用来检查任务状态,等待任务执行完毕或获取任务结果,如果任务失败,它会返回异常信息或者调用栈。
我们先尝试获取任务的执行结果:
>>> result = add.delay(1, 2)
>>> result.get()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/lib/python2.7/dist-packages/celery/result.py", line 169, in get
no_ack=no_ack,
File "/usr/local/lib/python2.7/dist-packages/celery/backends/base.py", line 604, in _is_disabled
'No result backend configured. '
NotImplementedError: No result backend configured. Please see the documentation for more information.
报错了:No result backend configured. 错误信息告诉我们没有配置 result backend。因为 celery 会将任务的 状态或结果保存在 result backend,result backend 的选择也有很多,本例中依然选用 redis 作为 result backend。
我们修改 task.py 的代码,添加上 result backend 的设置,保存后重启 celery worker。
# task.py
...
app = Celery('tasks', backend='redis://localhost', broker='redis://localhost//')
...
然后重新调用 add task:
>>> from task import add
>>> result = add.delay(1,2)
>>> result.get()
3
flower 是一个 celery 的监控工具,它提供了一个图形用户界面,可以极大的方便我们监控任务的执行过程, 执行细节及历史记录,还提供了统计功能。
flower 安装
pip install flower
or:
easy_install flower
flower 使用简介,首先启动通过命令行启动 flower 进程:
flower -A proj --port=5555
然后打开浏览器 http://localhost:5555/
celery flower
调用一个异步任务,这也是最常用的任务类型之一,delay 与它的作用相同,只是 delay 不支持 apply_async
中额外的参数。该方法有几个比较重要的参数,在实际应用中会经常用到:
countdown: 任务延迟执行的秒数,默认立即执行; eta:任务被执行的绝对时间
Celery 同样也支持定时任务:
from datetime import timedelta
from celery.schedules import crontab
app.conf.beat_schedule = {
# Executes every Monday morning at 7:30 A.M
'add-every-monday-morning': {
'task': 'tasks.add',
'schedule': crontab(hour=7, minute=30, day_of_week=1),
'args': (20, 20),
},
# execute every ten minutes
'every_ten_minutes': {
'task': 'worker.cntv.cntv_test',
'schedule': timedelta(minutes=10),
'args': ('args1',),
'options': {
'queue': 'queue_name'
}
},
}
要启动定时任务,需要启动一个心跳进程,假设
celery beat -A celery_app.celery_config -s /path/to/celerybeat-schedule -l info
其中 -s
参数指定 celerybeat 文件保存的位置。beat 主要的功能就是将 task 下发到 broker 中,让 worker 去消费。
取消队列中任务,可以使用命令行,也可以导入 celery app 然后使用 control()
celery -A proj -Q queue_name purge # 取消队列 queue_name 中的任务
# or
from proj.celery import app
app.control.purge()
From:stackoverflow
Celery 不能用 root 用户启动,所以在 supervisor 中启动时会报错:
If you really want to continue then you have to set the C_FORCE_ROOT
environment variable (but please think about this before you do).
User information: uid=0 euid=0 gid=0 egid=0
解决办法
from celery import Celery, platforms
platforms.C_FORCE_ROOT = True
或者 supervisor 配置中
environment=C_FORCE_ROOT="true"
年前的时候喉舌媒体批评豆瓣,猫眼等评分太低影响了票房,而导致 16 年的年度票房目标没有达到,广电很生气,后果很严重。可是豆瓣存在了那么多年,那么多的电影,在院线上映的,还是不上映的,从来也没有听说过 IMDB 或者 烂番茄的评分会影响到总体的票房。虽然得分的多少或多或少的会对票房有所影响,可这难道是豆瓣,或者 IMDB 或 烂番茄这样的影评网站应该承担的责任吗? 制片公司,发行商,甚至细化到导演,演员,剧本,在国内甚至可以拉上审查来负责,动不动删掉个 14 分钟,谁还愿意花了冤枉钱去大荧幕看一个不完整的片子呢?真正的影迷 大概会愿意花个机票钱去看一个完整版吧。
当然也不想过多的吐槽,或许被“认证”也才能证明豆瓣的评分也算良心吧。这里就看看国内玩几家影评站对网站打分的计分规则。其实早在很早就将计算的公式记录在了记事本里面,一直没有整理。而现在想要来整理一下,也是感觉豆瓣评分在一定程度上没有想象的真实,看过一部被恶意差评的国产片,看后感觉并不是 5 分多的水平,后来看评论才知道其中的某一位演员的黑粉恶意差评才导致这样的结果,而看一些长评论确实客观很多。或许是差评的人,没那么多的时间来写长评吧。所以就像那篇评论中说的那样,“中国电影市场的正常发展,不仅需要好的导演,好的编剧,好的演员,还需要好的观众”。
BGM,找资料时偶得,为某一期奥斯卡缅怀逝去的人时的背景音乐
先来说一说我使用最多的豆瓣,豆瓣也是评分规则中最简单的,豆瓣不人工干预评分,而一部电影的最终得分就是由每个用户的打分的加权平均,举个例子,一个用户打 5 星,一个用户打 3 星,一个用户打 1 星,那么这部片子就是(5+3+1)/3
也就是 3 星,6 分。
豆瓣最后得分的具体公式 1:
\[\frac{x_1*1+x_2*2+x_3*3+x_4*4+x_5*5}{x_1+x_2+x_3+x_4+x_5}*2\]其中, $x_1$ 表示打 1 颗星的人数,$x_2$ 表示打 2 颗星的人数,以此类推。由该公式能够看出,豆瓣的评分是很简单的计算,而至少一颗星(2 分)的最低评分,也无形中提高了影片的评分,因为豆瓣根本不存在 0 分的电影,哦,不,还是有的。其实,豆瓣一直是一个满分 8 分的机制,那些超过 8 分的电影,是一定不会差的。所以曾经有段时间,找不到片子看的时候就直接找 8 分以上的片子看。
豆瓣的评分机制简单粗暴,在降低用户打分思考的时候,也会造成用户对一部影片的看法截然不同,尤其是在恶意刷分时,会导致最后的评分波动较大。曾经有人开过玩笑说过豆瓣的评分图案,r 型(5 星占大多数)的为口碑爆棚的好片,P 型为普通好片,b 型为普通烂片,而 C 型是水军刷出来的烂片,还有 L 型是多少水军都刷不出来的超级烂片。现在想来还是依然非常好玩。
时光网的存在感近两年被慢慢的抹去,但还依然半死不活的存在,时光网和豆瓣的评分机制一样,都是加权平均,只是时光网采用的是 10 分制,也就是用户有 10 个选择,用户需要话时间在评分的分数上,更多的选择,使得绝大部分用户选择中间段进行评分,因而导致最终的评分呈现中庸状态,同样无法真正体现出一部电影的真正得分。
而这样的十分制同样会导致在遭受大规模恶意打分(无论是好评还是差评)之后直接在最终结果中明显体现。
IMDB 是国外最大的电影资料站,大家经常提到的 IMDB TOP 250,也就是在该站上评分最高的 250 名。他采用贝叶斯算法,具体的公式 2:
\[WR=\frac{v}{v+m}*R+\frac{m}{v+m}*C\]其中:
这个公式的目的是为了让得分更加偏向于平均分,如果投票越多,评分就越接近真实的平均分,否则就越接近所有电影的平均分。而这个公式的唯一人为设定的参数就是基准票数。而这个参数的设定也正是为了解决如何让冷门和热门影片在得分上具有可比性。冷门片不会因为爱好者而导致评分异常高,这个问题也是豆瓣经常遇到的问题,一些冷门韩综,日剧,韩剧在评分上都有一定的偏高。
而关于 IMDB 这个公式是怎么防止恶意刷分,有兴趣可以了解一下当年《蝙蝠侠》和《教父》的往事:
烂番茄主要是专业影评人士评价汇总,和 IMDB 和 豆瓣这样单纯由网名进行投票的评分制度有些不同。而烂番茄通过新鲜度来对电影进行评价,而这里的新鲜度并不是实际意义上的评分,而是由影评人对该影片正面打分的比例来决定的,若正面的评价超过 60% 以上,该部作品将会被认为是“新鲜”(fresh)。如果正面评价超过 75 % ,那么该作品会得到“Certified Fresh” 的评价,而如果一部作品的正面评价低于 60%,那么该作品会被标示为“腐烂”(rotten)。影评人只有两个选项,正面和反面。
烂番茄和其他影评网站的最大区别是,他突出的是人群对一部电影持有的主流观点,而不是一个让每个人都感同身受的数值。
Metacritic 是一个综合性评定网站,影评只是该网站其中的一个小模块,该网站上影评人多以纸媒为主。 Metacritic 的评分主要从主流媒体和专业影评机构聚合而来,这些影评人和其供职的机构大多在影评方面具有公信力,比如《卫报》、《纽约时报》、《时代周刊》等等。但是并不是每一个机构和影评人都给出一个确切的分数。 Metacritic 具体做法是,如果来源有具体评分则使用来源评分,来源有星级打分则换算成百分制,如果来源影评只提供文字,然后他们自己去找人阅读影评,根据读完的感受给分。3
各家网站都有各家的好坏,豆瓣的评分机制是最简单高效的,这也是绝大多数的系统惯常的做法。但正是这样的机制使得刷分异常容易,大批量的差评或者好评能在短时间内影响影片总体的分数。另外一个比较严重的问题就是,无法在冷门片和热门片之间比较,这也是豆瓣官方博客在文章中提及的,热门影片能在短时间内获得几十万的评分,但是一些冷门片,或者一些上映时间比较久远的电影可能难以达到这么多的评分,这样就会导致热门片和冷门片在评分上无法比较。口碑比较好的热门片可能因为观众口味不一而导致评分稍中庸,而冷门片可能因为资深影迷而导致评分过高。因此在豆瓣看评分时,一般还需要看一下评分人数。而最近我也会看一下长评论,毕竟愿意花时间来评价一部影片,远比花 1 秒打个评分要来的认真。
而 IMDB 的评分方式一定程度上解决了冷门影片和热门影片评分上的差异,但是选择基准票数却也需要经过不断的调整,IMDB 历史上也经历过变化,根据该数据,阈值从 3000 票提升到了 25000 票,这次变换也相应的造成了最后得分的变化,尤其是影响了得到 25000 票以下,并且得分较高的影片。可以说只有当影片的评分人数足够多时,基准票数的影响才会减至最小,而对于票数比较少的影片,就相当于一次洗牌。
而对于烂番茄和国内的猫眼专家评分,其实一定意义上说代表着专业领域的人士意见,这些评论都值得一读,但是更多的需要自己的看法,只有最后形成自己的世界观那部分东西才真正属于自己。所以豆瓣和 IMDB 对于我来说,一方面提供给我足够的信息,包括导演,演员,编剧等等,另一方面也是让我远离烂片,毕竟看一部烂片浪费的是自己的时间。
最后,引用数位时代中的一句话,“在美国,佳片会收到它应得的票房和好评作为奖赏,烂片就算进了电影院也不可能躲得开差评 —- 无论在报纸、电台还是在网络上。在美国,对电影的批评,也是言论自由保护的一部分”。
若差评不自由,则高分无意义。
在我的 Trello 中 2018 年 12 月份的时候,写下了一个问句,“豆瓣评分的权威性”,我想可能是当时脑袋里突然想起来的一个疑问,经过上面这么多的讨论,应该都知道豆瓣评分的计算方式了,虽然这种方式有其优势 —- 简单,但也有其劣势 —- 容易被恶意操作。然而这么多年过去了豆瓣还是依然是那个豆瓣,也越来越多的媒体引用豆瓣的评分来做宣传,那么豆瓣评分的权威性来自哪里?我想了很久,难道正是因为一人一票这种最简单的模式?豆瓣的评分严格来说只有打分的人达到一定的基数后其评分才真正有参考价值,也只有一部影片被放到公共领域被讨论,最后众人给出的评分才是能代表绝大多数的分数。这也就是豆瓣评分权威性的由来,当你参与一个影片的[[公共讨论]]成为其一部分的时候,最后的结果才是能被大多数人认可的。并且你参与的过程有且只有一次,你的打分权重不会比别人高,也不会比别人低,人人平等。那么最后的那个分数就是权威的。(当然这里讨论的豆瓣评分已经说的很清楚了,不包括哪些小众的内容)
lsof
用于列出当前系统打开的文件 (list open files),在 Linux 中,任何事物都以文件的形式存在,通过文件不仅仅可以访问常规数据,还可以访问网络连接和硬件。所以比如传输控制协议 (TCP) 和用户数据报协议 (UDP) 套接字等,系统在后台都为该应用程序分配了一个文件描述符,无论这个文件的本质如何,该文件描述符为应用程序与基础操作系统之间的交互提供了通用接口。因为 lsof
命令需要访问核心内存和各种文件,所以需要 root 用户执行。
比如可以使用 lsof
来查看当前系统中 80 端口是否被占用
sudo lsof -i tcp:80
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
docker-pr 14863 root 4u IPv6 38693061 0t0 TCP *:http (LISTEN)
然后获取到 PID 之后可以用 lsof 来查看进程
sudo lsof -p 14863
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
docker-pr 14863 root cwd DIR 8,0 4096 2 /
docker-pr 14863 root rtd DIR 8,0 4096 2 /
docker-pr 14863 root txt REG 8,0 3329080 17531 /usr/bin/docker-proxy
docker-pr 14863 root mem REG 8,0 1868984 20743 /lib/x86_64-linux-gnu/libc-2.23.so
docker-pr 14863 root mem REG 8,0 138696 11625 /lib/x86_64-linux-gnu/libpthread-2.23.so
docker-pr 14863 root mem REG 8,0 162632 10738 /lib/x86_64-linux-gnu/ld-2.23.so
docker-pr 14863 root 0r CHR 1,3 0t0 8085 /dev/null
docker-pr 14863 root 1w CHR 1,3 0t0 8085 /dev/null
docker-pr 14863 root 2w CHR 1,3 0t0 8085 /dev/null
docker-pr 14863 root 4u IPv6 38693061 0t0 TCP *:http (LISTEN)
docker-pr 14863 root 5u a_inode 0,12 0 8082 [eventpoll]
docker-pr 14863 root 12r REG 0,3 0 4026531993 net
由以上的信息就能看出来我的机器中的 80 端口是 docker 占用的,docker 的位置在系统中的 /usr/bin/docker-proxy
lsof
输出各列信息的意义如下:
FD 的取值
一般在标准输出、标准错误、标准输入后还跟着文件状态模式:r、w、u 等
在有了基本的概念之后来看 lsof
的参数
lsof [ -?abChKlnNOPRtUvVX ] [ -A A ] [ -c c ] [ +c c ] [ +|-d d ] [ +|-D D ] [ +|-e s ] [ +|-E ] [ +|-f [cfgGn] ] [ -F [f] ] [ -g [s] ] [ -i [i] ] [ -k k ] [ +|-L [l] ] [ +|-m m
] [ +|-M ] [ -o [o] ] [ -p s ] [ +|-r [t[m<fmt>]] ] [ -s [p:s] ] [ -S [t] ] [ -T [t] ] [ -u s ] [ +|-w ] [ -x [fl] ] [ -z [z] ] [ -Z [Z] ] [ -- ] [names]
能看到非常多的选项,因此也能看到 lsof
的强大。
默认 : 没有选项,lsof 列出活跃进程的所有打开文件
-i : 列出网络连接
使用 lsof -i
来显示所有的链接,可以用来代替 netstat
:
sudo lsof -i
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
sshd 2972 root 3u IPv4 18883553 0t0 TCP *:22 (LISTEN)
sshd 2972 root 4u IPv6 18883562 0t0 TCP *:22 (LISTEN)
docker-pr 14852 root 4u IPv6 38693034 0t0 TCP *:https (LISTEN)
docker-pr 14863 root 4u IPv6 38693061 0t0 TCP *:http (LISTEN)
这里输出结果有缩略,但也能看出来 22 的 SSH 端口,然后 http
默认的 80 端口,和 https
使用的 443 端口。如果要查看 IPv6 的流量可以使用 lsof -i 6
。
同样如果要查看 TCP 或者 UDP 连接信息,可以使用 lsof -iTCP
和 lsof -iUDP
。
假如已经知道了端口号,想要查看该端口哪一个进程在使用可以使用:
lsof -i:80
再比如查看和本地 22 端口的连接 lsof -i:22
显示来自特定主机的连接,lsof -i@1.2.3.4
,指定主机和端口 lsof -i@1.2.3.4:22
lsof
还可以使用过滤器来过滤连接状态,比如只查看正在监听 TCP 端口的进程:
lsof -iTCP -sTCP:LISTEN
如果已经知道进程的 PID,可以使用 -p
查看指定进程 ID 已打开的内容
lsof -p 10075
lsof -u einverne
使用 -U
选项来列出系统中正在使用的 Unix 域套接字:
lsof -U
比如说如果系统中依赖的一个 jar 被发现有漏洞,比如说可以查看 fastjson
在系统中有没有使用。
lsof -X | grep fastjson
WEB-INF 是 Java EE Web 程序一个特殊的目录,此目录中的资源不会被列入应用程序根目录可直接访问项。客户端不可直接访问 WEB-INF 中的资源。
根据 Servlet 2.4 specification 中的描述,这个不公开的目录虽然不能被外部访问,但是可以被 servlet 代码 getResource
或者 getResourceAsStream
等方法访问,并且可以暴露给 RequestDispatcher
。
目录 WEB-INF/web.xml
中保存 web 程序配置文件,XML 格式,描述 servlet 和其他应用组件配置和命名规则。
在 Spring MVC 和其他任何 web 程序中一个好的推荐方式是将所有的 views 或者 JSP 文件存放在 WEB-INF 文件夹中。这些放在 WEB-INF 中的 views 就被称为 internal resource view,这些 views 只能被 servlet 或者 Spring Controller 访问。
“听歌识曲” 虽然听起来是一个简单的功能,却还依然发展了很多年。在无数的网站评论中看到求求某某片段中的背景音乐,其实绝大部分情况下都可以通过听歌识曲来找到,剩下的也绝大部分可以通过电视,电影的OST找到。所以这篇文章就是介绍下目前市面上我使用过比较好用的一些听歌识曲的应用,这些应用解决了我95%以上,找到喜欢的背景音乐的需求。
授人以鱼不如授人以渔
在电视、综艺、或者大街上听到一首喜欢的背景音乐却不知道歌名的时候,我下意识的会拿出手机来打开网易云音乐,当然这个能够解决一大半的问题,因为经过多年的使用,其实网易的识别还是有些准确的,至少对于绝大部分流行的中日韩欧美歌曲基本都能够识别出来,更甚至之前看请回答系列,识别出来了上世纪六七十年代发行的歌曲。但是听歌识曲有一些小小的弊端,一般情况下需要环境噪声比较小,并且经验给我的感觉是需要一段较长有歌词的完整的片段才能够快速的找到精确的歌。所以如果周围噪音比较大,或者电影中人物有对白时,尽量记住连续一段完整的歌词,然后通过搜索引擎搜索歌词来获取歌名,一般情况下我会加上 lyrics + 记住的歌词
来搜索,基本也能够找到想要的歌。
还有其他的酷狗,微信摇一摇啊,就不介绍了。
应用名 | 支持平台 | 语种 | 特别功能 |
---|---|---|---|
网易云音乐 | Android/iOS | 中韩英 | 识别出即可播放(当然要排除版权问题) |
SoundHound | Android/iOS | 英中 | 可以通过哼唱来识别 |
Shazam | Android/iOS | 英 | 在低功耗情况下一直识别音乐 |
Siri | iOS | 英 | 随系统自带 |
2021 年更新
到目前为止,我基本上只会使用 Shazam 和 网易云音乐了,Shazam 被苹果收购之后越来越好用,反而是 SoundHound 可能一直没有找到合适的盈利模式而越来越难用。
在一些对精度要求特别高的系统中,比如会计,金融,Java 中的 double,float 已经不能满足精度需求,谁也不愿意再付款或者计价的时候出现付费 4.4 而账单只有 4.0 元的错误。所以在 Java 中为了更高精度的计算我们需要用到 java.math.BigDecimal.
BigDecimal 需要有两个能力:
通常在使用 BigDecimal 时建议使用 传入 String 的构造函数。
BigDecimal bd = new BigDecimal(1.5);
bd = new BigDecimal("1.5");
如果使用 double 的构造函数可能会造成一些问题,比如第一个 bd 可能结果是 1.49999999999999999999
设置小数点(decimal)后面的数字位数,需要使用 .setScale(scale)
方法,与此同时,在设置 scale (数值范围)的时候一并设置 Rounding Mode(舍入模式)被认为是一个比较好的实践方式(Good practice),需要使用 .setScale(scale, roundingMode)
。rounding mode 定义了在损失精度时使用的舍入方式,比如四舍五入,或者 Ceiling 或者 Floor 等等。
为什么需要舍入模式,来看一个例子
bd = new BigDecimal(1.5); // is actually 1.4999....
bd.setScale(1); // throws ArithmeticException
抛出算术异常,因为 BigDecimal 不知道如何处理 1.49999
.
有八种定义好的 Rounding Mode,假设保留小数点后两位
ROUND_CEILING: Ceiling function 向天花板舍入
0.333 -> 0.34
-0.333 -> -0.33
ROUND_DOWN: Round towards zero 向 0 舍入
0.333 -> 0.33
-0.333 -> -0.33
ROUND_FLOOR: Floor function 往小舍入
0.333 -> 0.33
-0.333 -> -0.34
ROUND_HALF_UP: Round up if decimal >= .5
0.5 -> 1.0
0.4 -> 0.0
ROUND_HALF_DOWN: Round up if decimal > .5 最常见的四舍五入
0.5 -> 0.0
0.6 -> 1.0
ROUND_HALF_EVEN: 当需要舍入的数字是 5 时需要特殊处理,需要看 5 左边的数字奇偶性
a = new BigDecimal("2.5"); // digit left of 5 is even, so round down
b = new BigDecimal("1.5"); // digit left of 5 is odd, so round up
a.setScale(0, BigDecimal.ROUND_HALF_EVEN).toString() // => 2
b.setScale(0, BigDecimal.ROUND_HALF_EVEN).toString() // => 2
ROUND_UNNECESSARY: 当需要使用一种舍入方式,但是你知道结果不需要舍入的时候,也就意味着如果使用了这种舍入模式,那么如果出现一个不精确的结果比如 1/3,那么会抛出 ArithmeticException。
BigDecimal 是不可变对象,也就意味这如果创建了一个 BigDecimal 是 2.00 那么这个 BigDecimal 会一直是 2.00。在做算术是比如 add()
或 multiply()
方法时会返回一个新的 BigDecimal 对象。
Never use .equal()
method to compare BigDecimal. Because this equals function will compare the scale. If the scale is different, .equals()
will return false, even if they are the same number mathematically.
BigDecimal a = new BigDecimal("2.00");
BigDecimal b = new BigDecimal("2.0");
print(a.equals(b)); // false
反之,应该使用 .compareTo()
and .signum()
方法
a.compareTo(b); // returns (-1 if a < b), (0 if a == b), (1 if a > b)
a.signum(); // returns (-1 if a < 0), (0 if a == 0), (1 if a > 0)
如果要在计算中使用舍入模式,那么用什么精度呢?答案是打算如何使用结果。一般情况下,会知道最后用户需要的结果的精确度。对于那些中间计算过程中出现的数字,你需要增加一位数字来提高精确度。
比如 0.0144 + 0.0143 最后保留两位小数的话,在结果舍入会得到 0.03,而如果将两个数字加法之前就舍入,那么会得到 0.02.
如果最后的结果是乘法得到,那么你应当保留尽可能多的精度。而 Ratios
比率 和 Unit costs 单位价格,不应当舍入。而应当在做完所有计算之后在对结果进行舍入。
之前一篇文章 主要是使用 mitmproxy 进行抓包,但是其实 mitmproxy 自带的 feature 远远不止于抓包,使用 mitmdump 可以自定义脚本来修改 response 返回,或者将请求结果 dump 到本地以便于之后的分析。
之前的那篇文章在 mitmdump 的时候只是简单的介绍了一下功能,并没有展开,所以有了这篇文章。mitmdump 可以理解为 mitmproxy 的命令行版本,他提供了 tcpdump 类似的功能来查看,记录,甚至编程改写 HTTP 流量。
开启代理模式,并将所有的请求写入文件
mitmdump -w outfile
-n
参数表示不开启代理, -r
表示读入 infile,然后将将所有 match ~m post
POST 流量写入 outfile 文件中。
mitmdump -nr infile -w outfile "~m post"
关于过滤的规则,可以具体参考这里
使用 -n
参数不开启代理,然后 -c filename
参数进行重放。
mitmdump -nc outfile
甚至是可以重放请求,然后将结果保存到另外的文件中
mitmdump -nc srcfile -w dstfile
可以在启动 mitmdump 时添加自定义的脚本用来改写请求。
mitmdump -s examples/add_header.py
如果脚本文件带有参数,则需要在 -s
参数后面增加双引号,比如 mitmdump -s "add_header.py custom_header"
将这些参数组合一起使用
mitmdump -ns examples/add_header.py -r srcfile -w dstfile
从 srcfile 文件中加载流量,然后使用特定的脚本改写,然后将结果写入 dstfile 文件中。
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import os
import sys
from mitmproxy import flowfilter, http, ctx
# events run ordr: start, request, responseheaders, response, error, done
class Filter:
MOVIE_TOP250 = '/api/v2/subject_collection/movie_top250/items'
def __init__(self, path):
self.folder_path = path
# 构造一个 HTTP response code
self.http_code_ok = flowfilter.parse('~c 200')
if not os.path.exists(self.folder_path):
os.makedirs(self.folder_path)
# 构造一个 URL 过滤器
self.douban_path = flowfilter.parse(
'~u frodo.douban.com/api/v2/subject_collection/movie_top250/items')
# @concurrent # Remove this and see what happens
def request(self, flow: http.HTTPFlow):
if flowfilter.match(self.douban_path, flow):
if flow.request.host:
ctx.log(
"handle request: %s%s" % (
flow.request.host, flow.request.path))
def response(self, flow: http.HTTPFlow):
if flowfilter.match(self.http_code_ok, flow):
"""只有 200 状态进入"""
ctx.log('code %s' % flow.response.status_code)
if flowfilter.match(self.MOVIE_TOP250, flow):
if flow.response.content:
pretty_path = str(flow.request.path.rstrip())
pretty_path = pretty_path.replace('/', '_') \
.replace(':', '_') \
.replace('&', '_')
pretty_path = pretty_path[:250] + '.json'
res_content = flow.response.content.decode('utf-8')
path = os.path.join(self.folder_path, pretty_path)
with open(path, 'w+') as f:
f.write(res_content)
def start():
if len(sys.argv) != 2:
raise ValueError('Usage: -s "save_response.py path"')
# 保存结果的 folder 路径
return Filter(sys.argv[1])
将上面的脚本执行
/usr/local/bin/mitmdump -s "save_response.py /tmp/response_result/"
然后在结果路径中就能得到请求的豆瓣 Top250 电影结果,然后再对电影结果进行解析即可。
或者可以将请求的 webp 或者 jpg 的图全都保存到另外的文件夹中
pretty_url = flow.request.pretty_url
if pretty_url.endswith(".webp") or pretty_url.endswith('.jpg'):
# ctx.log('pretty url %s' % flow.request.pretty_url)
filename = os.path.join(self.folder_path,
os.path.basename(pretty_url))
with open(filename, 'wb') as f:
f.write(flow.response.content)
然后只要浏览过的图片就全都保存在本地的文件夹中了。
mitm 的过滤都是依靠 flowfilter.py 来实现的,可以匹配的规则有如下
The following operators are understood:
~q Request
~s Response
Headers:
Patterns are matched against "name: value" strings. Field names are
all-lowercase.
~a Asset content-type in response. Asset content types are:
text/javascript
application/x-javascript
application/javascript
text/css
image/*
application/x-shockwave-flash
~h rex Header line in either request or response
~hq rex Header in request
~hs rex Header in response
~b rex Expression in the body of either request or response
~bq rex Expression in the body of request
~bs rex Expression in the body of response
~t rex Shortcut for content-type header.
~d rex Request domain
~m rex Method
~u rex URL
~c CODE Response code.
rex Equivalent to ~u rex
从这些匹配规则就能看出来过滤规则可以非常精细,比如过滤结果为 500 的请求,比如过滤 header 中 content-type 为某种类型的请求,比如按照正则去匹配 URL 等等。
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import sys
from mitmproxy import flowfilter, http, ctx
# events run ordr: start, request, responseheaders, response, error, done
class Filter:
MOVIE_TOP250 = '/api/v2/subject_collection/movie_top250/items'
def __init__(self):
# 构造一个 URL 过滤器
self.douban_path = flowfilter.parse(
'~u frodo.douban.com/api/v2/elendil/home_timeline')
# 构造一个 HTTP response code
self.http_code_ok = flowfilter.parse('~c 200')
# Domain
self.my_domain = flowfilter.parse('~d douban.com')
# Method
self.filter_mathod = flowfilter.parse('~m POST')
# content-type header
self.filter_content_type = flowfilter.parse('~t json')
# @concurrent # Remove this and see what happens
def request(self, flow: http.HTTPFlow):
if flowfilter.match(self.douban_path, flow):
if flow.request.host:
ctx.log(
"handle request: %s%s" % (
flow.request.host, flow.request.path))
def response(self, flow: http.HTTPFlow):
if flowfilter.match(self.http_code_ok, flow):
"""只有 200 状态进入"""
ctx.log('code %s' % (flow.response.status_code))
if flowfilter.match(self.my_domain, flow):
"""只有匹配域名"""
ctx.log('domain %s' % flow.response.text)
if flowfilter.match(self.douban_path, flow):
"""只有 特定 url 可以进入"""
ctx.log('douban text' + flow.response.text)
ctx.log('douban reason ' + flow.response.reason)
ctx.log('douban http version ' + flow.response.http_version)
pretty_url = flow.request.pretty_url
if flowfilter.match(self.MOVIE_TOP250, flow):
if flow.response.content:
res_content = flow.response.content.decode('utf-8')
ctx.log("content: " + res_content)
def start():
if len(sys.argv) != 2:
raise ValueError('Usage: -s "dump.py"')
return Filter()