Redis必知必会

什么是Redis

Redis是一个开源、基于内存的键值对存储数据库。通常情况下,键的类型都是字符串,值的类型有多种,Redis支持值的数据结构如下:

  1. 字符串
  2. 散列
  3. 列表
  4. 集合
  5. 有序集合
  6. 位图

Redis键和常见的值类型

键类型(key) 特点 备注
二进制安全 任何二进制序列作为键
较短的键占用内存较少
非常短的键,不利于语义化 需要在键的可读性和更少的内存消耗,找到合适平衡点。
尝试坚持使用固定的模式 例如,object-type:id => user:1000
键最大为512MB
Strings SET/GET 设置和获取字符串值 与Redis键关联最简单的值类型
支持包括二进制数据的字符串,最大512MB
SET nx 如果key不存在就设置值
SET xx 如果key存在才更新值
SET ex 设置指定的过期时间,多少秒
SET px 设置指定过期时间,多少毫秒
INCR key 解析字符串作为整数,自增1,得到的值作为新的值
EXISTS key 检查当前数据库是否存在给定的键
DEL key 删除键和关联的值
TTL key 检查键的剩余生存时间
列表(List) 通过链表实现 在常量时间内添加新元素到头部或者尾部,访问元素所需时间跟元素的索引成正比
LPUSH key value 将新元素添加到列表左侧,也就是头部
RPUSH key value 将新元素添加到列表右侧,也就是尾部
LRANGE key 0 size-1 从列表获取指定范围的元素,可以为负数。-1是最后一个元素,-2列表倒数第2个元素
RPOP key 从列表查找元素并同时从列表尾部弹出元素
LPOP key 从列表查找元素并同时从列表头部弹出元素
LTRIM key 0 2 只保存最新的N个元素,并丢弃所有最旧的元素
BRPOP key 阻塞版本的RPOP,等待指定列表的元素到达,若超时就立即返回。
BLPOP key 阻塞版本的LPOP,等待指定列表的元素到达,若超时就立即返回。
客户端以有序方式获取元素,返回值为2个元素的数组,超时则返回NULL
hash(哈希) hmset key k1 v1 k2 v2 设置哈希键多个字段值
hget key k1 获取散列中的指定字段
hget key k2
hmget key k1 k2 返回散列表中的多个字段值
hincrby key k1 value 对散列表中的指定字段自增值
集合(Set) 该集合是无序的
sadd myset 1 2 3 给指定集合添加多个元素
smembers myset 返回指定集合的元素
sismember myset 3 检查集合中元素是否存在
sinter k1 k2 k3 执行多个键间的交集
spop key 随机删除一个集合中的元素
sunionstore k1 k2 获取多个集合对应的并集,存储到第一个集合中。
scard key 获取指定集合的元素个数
有序集合(Sort Set) zadd key score value 添加有序集合的分数和值
内部实现是采用跳表+散列表实现。
zrang key 0 -1 返回指定有序集合全部元素,默认为升序
zrevrangekey 0 -1 倒序返回元素
zrangebyscore key -inf 1950 获取指定的排序之后的元素
zremrangebyscore key 1940 1960 删除指定范围内的元素
zrank key value 查询值所在的位置
位图(bit) SETBIT key 10 1 设置指定位图值
GETBIT key 10 获取指定位图值
BITCOUNT key 计算设置为1位的计数

为什么要用Redis

  1. Redis的访问速度快,完全基于内存操作,网络层采用epoll单线程解决高并发的问题。
  2. Redis有着丰富的数据类型,常见有5种:string、Hash、List、Set、SortSet、Bit等。

怎么用Redis

Redis列表

列表对很多任务都有用,比如:

  1. 记住用户发布到社交网络的最新更新。
  2. 流程之间的通信。
  3. 使用生产者将消息推入列表的消费者-生产者模式。

Redis集合

标记新闻文章的标签。假设文章id为1000,有标签1,2,5,77,那么你可以设计:

sadd news:1000:tags 1 2 55 77

也许你想要反向关系,标签对应的文章列表

sadd tag:1:news 1000
sadd tag:2:news 1000
sadd tag:5:news 1000
sadd tag:77:news 1000

smembers news:1000:tags

为了模拟基于网络的扑克游戏,你可以使用(C)lubs(黑梅花),(D)iamonds(红方块),(H)earts(红心),(S)pades(黑桃) 标识牌面的不同花色:

sadd deck C1 C2 C3 C4 C5 C6 C7 C8 C9 C10 CJ CQ CK
   D1 D2 D3 D4 D5 D6 D7 D8 D9 D10 DJ DQ DK H1 H2 H3
   H4 H5 H6 H7 H8 H9 H10 HJ HQ HK S1 S2 S3 S4 S5 S6
   S7 S8 S9 S10 SJ SQ SK

Redis位图

实现一个用户每日访问的最长连续性。位索引为,获取当前的unix时间戳,减去初始偏移量,然后除以3600*24。对于每一个用户都有一个包含每天访问信息的小字符串。使用BITCOUNT获取给定用户访问网站的天数,BITPOS调用,计算出最长的天数。

假设网站公开的日期为20190502,转化为时间戳为1556726400,那么位索引的计算公式为=(当前00:00:00的时间戳-1556726400)/(3600*24)取整,那么20190502的索引为0,20190503的索引,那么刚好=1。通过BITCOUNT可以计算出用户在指定期间访问网站的天数。

参考链接

  1. 介绍Redis数据类型和抽象
  2. 数据类型
  3. 为什么要用Redis

Kafka实践

前言

在上一篇文章,我们介绍的Kafka的基本概念和原理。我们尝试搭建一个Kafka的集群,并尝试生产和消费消息。在消费消息时,需要注意PHP的扩展rdkafka的消费接口不同以及消息ACK的机制。在Kafka集群中,有一个很重要的组件是zookeeper,下面我们会了解下什么是Zookeeper。

Zookeeper

Zookeeper是Apache的顶级项目,作为一个中心服务,用于维护命名和配置数据,并在分布式系统中提供灵活和健壮的数据同步。Zookeeper跟踪Kafka集群节点的状态、主题、分区等信息。Zookeeper允许多个客户端同时进行读写操作,并充当系统内的共享配置服务。

那Zeekeeper是如何工作的呢?Zookeeper将数据划分到多个节点集合中,这就是它实现高可用性和一致性的方法。如果有一个节点失败,Zookeeper可以执行即时故障转移,例如,如果主节点出现失败,则通过集合内的轮询实时选择一个新节点。如果第一个节点无法响应,连接服务器的客户端可以查询另外一个节点。

为什么Zookeeper对Apache Kafka来说是必须的?

在Kafka里,Zookeeper扮演着很重要的角色。有如下几个功能:

  1. 控制选举。控制者是Kafka生态中最重要的代理实体之一,负责维护所有分区之间的领导者与跟随者的关系。如果领导者节点挂掉,控制者负责在所有分区选举出新的领导者。
  2. 主题配置。保存着所有的主题配置,列表,分区数量,所有的副本位置,所有主题的配置,节点的首选领导等。
  3. 访问控制列表。Zookeeper维护着所有主题的访问控制列表。
  4. 集群的成员。Zeekeeper还维护着实时正在运行的所有代理列表。

消息消费

在PHP的扩展中,rdkafka实现了两种消费接口,High-level Consumer和Low-level Consumer。

接口 实现 备注
High-level Consumer 自动分区分配和撤销,自动管理offset
Low-level Consumer 手动维护分区,offset

消息ACK

Kafka消息可能会丢失的情况,可能会出现在消息发送和消息消费。Kafka通过配置request.required.acks属性是否进行消息确认。

配置 性能 内部实现 备注
0 不进行消息接受是否成功的确认
1 当Leader接受成功才确认
-1 当Leader和Follower都接受成功才确认

Kafka通过配置producer.type属性来决定消息的同步或异步发送。

综上,可能会有两种场景会丢失消息:

  1. acks = 0,不进行消息接受确认,网络异常、磁盘异常等会导致消息丢失。
  2. acks = 1,同步模式下,只有Leader确认接受成功但Leader由于异常挂掉了,副本同步失败,可能会造成消息丢失。

如何搭建Kafka服务

从Github上下载Kafka的Dockerfile

cd dev
git clone git@github.com:wurstmeister/kafka-docker.git

获取当前Ubuntu服务器的ip地址

ifconfig | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1'|grep -v '172*'| tail -1
192.168.1.3

替换版本库中的KAFKA_ADVERTISED_HOST_NAME

cd kafka-docker
sed -i 's/192.168.99.100/192.168.1.3/g' docker-compose.yml

启动整个集群

docker-compose up -d
Creating kafkadocker_kafka_1 ... 
Creating kafkadocker_zookeeper_1 ... 
Creating kafkadocker_kafka_1
Creating kafkadocker_kafka_1 ... done

经过漫长的等待,执行完成,可以看到启动了2个docker服务,分别以kafkadocker_kafka和wurstmeister/zookeeper为镜像。

docker ps

kafkadocker_kafka        "start-kafka.sh"       0.0.0.0:32768->9092/tcp kafkadocker_kafka_1
wurstmeister/zookeeper   "/bin/sh -c '/usr/sb…"   22/tcp, 2888/tcp, 3888/tcp, 0.0.0.0:2181->2181/tcp   kafkadocker_zookeeper_1

查看kafka的信息

./start-kafka-shell.sh 192.168.1.3  192.168.1.3:2181
# 进入docker环境后有4个脚本
bash-4.4# ls /usr/bin/ | grep '\.sh'
broker-list.sh 查看代理列表
create-topics.sh 创建主题
start-kafka.sh 启动kafka
versions.sh 显示版本

创建topics test

# 查看topics
kafka-topics.sh --list --zookeeper $ZK
# 创建一个topics test
kafka-topics.sh --create --topic test \
--partitions 4 --zookeeper $ZK --replication-factor 1
bash-4.4# kafka-topics.sh --list --zookeeper $ZK
test
# 显示topic test的相关信息
kafka-topics.sh --describe --topic test --zookeeper $ZK
Topic:test  PartitionCount:4    ReplicationFactor:1 Configs:
    Topic: test Partition: 0    Leader: 1001    Replicas: 1001  Isr: 1001
    Topic: test Partition: 1    Leader: 1001    Replicas: 1001  Isr: 1001
    Topic: test Partition: 2    Leader: 1001    Replicas: 1001  Isr: 1001
    Topic: test Partition: 3    Leader: 1001    Replicas: 1001  Isr: 1001

生产消息

kafka-console-producer.sh --topic=test --broker-list=`broker-list.sh`
# 输入msg后回车即可

消费消息

# 启动另外一个终端进行消费
./start-kafka-shell.sh 192.168.1.3  192.168.1.3:2181
# 进入docker 后在命令行输入
kafka-console-consumer.sh --topic=test --bootstrap-server `broker-list.sh`
# 收到消息后在直接显示出来

查看zookeeper信息

# 进入zookeeper shell
zookeeper-shell.sh $ZK
# 可查看相关帮助文档
help 
# 查看topics
ls /brokers/topics

遇到的问题

创建topics失败

kafka-topics.sh --create --topic test --partitions 4 --zookeeper $ZK --replication-factor 2
Error while executing topic command : Replication factor: 2 larger than available brokers: 1.
[2019-02-23 12:45:42,619] ERROR org.apache.kafka.common.errors.InvalidReplicationFactorException: Replication factor: 2 larger than available brokers: 1.
 (kafka.admin.TopicCommand$)

出现这个错误,提示brokers的数量较少,replication-factor 复制因子过大,如果只有一个broker,改为1即可。

消费失败

kafka-console-consumer.sh --topic=test --zookeeper=$ZK
# 出现报错
zookeeper is not a recognized option

最新版本的consumer去掉了这个选项,添加额外的选项即可。

kafka-console-consumer.sh --topic=test --bootstrap-server `broker-list.sh`

参考链接

  1. Dockerfile for Apache Kafka
  2. Run multiple Kafka brokers in Docker
  3. Rdkafka doc
  4. What is Zookeeper and why is it needed for Apache Kafka?
  5. kafka 数据可靠性深度解读
  6. Kafka High Availability

MySQL实践

前言

在上一篇文章,我们整理总结MySQL的常用知识点和MySQL的复制相关概念,在这篇文章,我们尝试搭建一个MySQL的主从服务。

主从架构

我们从github上拉取MySQL主从复制的版本库,直接尝试进行构建。该版本库构建了主从服务,定义了对数据库mydb的基于行的复制。

cd dev
git clone git@github.com:vbabak/docker-mysql-master-slave.git
./build.sh

# 执行成功后会显示
               Slave_IO_State: Waiting for master to send event
              Master_Log_File: mysql-bin.000003
          Read_Master_Log_Pos: 600
               Relay_Log_File: mysql-relay-bin.000002
                Relay_Log_Pos: 320
        Relay_Master_Log_File: mysql-bin.000003
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
      Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates

查看构建日志

docker-compose logs

查看当前进程

docker-compose ps

    Name                 Command             State                      Ports                   
------------------------------------------------------------------------------------------------
mysql_master   docker-entrypoint.sh mysqld   Up      3306/tcp, 33060/tcp, 0.0.0.0:4406->4406/tcp
mysql_slave    docker-entrypoint.sh mysqld   Up      3306/tcp, 33060/tcp, 0.0.0.0:5506->5506/tcp

查看master的状态

docker exec mysql_master sh -c 'mysql -u root -p111 -e "SHOW MASTER STATUS \G"'
mysql: [Warning] Using a password on the command line interface can be insecure.
*************************** 1. row ***************************
             File: mysql-bin.000003
         Position: 600
     Binlog_Do_DB: mydb
 Binlog_Ignore_DB: 
Executed_Gtid_Set:

查看slave的状态

docker exec mysql_slave sh -c 'mysql -u root -p111 -e "SHOW SLAVE STATUS \G"'

Slave_IO_State: Waiting for master to send event
                  Master_Host: 172.19.0.2
                  Master_User: mydb_slave_user
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysql-bin.000003
          Read_Master_Log_Pos: 600
               Relay_Log_File: mysql-relay-bin.000002
                Relay_Log_Pos: 320
        Relay_Master_Log_File: mysql-bin.000003
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes

在master上创建一个dev数据库,在slave上查看是否同步成功。

# 查看数据库
docker exec mysql_slave sh -c 'mysql -u root -p111 -e "SHOW DATABASES \G"'
docker exec mysql_master sh -c 'mysql -u root -p111 -e "SHOW DATABASES \G"'

# 查看记录
docker exec mysql_master sh -c "export MYSQL_PWD=111; mysql -u root mydb -e 'create table code(code int); insert into code values (100), (200)'"

# 查看master的状态
docker exec mysql_master sh -c "export MYSQL_PWD=111; mysql -u root mydb -e 'select * from code \G'"
docker exec mysql_master sh -c 'mysql -u root -p111 -e "SHOW DATABASES \G"'
docker exec mysql_master sh -c 'mysql -u root -p111 -e "SHOW MASTER STATUS \G"'

# 查看同步结果 slave状态
docker exec mysql_slave sh -c "export MYSQL_PWD=111; mysql -u root mydb -e 'select * from code \G'"
docker exec mysql_slave sh -c 'mysql -u root -p111 -e "SHOW DATABASES \G"'
docker exec mysql_slave sh -c 'mysql -u root -p111 -e "SHOW SLAVE STATUS \G"'

在这里,我们可以看到两条数据已经完整的同步过来。

*************************** 1. row ***************************
code: 100
*************************** 2. row ***************************
code: 200

参考链接

  1. Docker MySQL master-slave replication
  2. Mysql Master/Slave Replication With Docker