Code war of Angel


  • Home

  • Archives

Hey! JavaScript Developer, someone is stealing your bitcoin!

Posted on 2018-12-03 | In 前端

To summarizes this event:

  1. copay-dash is a popular bitcoin platform which includes event-stream as a dependency.
    copay-dash 是一个很流行的比特币平台,它使用了event-stream作为依赖包。
  2. For about a week in September, event-stream included flatmap-stream as a dependency, as it was passed along to a new maintainer who added the dependency and removed it a week later.
    九月份的时候,event-stream 使用了flatmap-stream作为依赖报,它传给了一个新的维护者,这个维护者加上了这个依赖包,一周之后又删掉了。
  3. flatmap-stream had some code hiding at the end of its minified built output which attempts to decode and execute strings from its packaged test/data.js file, using the description of the top-level package as the AES256 key.
    flatmap-steam 隐藏了一些代码在压缩代码的最后,它尝试使用顶级包的描述中的AES256 key去decode并且运行在test/data.js的字符串.
  4. For any other package, this produces a silently handled error (as the package description/decryption key is wrong). But for copay-dash, this produces some valid JavaScript which runs another round of decryption and executes a malicious script that steals your bitcoin wallet.
    对于其他包,这些产生了轻微的错误(被try catch了),但是对于copay-dash, 它产生了有效的JavaScript代码,运行另一轮解密并执行了偷取比特币的恶意脚本。

What now?

  1. Use a lockfile. Whether it’s yarn’s yarn.lock or npm’s package-lock.json, a lockfile ensures that you get the same package versions on every install, so if you’re secure today then you’ll be secure tomorrow as well. Apps that utilize floating dependencies without a lockfile are particularly vulnerable to malicious updates, because they will automatically install the latest patch version of their dependencies — meaning that you may be compromised as soon as your next deploy, if one of your dependencies is compromised and publishes a malicious patch. With a lockfile, you at least limit your exposure to manual developer actions that add or update a package, which can be double-checked via code reviews and organizational policies.
    使用lockfile, 比如说yarn.lock 或者package-lock.json。 lockfile文件确保了你每次install都会拿到相同版本的依赖包,所以如果你这次安装是安全的,那你下次也是安全的。如果你没有使用lockfile, 下载依赖包的时候会默认下载最新的版本,意味着你可能下载了一个含有恶意代码的版本。如果你某个依赖包遭到了破坏并发布了恶意版本,有lockfile的话,你至少可以限制对于手动添加、更新依赖包的风险,因为可以在代码审查、组织的时候double-check。

  2. Think before you install. This isn’t a panacea — as demonstrated above, it’s easy for attackers to slip malicious code into minified output, which is hard to find even if you knew it was there. But you will drastically reduce your exposure if you stick to popular, well-maintained packages. Before you install a new dependency, first, ask yourself if you really need it. If you already know how to write the code, and it won’t take you more than a few dozen lines, just do it yourself. If you do need the dependency, scope it out before you install. Does it have high download numbers on npm? Does the GitHub repo appear well-maintained and active? Has the package been updated recently? If not, also consider forking the repository and either publishing a fork to npm or installing the package directly from your GitHub; this reduces risk because you aren’t exposed to future malicious updates, and you can read and minify the source yourself so you’re confident about what it really does.
    下载前三思。这不是万能药 – 正如前面所示范的,将恶意代码写进压缩输出很简单,但是找出来很难,即使你知道它在。但是你会大大减少你的曝光率,如果你坚持使用流行的、维护好的依赖包。在你下载一个新的依赖包前,首先,问下你自己你真的需要吗?如果你已经知道怎么去写这段代码,而且不会很多,不如自己写。如果你真的需要这个依赖包,在你下载前确定范围,它有很高下载量吗?它的Github看起来最近有没有好好在维护?最近有更新吗?如果没有,也考虑下fork这个仓库而且将fork发不到npm或者直接从github下载包。这将减少风险,因为你没有暴露在未来可能的恶意更新中,而且你可以阅读、压缩源代码,知道它究竟在干什么。

原文

Centos系统docker的坑

Posted on 2018-11-30 | In 系统

环境:CentOS Linux release 7.1.1503 (Core)
dockerfiles 中有

1
2
3
RUN mv /app/docker/localtime /etc/localtime
......
RUM rm /app/docker

docker build 过程中发现报错

1
2
mv: can't rename '/app/docker/localtime': Invalid argument
The command '/bin/sh -c mv /app/docker/localtime /etc/localtime' returned a non-zero code: 1

而同样的脚本在另外一个Ubuntu系统下没有这样的报错
从 docker info中可以看到
不正常系统
Centos7
正常系统
Ubuntu16.04
并且可以看到报错信息

1
2
3
WARNING: overlay: the backing xfs filesystem is formatted without d_type support, which leads to incorrect behavior.
Reformat the filesystem with ftype=1 to enable d_type support.
Running without d_type support will not be supported in future releases.

Centos7 发行版默认的Kernel版本是3.10,但是Overlay2存储驱动需要4.0以上的kernel版本支持,所以默认使用overlay 并且不支持d_type, 才导致这个报错。
具体
解决方法一:
格式化 XFS 系统,添加ftype=1

1
2
3
4
# 查看当前系统xfs
xfs_info /
如果 ftype =0 表示不支持d_type
省略具体格式化方法

解决方法二:
修改 storage driver 为 devicemapper

1
2
3
4
5
6
vim /etc/docker/daemon.json
增加
{
...
"storage-driver": "devicemapper"
}

docker疑难杂症
docker文件系统参考
github上的讨论

ubuntu下安装配置mysql、Redis

Posted on 2018-11-30

以下为Ubuntu 16.04环境
安装配置Mysql:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 安装mysql
sudo apt-get install mysql-server
sudo apt install mysql-client
sudo apt install libmysqlclient-dev
# 测试安装mysql是否成功
sudo netstat -tap | grep mysql

# 修改root用户密码
>>> mysql -u root -p
# 进入到mysql
use mysql;
update user set authentication_string=PASSWORD('123') where User='root';
# 新增用户远程连接权限(指定具体ip地址)
grant all privileges on *.* to 'root'@'192.168.33.1' identified by '123';
flush privileges;

# 修改配置文件
vim /etc/mysql/mysql.conf.d/mysqld.cnf;
修改 bind-address=0.0.0.1

# 重启mysql服务
service mysql restart

安装配置Redis

1
2
3
4
5
6
7
8
9
10
11
12
# 安装
sudo apt-get update
sudo apt-get install redis-server
# 启动
sudo /etc/init.d/redis-server start
# 进入Redis
redis-cli
# 配置开机自启动
chmod +x /etc/init.d/redis-server
update-rc.d redis-server defaults
# 操作
service redis-server start/stop/restart

numpy的使用

Posted on 2018-11-28 | In Python

向量、矩阵

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import numpy as np
# 创建矩阵
matrix = np.array([[1, 4],
[2, 5]])
# 矩阵变形
matrix.reshape(1,4)
# 矩阵的逆
np.linalg.inv(matrix)
# 返回对角线元素
matrix.diagonal()
# 创建矩阵的迹
matrix.diagonal().sum()
# 展开矩阵
matrix.flatten()
# 返回矩阵的秩
np.linalg.matrix_rank(matrix)
# 返回最大元素
np.max(matrix)
# 返回最小元素
np.min(matrix)
# 寻找每列的最大元素
np.max(matrix, axis=0)
# 查看行和列数
matrix.shape
# 查看元素数(行乘列)
matrix.size
# 查看维数
matrix.ndim
# 返回均值
np.mean(matrix)
# 返回方差
np.var(matrix)
# 返回标准差
np.std(matrix)

#将字典转换为矩阵
from sklearn.feature_extraction import DictVectorizer
data_dict = [{'Red': 2, 'Blue': 4},
{'Red': 4, 'Blue': 3},
{'Red': 1, 'Yellow': 2},
{'Red': 2, 'Yellow': 2}]

# 创建 DictVectorizer 对象
dictvectorizer = DictVectorizer(sparse=False)
# 将字典转换为特征矩阵
features = dictvectorizer.fit_transform(data_dict)
# 查看特征矩阵的列名
dictvectorizer.get_feature_names()

Redis原理

Posted on 2018-11-22 | In 缓存

数据结构

字符串

简单动态字符串(SDS),比起 C 字符串, SDS 具有以下优点:

  1. 常数复杂度O1获取字符串长度。
  2. 杜绝缓冲区溢出。
  3. 减少修改字符串长度时所需的内存重分配次数:空间预分配未使用空间,SDS修改后,长度少于1m酒会分配跟len相同大小的未使用空间,大于1m分配1m的未使用空间。
  4. 二进制安全。
  5. 兼容部分 C 字符串函数。

链表

  1. 每个链表节点由一个 listNode 结构来表示, 每个节点都有一个指向前置节点和后置节点的指针, 所以 Redis 的链表实现是双端链表。
  2. 每个链表使用一个 list 结构来表示, 这个结构带有表头节点指针、表尾节点指针、以及链表长度等信息。
  3. 因为链表表头节点的前置节点和表尾节点的后置节点都指向 NULL , 所以 Redis 的链表实现是无环链表。
  4. 通过为链表设置不同的类型特定函数, Redis 的链表可以用于保存各种不同类型的值。

字典

  1. Redis 中的字典使用哈希表作为底层实现, 每个字典带有两个哈希表, 一个用于平时使用, 另一个仅在进行 rehash 时使用。
  2. 当字典被用作数据库的底层实现, 或者哈希键的底层实现时, Redis 使用 MurmurHash2 算法来计算键的哈希值。
  3. 哈希表使用链地址法来解决键冲突, 被分配到同一个索引上的多个键值对会连接成一个单向链表。
  4. 在对哈希表进行扩展或者收缩操作时, 程序需要将现有哈希表包含的所有键值对 rehash 到新哈希表里面, 并且这个 rehash 过程并不是一次性地完成的, 而是渐进式地完成的。
  5. 负载因子 =哈希表已保存节点数量 / 哈希表大小
  6. 索引值计算
  • 使用字典设置的哈希函数,计算键 key 的哈希值
    hash = dict->type->hashFunction(key);
  • 使用哈希表的 sizemask 属性和哈希值,计算出索引值
  • 根据情况不同, ht[x] 可以是 ht[0] 或者 ht[1]
    index = hash & dict->ht[x].sizemask;

跳跃表

  1. 跳跃表是有序集合的底层实现之一, 除此之外它在 Redis 中没有其他应用。
  2. Redis 的跳跃表实现由 zskiplist 和 zskiplistNode 两个结构组成, 其中 zskiplist 用于保存跳跃表信息(比如表头节点、表尾节点、长度), 而 zskiplistNode 则用于表示跳跃表节点。
  3. 每个跳跃表节点的层高都是 1 至 32 之间的随机数。
  4. 在同一个跳跃表中, 多个节点可以包含相同的分值, 但每个节点的成员对象必须是唯一的。
  5. 跳跃表中的节点按照分值大小进行排序, 当分值相同时, 节点按照成员对象的大小进行排序。

整数集合

  1. 整数集合是集合键的底层实现之一。
  2. 整数集合的底层实现为数组, 这个数组以有序、无重复的方式保存集合元素, 在有需要时, 程序会根据新添加元素的类型, 改变这个数组的类型。
  3. 升级操作为整数集合带来了操作上的灵活性, 并且尽可能地节约了内存。
  4. 整数集合只支持升级操作, 不支持降级操作。

压缩列表

  1. 压缩列表是一种为节约内存而开发的顺序型数据结构。
  2. 压缩列表被用作列表键和哈希键的底层实现之一。
  3. 压缩列表可以包含多个节点,每个节点可以保存一个字节数组或者整数值。
  4. 添加新节点到压缩列表, 或者从压缩列表中删除节点, 可能会引发连锁更新操作, 但这种操作出现的几率并不高。

quicklist(v3.2新增)

  1. 由ziplist组成的双向链表,链表中的每一个节点都以压缩列表ziplist的结构保存着数据,而ziplist有多个entry节点,保存着数据。
  2. quicklist宏观上是一个双向链表,因此,它具有一个双向链表的有点,进行插入或删除操作时非常方便,虽然复杂度为O(n),但是不需要内存的复制,提高了效率,而且访问两端元素复杂度为O(1)。
  3. quicklist微观上是一片片entry节点,每一片entry节点内存连续且顺序存储,可以通过二分查找以 log2(n)log2(n)的复杂度进行定位

编码

  1. 字符串对象的编码可以是int、raw、embstr
  2. 列表对象的编码可以是ziplist、linkedlist(quick list?)
  3. 哈希对象的编码可以是ziplist、hashtable
  4. 集合对象的编码可以是intset(整数集合)、hashtable
  5. 有序集合的编码可以是ziplist、skiplist

内存

  1. c语言不具备自动内存回收,redis构建了引用技术实现
  2. 只对包含整数值字符串对象进行内存共享:1. 将数据库键值的值指针指向一个现有的值对象。2. 将被共享的值对象引用计数增加一
  3. redis在服务器初始化时,创建0到9999一万个字符串对象
  4. 空转时长:对象记录了最后一次被访问时间

数据库

  1. 初始化服务时候,根据dbnum(可配默认16)创建数据库
  2. 键空间(字典):保存了所有键值对
  3. 命中次数(keyspace_hits) 不命中次数(keyspace_misses) ,INFO stats命令查看
  4. 过期字典:保存了所有键的过期时间(毫秒级时间戳)
  5. persist 实际是把键从过期字典中删除
  6. 过期删除策略:惰性删除(所有命令操作键时进行检查)+定期删除(定期从一定数量数据库取一定数量随机键检查并删除过期键)
  7. RDB持久化(在指定的时间间隔内,执行指定次数的写操作,则会将内存中的数据写入到磁盘中),生成RDB文件时已过期键不会保存,载入RDB文件时,主服务器模式运行时,过期键将被忽略,从服务器模式运行时会载入所有键,但在主从同步时过期键将被删除。
  8. AOF持久化(采用日志的形式来记录每个写操作,并追加到文件中。Redis 重启的会根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作),当过期键被删除的时候,向AOF文件追加一条DEL命令。AOF重写时,过期键被检查而不会保存到AOF文件。
  9. 数据库通知功能(notifyKeyspaceEvent)

持久化

RDB持久化

  1. 生成RDB文件命令:SAVE,阻赛Redis服务器进程,直到RDB文件创建完毕为止。BGSAVE,派生出一个子进程,由子进程负责生成RDB文件,服务器主进程基础处理命令请求。
  2. serverCron默认每隔100ms执行检查save选项保存条件(dirty,lastsave)是否满足,如满足执行BGSAVE

AOF持久化

  1. appendfsync选项。安全性always>every sec>no。 效率no>everysec>always

事件

  1. redis是事件驱动程序,服务器处理事件分为时间事件、文件事件
  2. 文件事件是对套接字操作的抽奖
  3. 服务器在一半情况下只执行serverCron函数一个时间事件
  4. 因为事件循环,时间事件的实际处理事件会比设定时间晚一些

客户端

  1. 服务器状态结构使用 clients 链表连接起多个客户端状态,新添加的客户端状态会被放到链表的末尾。
  2. 客户端状态的 flags 属性使用不同标志来表示客户端的角色, 以及客户端当前所处的状态。
  3. 输入缓冲区记录了客户端发送的命令请求, 这个缓冲区的大小不能超过 1 GB 。
  4. 命令的参数和参数个数会被记录在客户端状态的 argv 和 argc 属性里面, 而 cmd 属性则记录了客户端要执行命令的实现函数。
  5. 客户端有固定大小缓冲区和可变大小缓冲区两种缓冲区可用, 其中固定大小缓冲区的最大大小为 16 KB , 而可变大小缓冲区的最大大小不能超过服务器设置的硬性限制值。
  6. 输出缓冲区限制值有两种, 如果输出缓冲区的大小超过了服务器设置的硬性限制, 那么客户端会被立即关闭; 除此之外, 如果客户端在一定时间内, 一直超过服务器设置的软性限制, 那么客户端也会被关闭。
  7. 当一个客户端通过网络连接连上服务器时, 服务器会为这个客户端创建相应的客户端状态。 网络连接关闭、 发送了不合协议格式的命令请求、 成为 CLIENT_KILL 命令的目标、 空转时间超时、 输出缓冲区的大小超出限制, 以上这些原因都会造成客户端被关闭。
  8. 处理 Lua 脚本的伪客户端在服务器初始化时创建, 这个客户端会一直存在, 直到服务器关闭。
  9. 载入 AOF 文件时使用的伪客户端在载入工作开始时动态创建, 载入工作完毕之后关闭。

服务端

  1. 一个命令请求从发送到完成主要包括以下步骤:
    • 客户端将命令请求发送给服务器;
    • 服务器读取命令请求,并分析出命令参数;
    • 命令执行器根据参数查找命令的实现函数,然后执行实现函数并得出命令回复;
    • 服务器将命令回复返回给客户端。
  2. serverCron 函数默认每隔 100 毫秒执行一次, 它的工作主要包括更新服务器状态信息, 处理服务器接收的 SIGTERM 信号, 管理客户端资源和数据库状态, 检查并执行持久化操作, 等等。
  3. 服务器从启动到能够处理客户端的命令请求需要执行以下步骤:
    • 初始化服务器状态;
    • 载入服务器配置;
    • 初始化服务器数据结构;
    • 还原数据库状态;
    • 执行事件循环。

复制

  1. 部分重同步通过复制偏移量、复制积压缓冲区、服务器运行 ID 三个部分来实现。
  2. 在复制操作刚开始的时候, 从服务器会成为主服务器的客户端, 并通过向主服务器发送命令请求来执行复制步骤, 而在复制操作的后期, 主从服务器会互相成为对方的客户端。
  3. 主服务器通过向从服务器传播命令来更新从服务器的状态, 保持主从服务器一致, 而从服务器则通过向主服务器发送命令(REPLACONFACK)来进行心跳检测, 以及命令丢失检测。

Sentinel

  1. 一个运行在特殊模式下的 Redis 服务器, 它使用了和普通模式不同的命令表, 所以 Sentinel 模式能够使用的命令和普通 Redis 服务器能够使用的命令不同。
  2. Sentinel 会读入用户指定的配置文件, 为每个要被监视的主服务器创建相应的实例结构, 并创建连向主服务器的命令连接和订阅连接, 其中命令连接用于向主服务器发送命令请求, 而订阅连接则用于接收指定频道的消息。
  3. Sentinel 通过向主服务器发送 INFO 命令来获得主服务器属下所有从服务器的地址信息, 并为这些从服务器创建相应的实例结构, 以及连向这些从服务器的命令连接和订阅连接。
  4. 在一般情况下, Sentinel 以每十秒一次的频率向被监视的主服务器和从服务器发送 INFO 命令, 当主服务器处于下线状态, 或者 Sentinel 正在对主服务器进行故障转移操作时, Sentinel 向从服务器发送 INFO 命令的频率会改为每秒一次。
  5. 对于监视同一个主服务器和从服务器的多个 Sentinel 来说, 它们会以每两秒一次的频率, 通过向被监视服务器的 sentinel:hello 频道发送消息来向其他 Sentinel 宣告自己的存在。
  6. 每个 Sentinel 也会从 sentinel:hello 频道中接收其他 Sentinel 发来的信息, 并根据这些信息为其他 Sentinel 创建相应的实例结构, 以及命令连接。
  7. Sentinel 只会与主服务器和从服务器创建命令连接和订阅连接, Sentinel 与 Sentinel 之间则只创建命令连接。
  8. Sentinel 以每秒一次的频率向实例(包括主服务器、从服务器、其他 Sentinel)发送 PING 命令, 并根据实例对 PING 命令的回复来判断实例是否在线: 当一个实例在指定的时长中连续向 Sentinel 发送无效回复时, Sentinel 会将这个实例判断为主观下线。
  9. 当 Sentinel 将一个主服务器判断为主观下线时, 它会向同样监视这个主服务器的其他 Sentinel 进行询问, 看它们是否同意这个主服务器已经进入主观下线状态。
  10. 当 Sentinel 收集到足够多的主观下线投票之后, 它会将主服务器判断为客观下线, 并发起一次针对主服务器的故障转移操作。
  11. 相似RAFT算法选取领头Sentinel

集群

  1. 节点通过握手来将其他节点添加到自己所处的集群当中。
  2. 集群中的 16384 个槽可以分别指派给集群中的各个节点, 每个节点都会记录哪些槽指派给了自己, 而哪些槽又被指派给了其他节点。
  3. 节点在接到一个命令请求时, 会先检查这个命令请求要处理的键所在的槽是否由自己负责, 如果不是的话, 节点将向客户端返回一个 MOVED 错误, MOVED 错误携带的信息可以指引客户端转向至正在负责相关槽的节点。
  4. 对 Redis 集群的重新分片工作是由客户端执行的, 重新分片的关键是将属于某个槽的所有键值对从一个节点转移至另一个节点。
  5. 如果节点 A 正在迁移槽 i 至节点 B , 那么当节点 A 没能在自己的数据库中找到命令指定的数据库键时, 节点 A 会向客户端返回一个 ASK 错误, 指引客户端到节点 B 继续查找指定的数据库键。
  6. MOVED 错误表示槽的负责权已经从一个节点转移到了另一个节点, 而 ASK 错误只是两个节点在迁移槽的过程中使用的一种临时措施。
  7. 集群里的从节点用于复制主节点, 并在主节点下线时, 代替主节点继续处理命令请求。
  8. 集群中的节点通过发送和接收消息来进行通讯, 常见的消息包括 MEET 、 PING 、 PONG 、 PUBLISH 、 FAIL 五种。

发布订阅模式

  1. 服务器状态在 pubsub_channels 字典保存了所有频道的订阅关系: SUBSCRIBE 命令负责将客户端和被订阅的频道关联到这个字典里面, 而 UNSUBSCRIBE 命令则负责解除客户端和被退订频道之间的关联。
  2. 服务器状态在 pubsub_patterns 链表保存了所有模式的订阅关系: PSUBSCRIBE 命令负责将客户端和被订阅的模式记录到这个链表中, 而 UNSUBSCRIBE 命令则负责移除客户端和被退订模式在链表中的记录。
  3. PUBLISH 命令通过访问 pubsub_channels 字典来向频道的所有订阅者发送消息, 通过访问 pubsub_patterns 链表来向所有匹配频道的模式的订阅者发送消息。
  4. PUBSUB 命令的三个子命令都是通过读取 pubsub_channels 字典和 pubsub_patterns 链表中的信息来实现的。

事务

  1. 事务提供了一种将多个命令打包, 然后一次性、有序地执行的机制。
  2. 多个命令会被入队到事务队列中, 然后按先进先出(FIFO)的顺序执行。
  3. 事务在执行过程中不会被中断, 当事务队列中的所有命令都被执行完毕之后, 事务才会结束。
  4. 带有 WATCH 命令的事务会将客户端和被监视的键在数据库的 watched_keys 字典中进行关联, 当键被修改时, 程序会将所有监视被修改键的客户端的 REDIS_DIRTY_CAS 标志打开。
  5. 只有在客户端的 REDIS_DIRTY_CAS 标志未被打开时, 服务器才会执行客户端提交的事务, 否则的话, 服务器将拒绝执行客户端提交的事务。
  6. Redis 的事务总是保证 ACID 中的原子性、一致性和隔离性, 当服务器运行在 AOF 持久化模式下, 并且 appendfsync 选项的值为 always 时, 事务也具有耐久性。
  7. Redis事务不支持事务回滚机制,即使实行期间出现了错误也会全部执行所有命令。

排序

  1. SORT 命令通过将被排序键包含的元素载入到数组里面, 然后对数组进行排序来完成对键进行排序的工作。
  2. 在默认情况下, SORT 命令假设被排序键包含的都是数字值, 并且以数字值的方式来进行排序。
  3. 如果 SORT 命令使用了 ALPHA 选项, 那么 SORT 命令假设被排序键包含的都是字符串值, 并且以字符串的方式来进行排序。
  4. SORT 命令的排序操作由快速排序算法实现。
  5. SORT 命令会根据用户是否使用了 DESC 选项来决定是使用升序对比还是降序对比来比较被排序的元素
  6. 当 SORT 命令使用了 BY 选项时, 命令使用其他键的值作为权重来进行排序操作。
  7. 当 SORT 命令使用了 LIMIT 选项时, 命令只保留排序结果集中 LIMIT 选项指定的元素。
  8. 当 SORT 命令使用了 GET 选项时, 命令会根据排序结果集中的元素, 以及 GET 选项给定的模式, 查找并返回其他键的值, 而不是返回被排序的元素。
  9. 当 SORT 命令使用了 STORE 选项时, 命令会将排序结果集保存在指定的键里面。
  10. 当 SORT 命令同时使用多个选项时, 命令先执行排序操作(可用的选项为 ALPHA 、 ASC 或 DESC 、 BY ), 然后执行 LIMIT 选项, 之后执行 GET 选项, 再之后执行 STORE 选项, 最后才将排序结果集返回给客户端。
  11. 除了 GET 选项之外, 调整选项的摆放位置不会影响 SORT 命令的排序结果。

二进制位数组

  1. Redis 使用 SDS 来保存位数组。
  2. SDS 使用逆序来保存位数组, 这种保存顺序简化了 SETBIT 命令的实现, 使得 SETBIT 命令可以在不移动现有二进制位的情况下, 对位数组进行空间扩展。
  3. BITCOUNT 命令使用了查表算法和 variable-precision SWAR 算法来优化命令的执行效率。
  4. BITOP 命令的所有操作都使用 C 语言内置的位操作来实现。

慢日志

  1. Redis 的慢查询日志功能用于记录执行时间超过指定时长的命令。
  2. Redis 服务器将所有的慢查询日志保存在服务器状态的 slowlog 链表中, 每个链表节点都包含一个 slowlogEntry 结构, 每个 slowlogEntry 结构代表一条慢查询日志。
  3. 打印和删除慢查询日志可以通过遍历 slowlog 链表来完成。
  4. slowlog 链表的长度就是服务器所保存慢查询日志的数量。
  5. 新的慢查询日志会被添加到 slowlog 链表的表头, 如果日志的数量超过 slowlog-max-len 选项的值, 那么多出来的日志会被删除。

监视器

  1. 客户端可以通过执行 MONITOR 命令, 将客户端转换成监视器, 接收并打印服务器处理的每个命令请求的相关信息。
  2. 当一个客户端从普通客户端变为监视器时, 该客户端的 REDIS_MONITOR 标识会被打开。
  3. 服务器将所有监视器都记录在 monitors 链表中。
  4. 每次处理命令请求时, 服务器都会遍历 monitors 链表, 将相关信息发送给监视器。

LUA

好处

  1. [原子操作]lua脚本式在redis中原子执行的,在执行过程中不会插入其他命令
  2. [可复用]lua脚本可以帮助开发和运维人员创造出自己定制的命令,并可以将这些命令常驻在Redis内存中,实现复用的效果。
  3. [高性能]lua脚本可以将多条命令一次性打包,有效的减少网络开销。

要点:

  1. Redis 服务器在启动时, 会对内嵌的 Lua 环境执行一系列修改操作, 从而确保内嵌的 Lua 环境可以满足 Redis 在功能性、安全性等方面的需要。
  2. Redis 服务器专门使用一个伪客户端来执行 Lua 脚本中包含的 Redis 命令。
  3. Redis 使用脚本字典来保存所有被 EVAL 命令执行过, 或者被 SCRIPT_LOAD 命令载入过的 Lua 脚本, 这些脚本可以用于实现 SCRIPT_EXISTS 命令, 以及实现脚本复制功能。
  4. EVAL 命令为客户端输入的脚本在 Lua 环境中定义一个函数, 并通过调用这个函数来执行脚本。
  5. EVALSHA 命令通过直接调用 Lua 环境中已定义的函数来执行脚本。
  6. SCRIPT_FLUSH 命令会清空服务器 lua_scripts 字典中保存的脚本, 并重置 Lua 环境。
  7. SCRIPT_EXISTS 命令接受一个或多个 SHA1 校验和为参数, 并通过检查 lua_scripts 字典来确认校验和对应的脚本是否存在。
  8. SCRIPT_LOAD 命令接受一个 Lua 脚本为参数, 为该脚本在 Lua 环境中创建函数, 并将脚本保存到 lua_scripts 字典中。
  9. 服务器在执行脚本之前, 会为 Lua 环境设置一个超时处理钩子, 当脚本出现超时运行情况时, 客户端可以通过向服务器发送 SCRIPT_KILL 命令来让钩子停止正在执行的脚本, 或者发送 SHUTDOWN nosave 命令来让钩子关闭整个服务器。
  10. 主服务器复制 EVAL 、 SCRIPT_FLUSH 、 SCRIPT_LOAD 三个命令的方法和复制普通 Redis 命令一样 —— 只要将相同的命令传播给从服务器就可以了。
  11. 主服务器在复制 EVALSHA 命令时, 必须确保所有从服务器都已经载入了 EVALSHA 命令指定的 SHA1 校验和所对应的 Lua 脚本, 如果不能确保这一点的话, 主服务器会将 EVALSHA 命令转换成等效的 EVAL 命令, 并通过传播 EVAL 命令来获得相同的脚本执行效果。

Redis与MC对比

对比
why redis

参考:
Redis 设计与实现(第一版)

设计可拓展框架

Posted on 2018-11-21 | In 架构设计

可拓展框架基本模式

  1. 面向流程拆分:分层结构(通过分层强制约束两两依赖,缺点在于冗余的调用、性能)
    比如MVC分层
  2. 面向服务拆分:SOA(服务+ESB) 、微服务
  3. 面向功能拆分:微内核架构

微服务的基础设施:

  1. 自动化测试
  2. 自动化部署
  3. 配置中心
  4. 接口框架
  5. api网关
  6. 服务发现
  7. 服务路由
  8. 服务容错
  9. 服务监控
  10. 服务跟踪
  11. 服务安全

PHP项目中并发处理

Posted on 2018-11-21 | In PHP

总结一下PHP项目中并发处理:

高并发业务处理的重点在于原子操作,常见操作:

  1. redis->incr
  2. 如果redis是单机,可以使用watch方案
    WATCH mykey
     val = GET mykey
     val = val + 1
     MULTI
     SET mykey $val
     EXEC
    
  3. 如果redis是集群,使用redis->setNX
    死锁问题:

    1. 上锁的 key 保存的是 unix 时间戳,假如 key 值的时间戳小于当前的时间戳,表示锁已经不再有效。
    2. C4 向 lock.foo 发送 SETNX 命令。因为崩溃掉的 C3 还锁着 lock.foo ,所以 Redis 向 C4 返回 0 。
    3. C4 向 lock.foo 发送 GET 命令,查看 lock.foo 的锁是否过期。如果不,则休眠(sleep)一段时间,并在之后重试。另一方面,如果 lock.foo 内的 unix 时间戳比当前时间戳老,C4 执行以下命令:
      GETSET lock.foo
      因为 GETSET 的作用,C4 可以检查看 GETSET 的返回值,确定 lock.foo 之前储存的旧值仍是那个过期时间戳,如果是的话,那么 C4 获得锁。
    4. 如果其他客户端,比如 C5,比 C4 更快地执行了 GETSET 操作并获得锁,那么 C4 的 GETSET 操作返回的就是一个未过期的时间戳(C5 设置的时间戳)。C4 只好从第一步开始重试。
    5. 即便 C4 的 GETSET 操作对 key 进行了修改,这对未来也没什么影响。
  1. 队列方案:每次pop一个出来,但是数量多的时候会造成hotkey ,时间复杂度与数量相关

项目中整理处理:

  1. 降级:根据业务判断降级内容及手段。
    降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。
    梳理业务,可以参考日志级别设置预案:
    一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;
    警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警;
    错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;
    严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。

    降级按照是否自动化可分为:自动开关降级和人工开关降级。
    降级按照功能可分为:读服务降级、写服务降级。
    降级按照处于的系统层次可分为:多级降级。

    降级的功能点主要从服务端链路考虑,即根据用户访问的服务调用链路来梳理哪里需要降级:
    页面降级:在大促或者某些特殊情况下,某些页面占用了一些稀缺服务资源,在紧急情况下可以对其整个降级,以达到丢卒保帅;
    页面片段降级:比如商品详情页中的商家部分因为数据错误了,此时需要对其进行降级;
    页面异步请求降级:比如商品详情页上有推荐信息/配送至等异步加载的请求,如果这些信息响应慢或者后端服务有问题,可以进行降级;
    服务功能降级:比如渲染商品详情页时需要调用一些不太重要的服务:相关分类、热销榜等,而这些服务在异常情况下直接不获取,即降级即可;
    读降级:比如多级缓存模式,如果后端服务有问题,可以降级为只读缓存,这种方式适用于对读一致性要求不高的场景;
    写降级:比如秒杀抢购,我们可以只进行Cache的更新,然后异步同步扣减库存到DB,保证最终一致性即可,此时可以将DB降级为Cache。
    爬虫降级:在大促活动时,可以将爬虫流量导向静态页或者返回空数据,从而保护后端稀缺资源。
    参考《亿级流量网站架构核心设计》

  2. 熔断:关键在统一的api调用层,阈值设计。比如利用apcu,计算某个底层接口超时/返回错误的情况,假如在一个30s的周期内,失败次数超过n次,则不调用这个接口,直接返回某个约定值。

  3. 限流:基于请求、基于资源(cpu/..)
    限流的目的是通过对并发访问/请求进行限速或者一个时间窗口内的的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务(定向到错误页或告知资源没有了)、排队或等待(比如秒杀、评论、下单)、降级(返回兜底数据或默认数据,如商品详情页库存默认有货)。

    一般开发高并发系统常见的限流有:限制总并发数(比如数据库连接池、线程池)、限制瞬时并发数(如nginx的limit_conn模块,用来限制瞬时并发连接数)、限制时间窗口内的平均速率(如Guava的RateLimiter、nginx的limit_req模块,限制每秒的平均速率);其他还有如限制远程接口调用速率、限制MQ的消费速率。另外还可以根据网络连接数、网络流量、CPU或内存负载等来限流。

    先有缓存这个银弹,后有限流来应对618、双十一高并发流量,在处理高并发问题上可以说是如虎添翼,不用担心瞬间流量导致系统挂掉或雪崩,最终做到有损服务而不是不服务;限流需要评估好,不可乱用,否则会正常流量出现一些奇怪的问题而导致用户抱怨。

    限流算法
    常见的限流算法有:令牌桶、漏桶。计数器也可以进行粗暴限流实现。
    参考《亿级流量网站架构核心设计》

  4. 排队:排队模块、调度模块、服务模块

HTTP2改造实践

Posted on 2018-11-21

HTTP2特点:

  1. 新的二进制格式:HTTP1.x的解析是基于文本,HTTP2.0的协议解析决定采用二进制格式
  2. 多路复用

image.png

  1. header压缩
  2. 服务端推送
  3. HTTP2.0其实可以支持非HTTPS的,但是现在主流的浏览器像chrome,firefox表示还是只支持基于 TLS 部署的HTTP2.0协议,所以要想升级成HTTP2.0还是先升级HTTPS为好

http1.1问题:

  1. 链接安全性
  2. 建立链接消耗
  3. header内容过大
  4. keep-alive(与http2区别:线头阻塞)浪费

参考

实践:
为了升级图片域域名到HTTP2新域名,可选方案要不就统一后端接口处理匹配替换,要不就前端拿到数据后匹配替换。
由于考虑到后端接口分布比较零散,本次采用前端替换方案。

步骤:

  1. 需要获取所有接口数据后,替换图片域名:扩展了jquery的ajax,ajaxsetting.dataFilter处理返回是json的数据,ajaxsetting.beforeSend重写callback方法,先处理图片域名,再执行用户callback
  2. 增加了dns-prefetch属性,预解析图片域dns
  3. 增加了图片容错处理,MutationObserve监听dom结构

V8垃圾回收

Posted on 2018-11-21
  1. 引用计数垃圾回收:一个对象会被认为可以被垃圾回收当没有被引用到时。如果存在循环引用,就会发生问题。
  2. 标记清除法[主要]:判断一个对象是否可以被使用。
    1. (离开某个函数环境时?)垃圾回收器将会给全局变量一个列表
    2. 然后将所有全局变量以及它们引用的变量标志成“active”
    3. 回收所有没有被标记成active的对象内存

垃圾回收是不可预测的。

####内存泄漏:不需要的内存没有被回收

  1. 给未声明的变量赋值时(没有用var)/错误的使用this,会隐式声明一个全局变量
  2. 监听器引用的对象,现代浏览器发现dom节点无法引用的时候也会移除监听器,但ie6不会
  3. 闭包
  4. 对表格单一单元格的引用会使整个表格被保存在内存中

JS中的引用类型

Posted on 2018-11-21 | In 前端

JS 引用类型变量的值是一个指针,指向堆内存中的实际对象。

  • 基本类型是传值调用
  • 引用类型传共享调用

####传值调用(Pass by value)
在传值调用中,传递给函数参数是函数被调用时所传实参的拷贝。在传值调用中实际参数被求值,其值被绑定到函数中对应的变量上(通常是把值复制到新内存区域)。

####传引用调用(Pass by reference)
在传引用调用调用中,传递给函数的是它的实际参数的隐式引用而不是实参的拷贝。通常函数能够修改这些参数(比如赋值),而且改变对于调用者是可见的。

####传共享调用(Call by sharing)
传共享调用和传引用调用的不同之处是,该求值策略传递给函数的参数是对象的引用的拷贝,即对象变量指针的拷贝。

1…78910
Angel Teng

Angel Teng

97 posts
14 categories
37 tags
© 2021 Angel Teng
Powered by Hexo
|
Theme — NexT.Muse v5.1.4