0%

先决条件
本教程假定 RabbitMQ 已经安装,并运行在localhost 标准端口(5672)。如果你使用不同的主机、端口或证书,则需要调整连接设置。

从哪里获得帮助
如果您在阅读本教程时遇到困难,可以通过邮件列表 联系我们

主题#

(使用 .NET 客户端)

教程[4] 中,我们改进了我们日志系统。我们用direct交换器替换了只能呆滞广播消息的fanout交换器,从而可以有选择性的接收日志。

虽然使用direct交换器改进了我们的系统,但它仍然有局限性 - 不能基于多个标准进行路由。

在我们的日志系统中,我们可能不仅要根据日志的严重性订阅日志,可能还要根据日志分发源来订阅日志。或许您可能从 unix syslog 工具中了解过这种概念,syslog 工具在路由日志的时候是可以既基于严重性(info/warn/crit…)又基于设备(auth/cron/kern…)的。

这种机制会给我们带来极大的灵活性 - 我们可以仅监听来自cron的关键错误日志,与此同时,监听来自kern的所有日志。

要在我们的日志系统中实现这一特性,我们需要学习更复杂的topic交换器。

Topic交换器#

发送到topic交换器的消息不能随意指定routing key,它必须是一个由点分割的单词列表,这些单词可以是任意内容,但通常会在其中指定一些与消息相关的特性。请看一些合法的路由键示例:stock.usd.nysenyse.vmwquick.orange.rabbit,路由键可以包含任意数量的单词,但不能超过255个字节的上限。

binding key也必须是相同的形式,topic交换器的背后逻辑与direct交换器类似 - 使用指定路由键发送的消息会被分发到与其绑定键匹配的所有队列中。不过对于绑定键来说,有两个重要的特殊情况需要注意:

  • *(星号)可以代替一个单词。
  • #(哈希)可以代替零个或多个单词。

下图示例是对上述内容最简单的解释:

在这个示例中,我们打算发送的消息全是用来描述动物的,这些消息会使用由三个单词(两个点)组成的路由键来发送。在路由键中,第一个单词用来描述行动速度、第二个是颜色、第三个是物种,即:<speed>.<colour>.<species>

我们创建了三个绑定:Q1绑定了键.orange.,Q2绑定了键*.*.rabbitlazy.#

这些绑定可以被概括为:

  • Q1对所有橙色的动物感兴趣。
  • Q2对兔子以及所有行动缓慢的动物感兴趣。

路由键为quick.orange.rabbit的消息会被发送到这两个队列,消息lazy.orange.elephant也会被发送到这两个队列。另外,quick.orange.fox只会进入第一个队列,lazy.brown.fox只会进入第二个队列。lazy.pink.rabbit只会被发送到第二个队列一次,尽管它匹配了两个绑定(避免了消息重复)。quick.brown.fox没有匹配的绑定,因此它将会被丢弃。

如果我们打破约定,发送使用一个或四个单词(例如:orangequick.orange.male.rabbit)作路由键的消息会发生什么?答案是,这些消息因为没有匹配到任何绑定,将被丢弃。

但是,另外,例如路由键为lazy.orange.male.rabbit的消息,尽管它有四个单词,也会匹配最后一个绑定,并将被发送到第二个队列。

Topics 交换器
topic交换器的功能是很强大的,它可以表现出一些其他交换器的行为。
当一个队列与键(哈希)绑定时, 它会忽略路由键,接收所有消息,这就像fanout交换器一样。
当特殊字符*(星号)和(哈希)未在绑定中使用时,topic交换器的行为就像direct交换器一样。

组合在一起#

我们将要在我们的日志系统中使用topic交换器,首先假设日志的路由键有两个单词组成:<facility>.<severity>

代码与上一篇 教程 中的代码几乎相同。

EmitLogTopic.cs的代码:

using System;
using System.Linq;
using RabbitMQ.Client;
using System.Text;

class EmitLogTopic
{
    public static void Main(string[] args)
    {
        var factory = new ConnectionFactory() { HostName = "localhost" };
        using(var connection = factory.CreateConnection())
        using(var channel = connection.CreateModel())
        {
            channel.ExchangeDeclare(exchange: "topic_logs",
                                    type: "topic");

            var routingKey = (args.Length > 0) ? args[0] : "anonymous.info";
            
            var message = (args.Length > 1)
                          ? string.Join(" ", args.Skip(1).ToArray())
                          : "Hello World!";
            var body = Encoding.UTF8.GetBytes(message);
            
            channel.BasicPublish(exchange: "topic_logs",
                                 routingKey: routingKey,
                                 basicProperties: null,
                                 body: body);
                                 
            Console.WriteLine(" [x] Sent '{0}':'{1}'", routingKey, message);
        }
    }
}

ReceiveLogsTopic.cs的代码:

using System;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System.Text;

class ReceiveLogsTopic
{
    public static void Main(string[] args)
    {
        var factory = new ConnectionFactory() { HostName = "localhost" };
        using(var connection = factory.CreateConnection())
        using(var channel = connection.CreateModel())
        {
            channel.ExchangeDeclare(exchange: "topic_logs", type: "topic");
            var queueName = channel.QueueDeclare().QueueName;

            if(args.Length < 1)
            {
                Console.Error.WriteLine("Usage: {0} [binding_key...]",
                                        Environment.GetCommandLineArgs()[0]);
                Console.WriteLine(" Press [enter] to exit.");
                Console.ReadLine();
                Environment.ExitCode = 1;
                return;
            }

            foreach(var bindingKey in args)
            {
                channel.QueueBind(queue: queueName,
                                  exchange: "topic_logs",
                                  routingKey: bindingKey);
            }

            Console.WriteLine(" [*] Waiting for messages. To exit press CTRL+C");

            var consumer = new EventingBasicConsumer(channel);
            consumer.Received += (model, ea) =>
            {
                var body = ea.Body;
                var message = Encoding.UTF8.GetString(body);
                var routingKey = ea.RoutingKey;
                Console.WriteLine(" [x] Received '{0}':'{1}'",
                                  routingKey,
                                  message);
            };
            channel.BasicConsume(queue: queueName,
                                 autoAck: true,
                                 consumer: consumer);

            Console.WriteLine(" Press [enter] to exit.");
            Console.ReadLine();
        }
    }
}

请运行以下示例:

要接收所有日志:

cd ReceiveLogsTopic
dotnet run "#"

要接收来自设备kern的所有日志:

cd ReceiveLogsTopic
dotnet run "kern.*"

或者,如果您只想监听级别为critical的日志:

cd ReceiveLogsTopic
dotnet run "*.critical"

您可以创建多个绑定:

cd ReceiveLogsTopic
dotnet run "kern.*" "*.critical"

使用路由键kern.critical发出日志:

cd EmitLogTopic
dotnet run "kern.critical" "A critical kernel error"

希望运行这些程序能让您玩得开心。要注意的是,这些代码没有针对路由键和绑定键做任何预设,您可以尝试使用两个以上的路由键参数。

EmitLogTopic.csReceiveLogsTopic.cs 的完整源码)

接下来,在 教程[6] 中将了解如何将往返消息作为远程过程调用。

写在最后#

本文翻译自 RabbitMQ 官方教程 C# 版本。如本文介绍内容与官方有所出入,请以官方最新内容为准。水平有限,翻译的不好请见谅,如有翻译错误还请指正。

  • 原文链接:RabbitMQ tutorial - Topics
  • 实验环境:RabbitMQ 3.7.4 、.NET Core 2.1.3、Visual Studio Code
  • 最后更新:2018-09-06

环境检查

1
ls /sys/firmware/efi/efivars #UEFI/BIOS检测

若该目录不存在,则 ArchISO 是以 BIOS/CSM 模式启动,否则是以 UEFI 模式启动。

通常而言,UEFI 系统须使用 GPT 分区才能引导,BIOS 系统须使用 MBR 分区才能引导。

分区

fdisk

1
fdisk -l //查看所有分区情况

常用fdisk命令:p 显示当前磁盘分区,d 删除指定分区,n 创建新分区, a 为指定分区创建启动标记,t 更改分区格式, w将磁盘分区信息写入磁盘。

parted

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# parted /dev/sda

检查 MINOR #对文件系统进行一个简单的检查
cp [FROM-DEVICE] FROM-MINOR TO-MINOR #将文件系统复制到另一个分区
help [COMMAND] #打印通用求助信息,或关于 COMMAND 的信息
mklabel 标签类型 #创建新的磁盘标签 (分区表)
mkfs MINOR 文件系统类型 #在 MINOR 创建类型为“文件系统类型”的文件系统
mkpart 分区类型 [文件系统类型] 起始点 终止点 #创建一个分区
mkpartfs 分区类型 文件系统类型 起始点 终止点 #创建一个带有文件系统的分区
move MINOR 起始点 终止点 #移动编号为 MINOR 的分区
name MINOR 名称 #将编号为 MINOR 的分区命名为“名称”
print [MINOR] #打印分区表,或者分区
quit #退出程序
rescue 起始点 终止点 #挽救临近“起始点”、“终止点”的遗失的分区
resize MINOR 起始点 终止点 #改变位于编号为 MINOR 的分区中文件系统的大小
rm MINOR #删除编号为 MINOR 的分区
select 设备 #选择要编辑的设备
set MINOR 标志 状态 #改变编号为 MINOR 的分区的标志


格式化分区

1
2
3
4
mkfs.vfat -F 32 /dev/sda1 #生成ESP分区的文件系统FAT32
mkswap /dev/sda2 #格式化swap
mkfs.ext4 /dev/sda3 #格式化ext4
mkfs.xfx /dev/sda4 #格式化xfs

分区方案

1
2
3
4
/dev/sda1 /boot 500M
/dev/sda2 swap 8G(根据内存大小调整)
/dev/sda3 / 200G
/dev/sda4 /home 剩余所有空间

挂载分区

1
#mount /dev/sda6 /mnt # 挂载根分区

非UEFI挂载boot

1
2
# mkdir -p /mnt/boot
# mount /dev/sda1 /mnt/boot

建立efi目录,把EFI分区装载到刚建立的efi目录上。

1
2
#mkdir -p /mnt/boot/efi
#mount /dev/sdc1 /mnt/boot/efi

挂载交换分区和home

1
2
3
#swap on /dev/sda5
#mkdir -p /mnt/home
#mount /dev/sda7 /mnt/home

生成引导

  • BIOS:

依赖包: grub os-prober

1
2
# grub-install --recheck /dev/<目标磁盘>
# grub-mkconfig -o /boot/grub/grub.cfg
  • UEFI:—如果BIOS是UEFI的,就要用下面的命令安装grub了

依赖包: dosfstools grub efibootmgr

1
2
# grub-install --target=x86_64-efi --efi-directory=<EFI 分区挂载点> --bootloader-id=arch_grub --recheck
# grub-mkconfig -o /boot/grub/grub.cfg

低格填零

1
# dd if=/dev/zero of=/dev/sda bs=16M

1. 下载镜像 制作启动U盘

Arch Linux 官方网站 https://www.archlinux.org/

制作启动盘工具 Rufus - 轻松创建 USB 启动盘

Linux下

1
dd if=*iso of /dev/sdb bs=41M

2. 网络连接

参考 Linux配置网络及SSH配置

3. 选择软件源

推荐国内的用户选择http://mirrors.ustc.edu.cn 默认的mirrorlist是开启所有源的,因此我们使用sed先在所有源的前面加上#

1
2
#sed -i "s/^\b/#/g" /etc/pacman.d/mirrorlist
#nano /etc/pacman.d /mirrorlist

将mirrors.ustc.edu.cn前面的#去掉

4. 分区/格式化/挂载

参考 Linux分区

5. 安装基本系统

1. 将基本系统安装到根目录上去

1
#pacstrap /mnt base base-devel
其实,这里安装的基本系统也肯定有自己用不到的冗余功能,例如我就用不到nano文本编辑器,但系统会默认给安上。如果知道基本系统每个文件的作用,其实也完全可以自定义安装。比如:
1
2
#pacstrap /mnt bash coreutils file filesystem grub2 linux pacman \
procps-ng syslog-ng glibc systemd-sysvcompat shawd dhcpcd vi
> 如果你想使用ifconfig之类的工具,请在上面加上net-tools

2. 生成fstab

用下面命令生成 fstab。如果想使用 UUIDs,使用 -U 选项;如果想使用标签,用 -L 选项.

1
#genfstab -U -p /mnt >>/mnt/etc/fstab
> [red]**后面如果出现问题,请不要再次运行genfstab**[red],如果需要,手动编辑/etc/fstab /etc/fstab文件在运行genfstab后应该被检查一下。如果之前你生成了一个EFI系统分区,那么 genfstab给EFI分区添加了错误的选项,会导致无法启动。因此你需要移除EFI分区的所有选项,除了noatime. 对其他分区, 替换"codepage=cp437" 为 "codepage=437" , 会挂载失败导致systemd进入恢复模式。

3. 切换到新系统中

1
2
#arch-chroot /mnt
#sh-4.2#bash
> 到这一步之后,开始系统的主要配置,如果下面文件不存在,需要手动创建。 > 理解并完全安装步骤设置是保证系统配置成功的关键。

4. 对新的基本系统进行设置

写入本机的字符编码方式

1
2
3
#nano /etc/locale.conf #

LANG=en_US.UTF-8 #简略写法 echo LANG= en_US.UTF-8 >> locale.conf

locale.conf 文件默认不存在,一般设置LANG就行了,它是其它设置的默认值。

1
2
3
4
/etc/locale.conf

LANG=zh_CN.UTF-8
LC_TIME=en_GB.UTF-8

修改本机编码

1
# nano /etc/locale.gen  将用不到的编码全删掉,只保留en_US与zh_CN的几行。 

默认情况下 /etc/locale.gen 是一个仅包含注释文档的空文件。选定你需要的本地化类型(移除前面的#即可), 比如中文系统可以使用:

1
2
3
4
5
en_US.UTF-8 UTF-8
zh_CN.GB18030 GB18030
zh_CN.GBK GBK
zh_CN.UTF-8 UTF-8
zh_CN GB2312

对系统的编码进行更新

1
#locale-gen 

写入本机的名称

1
# nano /etc/hostname #简略写法:echo {name} >/etc/hostname,也是一样的。

写入键盘布局方案

1
#nano /etc/vconsole.conf

美式键盘,如下:

1
2
3
KEYMAP=us
FONT=
FONT_MAP=

写入时区

1
2
3
# nano /etc/timezone

Asia/Shanghai

建立时区的软链接

1
#ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

设定系统将用的时间方案

1
#hwclock --systohc --utc

这个时间方案我是试过很多次,如果是双系统,电脑里还有win系统的话,建议设为:–localtime,否则可设为—utc。不过,我现在虽然也用双系统,但还是设的utc,因为设为–localtime虽然在win下时间不会出错,但回到linux下,经常系统会有些古怪的问题,比如,升级系统之时,报密钥错误。使用–utc,虽然在linux下时间会慢8个多小时,但毕竟对整个系统没有影响。

生成内核的启动镜象。

1
#mkinitcpio -p linux

安装必要工具

安装必要的网络工具以便于开机后可以配置网络连接(包括无线)

1
2
3
4
#pacman –S wpa_supplicant net-tools
#pacman -S dialog
#pacman -S netctl
#pacman -S wireless_tools

6. 安装引导

安装grub

1
2
#pacman -S grub-bios os-prober
#grub-install /dev/sda

UEFI 注意分区,参考: Linux分区.格式化.挂载

1
2
#pacman -S grub-bios efibootmgr os-prober
#grub-install --efi-directory=/boot/efi --bootloader-id=arch-grub --target=x86_64-efi

生成启动菜单

1
2
3

#grub-mkconfig -o /boot/grub/grub.cfg
#nano /boot/grub/grub.cfg

生成grub引导windows

如何生成grub引导文件grub.cfg 这里我们需要充分参考点击打开链接grub的说明。首先,需要额外安装一个 os-prober的软件包,直接pacman就行;然后grub-makeconfig 到/boot/grub/grub.cfg 。此时才能生成可以引导多系统的引导文件.如下图。
Window引导

开机自启网络

1
2
#systemctl enable dhcpcd@.service
#dhcpcd

卸载挂载的分区并重启

1
2
#umount /mnt/{boot,home,mnt}
# reboot

基本系统已安装完成

7. 系统配置

忘记安装net-tools补救

1
2
ip link show #查看网卡
ip link set eth0 up # 启用网卡

如果是DHCP的当然简单,直接dhcpcd即可,如果是固定IP的,则要如下操作:

1
2
3
#ip addr add 固定IP/24 dev eth0
#ip link set dev eth0 up
#ip route add default via 网关

系统更新

1
#pacman –Syu

添加用户

1
2
3
#useradd -m 新用户 #新建用户
#passwd 新用户 #指定密码:
#usermod -a -G video,audio,lp,log,wheel,optical,scanner,games,users,storage,power 新用户 #指定用户所在的组

sudo权限

1
nano /etc/sudoers (添加sudo权限)

放开%wheel %sudo权限

sudo命令补全

1
2
#sudo pacman -S bash-completion 
#echo "source /usr/share/bash-completion/bash_completion" >>/home/$USER/.bashrc

更新源列表

1
#pacman -S reflector 

reflector是一个可以从arch官方MirrorStatus列表取回最新mirrorlist的脚本,并且可以根据最新同步时间和速度排序。
下面先说如何自动配置源列表。直接终端输入命令5(注意备份原有源列表)

1
#reflector --verbose --country 'China' -l 200 -p http --sort rate --save /etc/pacman.d/mirrorlist

安装yaourt

1
#vim /etc/pacman.conf
1
2
3
4
[archlinuxcn]
#The Chinese Arch Linux communities packages.
SigLevel = Optional TrustAll
Server = http://repo.archlinuxcn.org/$arch
1
# pacman -Syu yaourt

安装powerpill

1
#yaourt -S powerpill

powerpill是一个可以从多个源多线程下载软件包的程序,类似于迅雷一样,可以明显提升更新速度,相当于pacman的外壳程序,使用方法完全和pacman相同。下面说说powerpill,玩arch的人不知道powerpill是不行的,需要注意的是它也是要调用reflector的,但并不是作为依赖。如果安装reflector后powerpill更新前会默认从mirrorstatus取回45个最新更新的源地址,然后并行下载,否则就是读取/etc/pacman.d/mirrorlist然后配置下载。当然我们推荐第一种,总不能每次都手动执行

7. 驱动显卡

安装显卡驱动

1
2
3
4
5
6
7
# pacman -S mesa
# lspci | grep VGA(查看本机的显卡类型)
# pacman -Ss xf86-video | less(查看能够安装的显卡类型)
# pacman -S …… 安装显卡驱动(或者可以直接所有驱动都自动安装)
# pacman –S xf86-video-vesa
# pacman –S xf86-video-nouveau #如果是ATI显卡的话,要安xf86-video-ati;
# pacman –S virtualbox-guest-utils #虚拟机

安装系统基础程序:

1
2
# pacman -S xorg-server xorg-xinit xorg-utils xorg-server-utils dbus # 先安装x-window服务
# pacman –S xterm xorg-xclock xorg-twm # 安装测试环境

重设系统的编码方式

编辑.xinitrc,把以下内容添加到文件最开始。内可以使用你所喜欢的编辑器,比如nano

1
2
LANG=zh_CN.UTF-8
LC_ALL="zh_CN.UTF-8"

更新系统的编码:

1
#locale-gen

更新一下系统的时间

1
2
# date -s "2013-01-14 14:40:10"
# hwclock --systohc

音频管理

1
# pacman -S alsa-utils pulseaudio-alsa

安装网络管理工具

1
2
3
# pacman –S networkmanager network-manager-applet wireless_tools
# systemctl enable NetworkManager
# systemctl start NetworkManager

安装桌面

击右键菜单,找到文件管理器,然后进入到目录/usr/share/applications/下,你会看到你已经安装完成的程序,全都可以从这儿启动。此时,你不妨复制几个常用的到你的用户目录:/home/新用户/桌面/下去。复制之后,你会在你的桌面上,看到这些程序的启动器。

安装完ibus之后,在/home/$USER/.xinitrc文件中,写入:

1
2
3
4
export GTK_IM_MODULE=ibus
export QT_IM_MODULE=ibus
export XMODIFIERS=@im=ibus
ibus-daemon -d -x

Windows下的磁盘挂载
参考Windows下的磁盘挂载

Xfce主题

字体及补丁

1
2
3
# pacman -S ttf-dejavu ttf-ubuntu-font-family ttf-arphic-ukai ttf-arphic-uming
# pacman -S wqy-microhei wqy-bitmapfont wqy-zenhei ttf-fireflysung
$ yaourt -S cairo-ubuntu libxft-ubuntu freetype2-ubuntu fontconfig-ubuntu #以普通用户身份执行

安装系统主题:

1
sudo pacman -S gtk-aurora-engine gtk-engine-murrine 

鼠标主题:

1
sudo pacman -S xcursor-vanilla-dmz xcursor-vanilla-dmz-aa

图标主题:

1
# pacman -S gnome-icon-theme-extras oxygen-icons human-icon-theme lxde-icon-theme tangerine-icon-theme

针对笔记本电脑的配置:(Speed-step 、 Suspend 等功能)

1
2
# pacman -S  gnome-power-manager  volumeicon
$ yaourt -S laptop-mode-tools pmount

Grub主题
在启动过程中发现Xfce桌面启动载入真心简陋,没有关系,我们在AUR里下载一个balou并设置就好了。

1
$ yaourt -S archlinux-themes-balou

下面来配置grub的启动界面。AUR里有一个非常棒的包grub2-theme-archlinux

1
$ yaourt -S grub2-theme-archlinux

安装后编辑/etc/default/grub,
#GRUB_THEME="/path/to/gfxtheme"改为GRUB_THEME="/boot/grub/themes/Archlinux/theme.txt"
GRUB_GFXMODE=auto改为GRUB_GFXMODE=1024x768修改完成后重新生成一下启动文件

1
# grub-mkconfig -o /boot/grub/grub.cfg

安装 i3 窗口管理器

1
# pacman -S i3

安装 lightdm 显示管理器,

1
# pacman -S lightdm-gtk3-greeter

然后

1
2
# systemctl enable lightdm
# systemctl start lightdm

8. 桌面及美化

本文方案适用于Microsoft Sql Server 2008/2012/2012 r2/2014版本,以下简称MSSQLSERVER。

MSSQL默认是不允许远程连接,并且禁用sa账户的。如果想要在本地用SSMS连接远程服务器上的MSSQLSERVER,需要做两个部分的配置:

1. SQL SERVER MANAGEMENT STUDIO(SSMS)

2. SQL SERVER配置管理器(SQL SERVER CONFIGURATION MANAGER - SSCM)

并且需要注意的是,有些地方如果没有生效,请重启一下sql server(可以从SSCM里,也可以从系统服务中找),下面是详细的步骤:

STEP1. 打开SSMS,使用Windows身份连接数据库,登录后,右键选择“属性”

STEP 2. 选择“安全性”,选中SQL SERVER和Windows身份验证模式

STEP 3. 再选择“连接”,勾选“允许远程连接此服务器”,然后点击“确定”按钮。

STEP 4. 展开“安全性” -》登录名 -》sa,右键选择“属性”

STEP 5. 在“常规”中,改好你自己的密码,这是你sa登录的密码。

STEP 6. 在“状态”中,启用sa登录,点击“确定”按钮

STEP 7. 右键数据库server,选择“方面”

STEP 8. 选择“服务器配置”,找到RemoteAccessEnabled,设置为“True”

STEP 9. 重新启动SQL SERVER服务,退出当前的连接,这时候应该可以用sa进行登录了。

STEP 10. 配置SSCM,选中左侧的“SQL SERVER服务”,确保右侧的“SQL SERVER”以及“SQL SERVER BROWER”正在运行,选择“网络配置”,双击TCP/IP,确保状态为“启用”

STEP 11. 在Client里也确保TCP/IP是启用的,默认的端口都是1433,可以自己修改,非默认端口需要在连接字符串里显式指明。

STEP 12. 到这里再次重启SQL SERVER服务,应该就可以用了。不过还不能用,确认防火墙端口设置,并把SQL SERVER安装目录下,C:\Program Files\Microsoft SQL Server\MSSQL10.SQLEXPRESS\MSSQL\Binn\sqlservr.exe添加到允许的列表中。

1、安装nuget包MSBuildTasks

2、编辑项目的csproj文件,找到被注释掉的target的beforebuild,去掉注释,添加如下代码。代码如下。

1
2
3
4
5
6
7
8
9
<Target Name="BeforeBuild">
<Version VersionFile="Propertiesversion.txt" Major="1" Minor="0" BuildType="Automatic" StartDate="09/01/2017" RevisionType="BuildIncrement">
<Output TaskParameter="Major" PropertyName="Major" />
<Output TaskParameter="Minor" PropertyName="Minor" />
<Output TaskParameter="Build" PropertyName="Build" />
<Output TaskParameter="Revision" PropertyName="Revision" />
</Version>
<AssemblyInfo CodeLanguage="CS" OutputFile="Properties\FileVersionInfo.cs" AssemblyFileVersion="$(Major).$(Minor).$(Build).$(Revision)" />
</Target>     

3、编译项目一次就会在项目文件夹下生成 Propertiesversion.txt

4、在AssemblyInfo.cs文件中包含了AssemblyVersion和AssemblyFileVersion,这里把AssemblyFileVersion单独放到了FileVersionInfo.cs中,编译时会自动生成FileVersionInfo.cs文件,其内容为AssemblyFileVersion,把该文件包含到项目中即可。这里并没有让程序自动生成AssemblyVersion。

这种方式生成版本号会在vs编译时更新版本号。

一、先来看一篇转载文章《在 VS2015 中使用 Qt4

http://tangzx.qiniudn.com/post-0111-qt4-vs2015.html 最早的原文,看不到了

https://github.com/district10/qt4-vs2015x64 原作者的github,里面的东东都下载不了了

二、firecat本人的教程

0、Qt官方

Qt4.8.7官方源码下载

https://download.qt.io/new_archive/qt/4.8/4.8.7/

官网的exe只提供了MSVC2010,没有更高版本的。高版本需要自己下载源码编译。

源码里面的配置文件已经提供了MSVC 2015的编译选项,\qt-everywhere-opensource-src-4.8.7\mkspecs\win32-msvc2015

参照官方提供的编译文档一步一步执行即可;但是配置文件里没有提供MSVC 2017的编译选项。

官方编译的文档

https://doc.qt.io/archives/qt-4.8/installation.html

https://doc.qt.io/archives/qt-4.8/configure-options.html

https://doc.qt.io/archives/qt-4.8/install-win.html

https://doc.qt.io/archives/qt-4.8/install-mac.html

https://doc.qt.io/qt-5/build-sources.html

1、Qt 4.8.7+MSVC 2017

推荐使用第三方提供的源码,它已经是修改好的,里面含有MSVC 2017编译选项,可以编译。

https://github.com/scharsig/Qt Qt4.8.7+MSVC2017源码

https://forum.qt.io/topic/91623/building-qt-4-8-7-with-visual-studio-2017 Qt4.8.7+MSVC2017论坛

https://github.com/sandym/qt-patches 仅供参考,编译补丁

https://github.com/Homebrew/formula-patches/tree/master/qt 仅供参考,编译补丁

https://github.com/BartVandewoestyne/qt_4_8_7_with_vs2017_patch 仅供参考,编译补丁

完整的编译过程:

下载第三方源码https://github.com/scharsig/Qt/tree/master/qt-4.8.7-vs2017 然后解压

-–step1—

Windows桌面-开始-程序-Visual Studio 2017-Visual Studio Tools-VC-x86 Native Tools Command Prompt for VS 2017

-–step2—

C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise>cd F:\Qt\setup-exe\4.8.7\Qt-master\qt-4.8.7-vs2017

-–step3—

F:\Qt\setup-exe\4.8.7\Qt-master\qt-4.8.7-vs2017>configure -help

-–step4—

F:\Qt\setup-exe\4.8.7\Qt-master\qt-4.8.7-vs2017>
configure -make nmake -debug-and-release -opensource -confirm-license -platform win32-msvc2017 -prefix F:\Qt\Qt4.8.7-msvc2017 -nomake examples -nomake tests

如果不想编译这么多功能模块,可以精简为:

configure -make nmake -debug-and-release -opensource -confirm-license -platform win32-msvc2017 -prefix F:\Qt\Qt4.8.7-msvc2017 \
  -no-qt3support -no-multimedia \
  -no-audio-backend -no-phonon -no-phonon-backend -no-libtiff \
  -no-libmng -no-dbus -no-nis -nomake examples -nomake tests

 -release              Compile and link Qt with debugging turned off. -debug                Compile and link Qt with debugging turned on. -nomake tests         Disable building of tests to speed up compilation -nomake examples      Disable building of examples to speed up compilation -confirm-license      Automatically acknowledge the LGPL 2.1 license.

-–step5—

F:\Qt\setup-exe\4.8.7\Qt-master\qt-4.8.7-vs2017>nmake

-–step6—

F:\Qt\setup-exe\4.8.7\Qt-master\qt-4.8.7-vs2017>nmake install

-–step7—

添加到Qt Creator

-–step8—

新建项目测试,Qt Creator+Qt4.8.7+MSVC2017编译项目时,如果报错:

intermediate\moc\moc_rs_actionzoompan.cpp:-1: error: C1041: 无法打开程序数据库“F:\CADCAM\QCAD\src\build-LibreCAD-v1.0.4-qt4-Desktop_Qt_4_8_7_MSVC2017_32bit-Debug\librecad\vc140.pdb”;如果要将多个 CL.EXE 写入同一个 .PDB 文件,请使用 /FS

解决办法:

在Qt Creator的项目文件,即.pro文件中,可以通过QMAKE_CXXFLAGS来给MSVC编译器传递编译开关。

QMAKE_CXXFLAGS += /FS

win32-msvc*:QMAKE_CXXFLAGS += /wd"4819" QMAKE_CXXFLAGS_RELEASE_WITH_DEBUGINFO -= -Zc:strictStrings 

MSVC 2017编译器常见错误的解决:

https://docs.microsoft.com/en-us/cpp/error-messages/compiler-errors-1/c-cpp-build-errors?view=vs-2017

2、Mac OS+Qt 4.8.7

笔者的Mac OS版本是MacOS-10.15-Catalina,高版本的OS和Clang已经不再支持Qt官方发布的Qt4了。

解决办法可以参见我的另一篇博文:https://blog.csdn.net/libaineu2004/article/details/104740623

https://trac.macports.org/ticket/58651 mac下编译qt4遇到问题

https://github.com/macports/macports-ports/tree/master/aqua/qt4-mac mac编译补丁

解决路径显示数字问题

  • 命令及现象
1
git status # 当提交文件中有中文目录时,目录会显示为数字
  • 原因

    在默认设置下中文名不能正常显示,而是显示为八进制的字符编码。

  • 解决办法

    修改git配置文件core.quotepath 为false

    1
    2
    # --global 表示全局配置
    git config --global core.quotepath false

    终端输出中文为乱码

    • 命令及现象

      1
      git log # 当log中有中文日志时,日志显示为乱码
    • 原因

      git commit 默认的编码是UTF-8 ,cmd默认的编码是GB-2312,字符集不同显示为乱码。

    • 解决办法

      1
      2
      3
      4
      5
      # 设置GUI编码为UTF-8
      git config --global gui.encoding utf-8
      # 设置提交日志编码为UTF-8
      git config --global i18n.commitencoding utf-8

配置文件

配置文件默认存放路径

1
2
C:\Users\[用户名]\.gitconfig # 全局配置文件存放路径,修改此文件效果等价git config --global
[存储库根目录]\.git\config # 当前存储库配置文件,只影响当前存储库,效果等价于在此存储库执行 git config
1
2
3
4
5
6
7
8
# 全局设置
[gui]
encoding = utf-8
[core]
quotePath = false
[i18n]
commitencoding = utf-8

非常详细的 Docker 学习笔记 - Docker - 服务器软件 - 深度开源 (open-open.com)

Docker-创建和分享应用(3) - 头痛不头痛 - 博客园 (cnblogs.com)

常用docker命令,及一些坑_dockerfile 2>&1-CSDN博客

Docker常用命令_docker -i -t -v-CSDN博客

Docker 常用命令 - Phuker’s Blog

详解Docker 容器基础系统镜像打包_docker_脚本之家 (jb51.net)

一种docker基础镜像制作方法_docker 从iso制作镜像-CSDN博客

系统运维|两种方式创建你自己的 Docker 基本映像 (linux.cn)

Windows Azure 是微软基于云计算的操作系统,能够为开发者提供一个平台,帮助开发可运行在云服务器、数据中心、Web 和 PC 上的应用程序。

Azure 是一种灵活和支持互操作的平台,能够将处于云端的开发者个人能力,同微软全球数据中心网络托管的服务,比如存储、计算和网络基础设施服务,紧密结合起来。帮助开发者在“云端”和“客户端”同时部署应用,使得企业与用户都能共享资源。

本文整理了丰富的 Windows Azure 学习资源,帮助开发者能全面地学习 Windows Azure 知识,并将 Windows Azure 运用在项目和实际工作中。

通过本系列博客,先来了解一下 Windows Azure 平台的基本知识。Windows Azure,正如同桌面操作系统 Windows 和服务器操作系统 Windows Server 一样,是一个云端的操作系统。开发人员可以使用同一套技术:.NET(包括 Silverlight),或者 Win32,同时针对桌面,服务器,以及云,开发程序,而不需要针对某个平台学习专门的技术。Visual Studio 和 Expression Studio 为开发人员提供了强大的工具支持。

Windows Azure平台简介(一):定位与产品结构

Windows Azure平台简介(二):Windows Azure

Windows Azure平台简介(三):AppFabric

Windows Azure平台简介(四):SQL Azure以及其他服务

在开始本教学之前,请确保你从 Windows Azure 平台下载下载并安装了最新的 Windows Azure 开发工具。本教学使用 Visual Studio 2010 作为开发工具。

Windows Azure入门教学系列 (一):创建第一个WebRole程序

Windows Azure入门教学系列 (二):部署第一个Web Role程序

Windows Azure入门教学系列 (三):创建第一个Worker Role程序

Windows Azure入门教学系列 (四):使用Blob Storage

Windows Azure入门教学系列 (五):使用Queue Storage

Windows Azure入门教学系列 (六):使用Table Storage

Windows Azure入门教学系列 (七):使用REST API访问Storage Service

Windows Azure入门教学系列 (八):使用Windows Azure Drive

Azure学习笔记:Web Site(1)

Azure学习笔记:Service Bus(2)

Azure学习笔记:Storage(3)

Azure学习笔记:Cloud Service(4)

Azure Storage 是微软 Azure 云提供的云端存储解决方案,当前支持的存储类型有 Blob、Queue、File 和 Table。

Azure Blob Storage 基本用法 – Azure Storage 之 Blob

Azure Queue Storage 基本用法 – Azure Storage 之 Queue

Azure File Storage 基本用法 – Azure Storage 之 File

Azure Table storage 基本用法 – Azure Storage 之 Table

Windows Azure Storage 支持三重冗余的。保存在 Azure Storage 的内容,会在同一个数据中心保留有3个副本。这样的好处显而易见:当数据中心发生一般性故障的时候,比如磁盘损坏,机架服务器损坏等,用户保存在 Azure Storage 的数据不会丢失。每次对于 Storage 的写操作,都会对三个副本进行同步写操作,等到在副本操作完毕之后,才会返回执行成功给客户端。

Windows Azure 提供了三种不同类型的存储服务(这里的存储是非关系型数据,比如图片、文档等文件),用来提供给 Windows Azure 上运行的应用程序存储数据使用。依据不同的存储格式会有不同的限制,因为这些存储服务都是以分散式巨量存储(Distributed Mass Storage)为核心概念所设计出来的,为了要达成快速在分散式存储空间中存储与管理数据(还包含高可用度的赘余存储管理),微软有在数据的存储上做一些限制。

微软还提供了 REST API 来方便用户操作 Storage Service。

(1)Windows Azure Storage Service存储服务

(2)Windows Azure Storage Service存储服务之Blob详解(上)

(3)Windows Azure Storage Service存储服务之Blob详解(中)

(4)Windows Azure Storage Service存储服务之Blob Share Access Signature

(5)Windows Azure Drive

(6)Windows Azure Storage之Table

(7)使用工具管理Windows Azure Storage

(8)Windows Azure 上的托管服务CDN (上)

(9)Windows Azure 上的托管服务CDN (中) Blob Service

(10)Windows Azure 上的托管服务CDN (下) Hosted Service

(11)计算你存储的Blob的大小

(12)本地冗余存储 vs 地理冗余存储 (上)

(13)本地冗余存储 vs 地理冗余存储 (下)

(14)使用Azure Blob的PutBlock方法,实现文件的分块、离线上传

(15)使用WCF服务,将本地图片上传至Azure Storage (上) 服务器端代码

(16)使用WCF服务,将本地图片上传至Azure Storage (上) 客户端代码

(17)Azure Storage读取访问地域冗余(Read Access – Geo Redundant Storage, RA-GRS)

(18)使用HTML5 Portal的Azure CDN服务

(19)再谈Azure Block Blob和Page Blob

(20)使用Azure File实现共享文件夹

(21)使用AzCopy工具,加快Azure Storage传输速度

(22)Azure Storage如何支持多级目录

(23)计算Azure VHD实际使用容量

PowerShell 是管理 Azure 的最好方式之一,通过使用 PowerShell 脚本可以把很多的工作自动化。比如对于 Azure 上的虚拟机,可以设置定时关机操作,并在适当的时间把它开机,这样就能减少虚拟机的运行时间,同时也能为节能减排做出贡献。

(1)PowerShell入门

(2)修改Azure订阅名称

(3)上传证书

(4)使用PowerShell管理多个订阅

(5)使用Azure PowerShell创建简单的Azure虚拟机和Linux虚拟机

(6)设置单个Virtual Machine Endpoint

(7)使用CSV文件批量设置Virtual Machine Endpoint

(8)使用PowerShell设置Azure负载均衡器规则

(9)使用PowerShell导出订阅下所有的Azure VM的Public IP和Private IP

(10)使用PowerShell导出订阅下所有的Azure VM和Cloud Service的高可用情况

(11)使用自定义虚拟机镜像模板,创建Azure虚拟机并绑定公网IP(VIP)和内网IP(DIP)

(12)通过Azure PowerShell创建SSH登录的Linux VM

SQL Azure 是微软基于 Microsoft SQL Server Denali,也就是 SQL Server 2012 构建的云端关系型数据库服务。SQL Azure 是 SQL Server 的一个大子集,能够实现 SQL Server 的绝大部分功能,并且将它们作为云端的服务来扩展。SQL Azure Database 提供内置的高精准、可用性、功效与其他功能。

(1)入门

(2)SQL Azure vs SQL Server

(3)创建一个SQL Azure 服务器

(4)创建一个SQL Azure数据库

(5)使用SQL Server Management Studio连接SQL Azure

(6)使用Project Houston管理SQL Azure

(7)在SQL Azure Database中执行的T-SQL

(8)使用Visual Studio 2010开发应用连接SQL Azure云端数据库

(9)把本地的SQL Server数据库迁移到SQL Azure云数据库上

(10)SQL Azure Data Sync数据同步功能(上)

(11)SQL Azure Data Sync数据同步功能(下)

(12)使用新Portal 创建 SQL Azure Database

(13)Azure的两种关系型数据库服务:SQL Azure与SQL Server VM的不同

(14)将云端SQL Azure中的数据库备份到本地SQL Server

(15)SQL Azure 新的规格

(16)创建PaaS SQL Azure V12数据库

(17)SQL Azure V12 - 跨数据中心标准地域复制(Standard Geo-Replication)

(18)使用External Table实现垮库查询

(19)Stretch Database 概览

(20)使用SQL Server 2016 Upgrade Advisor

(21)将整张表都迁移到Azure Stretch Database里

(22)迁移部分数据到Azure Stretch Database

1. 《Windows Azure 实战》全面深入,完整覆盖 Windows Azure 所有关键技术和理论,详细讲解云计算开发流程、云服务架构(可用性、可靠性和高性能)、云设备整合、系统整合,以及云计算项目的管理。
注重实战,68个精心策划的针对特定实际应用场景的真实案例,详细呈现案例的设计思路和完整实现步骤。

2. 《Windows Azure 从入门到精通》介绍了如何构建和管理云端的可扩展应用,一次一个知识点,同时辅之以适当的练习,可帮助读者轻松掌握基本的编程技能,掌握 Windows Azure 云计算平台的核心服务和特性,是一本理想的入门教程。

3. 《云计算与Azure平台实战》解决了从本地转移到基于云的应用程序时,可能面临的各种问题;展示了如何将 ASP.NET 身份验证和角色管理用应用于 Azure Web 角色;揭示了迁移到 Windows Azure 时把计算服务卸载到一个或多个 WorkerWeb 角色的益处;讲解如何为共享 Azure 表选择最合适的 PartionKey 和 RowKey 值的组合;探讨了改善 Azure 表的可扩展性和性能的方法。

4. 《走进云计算:Windows Azure实战手记》介绍了你必须学会的微软云开发技术,介绍目前最火爆的云计算,深入剖析微软最新的云开发平台,涵盖 Windows Azure 环境、存储服务、SQL Azure 数据库与 App Fabric 服务平台 Step by Step 递进教学,初学者可按部就班地学习云应用的开发技术。

相关阅读:

Azure Blob Storage 基本用法 – Azure Storage 之 Blob

Azure Queue Storage 基本用法 – Azure Storage 之 Queue

Azure File Storage 基本用法 – Azure Storage 之 File

Azure Table storage 基本用法 – Azure Storage 之 Table

一、需求缘起

几乎所有的业务系统,都有生成一个记录标识的需求,例如:

(1)消息标识:message-id

(2)订单标识:order-id

(3)帖子标识:tiezi-id

这个记录标识往往就是数据库中的唯一主键,数据库上会建立聚集索引(cluster index),即在物理存储上以这个字段排序。

这个记录标识上的查询,往往又有分页或者排序的业务需求,例如:

(1)拉取最新的一页消息:selectmessage-id/ order by time/ limit 100

(2)拉取最新的一页订单:selectorder-id/ order by time/ limit 100

(3)拉取最新的一页帖子:selecttiezi-id/ order by time/ limit 100

所以往往要有一个time字段,并且在time字段上建立普通索引(non-cluster index)。

我们都知道普通索引存储的是实际记录的指针,其访问效率会比聚集索引慢,如果记录标识在生成时能够基本按照时间有序,则可以省去这个time字段的索引查询:

select message-id/ (order by message-id)/limit 100

再次强调,能这么做的前提是,message-id的生成基本是趋势时间递增的

这就引出了记录标识生成(也就是上文提到的三个XXX-id)的两大核心需求:

(1)全局唯一

(2)趋势有序

这也是本文要讨论的核心问题:如何高效生成趋势有序的全局唯一ID。

二、常见方法、不足与优化

【常见方法一:使用数据库的 auto_increment 来生成全局唯一递增ID】

优点:

(1)简单,使用数据库已有的功能

(2)能够保证唯一性

(3)能够保证递增性

(4)步长固定

缺点:

(1)可用性难以保证:数据库常见架构是一主多从+读写分离,生成自增ID是写请求,主库挂了就玩不转了

(2)扩展性差,性能有上限:因为写入是单点,数据库主库的写性能决定ID的生成性能上限,并且难以扩展

改进方法:

(1)增加主库,避免写入单点

(2)数据水平切分,保证各主库生成的ID不重复

如上图所述,由1个写库变成3个写库,每个写库设置不同的auto_increment初始值,以及相同的增长步长,以保证每个数据库生成的ID是不同的(上图中库0生成0,3,6,9…,库1生成1,4,7,10,库2生成2,5,8,11…)

改进后的架构保证了可用性,但缺点是:

(1)丧失了ID生成的“绝对递增性”:先访问库0生成0,3,再访问库1生成1,可能导致在非常短的时间内,ID生成不是绝对递增的(这个问题不大,我们的目标是趋势递增,不是绝对递增)

(2)数据库的写压力依然很大,每次生成ID都要访问数据库

为了解决上述两个问题,引出了第二个常见的方案

【常见方法二:单点批量ID生成服务】

分布式系统之所以难,很重要的原因之一是“没有一个全局时钟,难以保证绝对的时序”,要想保证绝对的时序,还是只能使用单点服务,用本地时钟保证“绝对时序”。数据库写压力大,是因为每次生成ID都访问了数据库,可以使用批量的方式降低数据库写压力。

如上图所述,数据库使用双master保证可用性,数据库中只存储当前ID的最大值,例如0。ID生成服务假设每次批量拉取6个ID,服务访问数据库,将当前ID的最大值修改为5,这样应用访问ID生成服务索要ID,ID生成服务不需要每次访问数据库,就能依次派发0,1,2,3,4,5这些ID了,当ID发完后,再将ID的最大值修改为11,就能再次派发6,7,8,9,10,11这些ID了,于是数据库的压力就降低到原来的1/6了。

优点

(1)保证了ID生成的绝对递增有序

(2)大大的降低了数据库的压力,ID生成可以做到每秒生成几万几十万个

缺点

(1)服务仍然是单点

(2)如果服务挂了,服务重启起来之后,继续生成ID可能会不连续,中间出现空洞(服务内存是保存着0,1,2,3,4,5,数据库中max-id是5,分配到3时,服务重启了,下次会从6开始分配,4和5就成了空洞,不过这个问题也不大)

(3)虽然每秒可以生成几万几十万个ID,但毕竟还是有性能上限,无法进行水平扩展

改进方法

单点服务的常用高可用优化方案是“备用服务”,也叫“影子服务”,所以我们能用以下方法优化上述缺点(1):

如上图,对外提供的服务是主服务,有一个影子服务时刻处于备用状态,当主服务挂了的时候影子服务顶上。这个切换的过程对调用方是透明的,可以自动完成,常用的技术是vip+keepalived,具体就不在这里展开。

 

【常见方法三:uuid】

上述方案来生成ID,虽然性能大增,但由于是单点系统,总还是存在性能上限的。同时,上述两种方案,不管是数据库还是服务来生成ID,业务方Application都需要进行一次远程调用,比较耗时。有没有一种本地生成ID的方法,即高性能,又时延低呢?

uuid是一种常见的方案:string ID =GenUUID();

优点

(1)本地生成ID,不需要进行远程调用,时延低

(2)扩展性好,基本可以认为没有性能上限

缺点

(1)无法保证趋势递增

(2)uuid过长,往往用字符串表示,作为主键建立索引查询效率低,常见优化方案为“转化为两个uint64整数存储”或者“折半存储”(折半后不能保证唯一性)

【常见方法四:取当前毫秒数】

uuid是一个本地算法,生成性能高,但无法保证趋势递增,且作为字符串ID检索效率低,有没有一种能保证递增的本地算法呢?

取当前毫秒数是一种常见方案:uint64 ID = GenTimeMS();

优点

(1)本地生成ID,不需要进行远程调用,时延低

(2)生成的ID趋势递增

(3)生成的ID是整数,建立索引后查询效率高

缺点

(1)如果并发量超过1000,会生成重复的ID

我去,这个缺点要了命了,不能保证ID的唯一性。当然,使用微秒可以降低冲突概率,但每秒最多只能生成1000000个ID,再多的话就一定会冲突了,所以使用微秒并不从根本上解决问题。

【常见方法五:类snowflake算法】

snowflake是twitter开源的分布式ID生成算法,其核心思想是:一个long型的ID,使用其中41bit作为毫秒数,10bit作为机器编号,12bit作为毫秒内序列号。这个算法单机每秒内理论上最多可以生成1000*(2^12),也就是400W的ID,完全能满足业务的需求。

借鉴snowflake的思想,结合各公司的业务逻辑和并发量,可以实现自己的分布式ID生成算法。

举例,假设某公司ID生成器服务的需求如下:

(1)单机高峰并发量小于1W,预计未来5年单机高峰并发量小于10W

(2)有2个机房,预计未来5年机房数量小于4个

(3)每个机房机器数小于100台

(4)目前有5个业务线有ID生成需求,预计未来业务线数量小于10个

(5)…

分析过程如下:

(1)高位取从2016年1月1日到现在的毫秒数(假设系统ID生成器服务在这个时间之后上线),假设系统至少运行10年,那至少需要10年*365天*24小时*3600秒*1000毫秒=320*10^9,差不多预留39bit给毫秒数

(2)每秒的单机高峰并发量小于10W,即平均每毫秒的单机高峰并发量小于100,差不多预留7bit给每毫秒内序列号

(3)5年内机房数小于4个,预留2bit给机房标识

(4)每个机房小于100台机器,预留7bit给每个机房内的服务器标识

(5)业务线小于10个,预留4bit给业务线标识

这样设计的64bit标识,可以保证:

(1)每个业务线、每个机房、每个机器生成的ID都是不同的

(2)同一个机器,每个毫秒内生成的ID都是不同的

(3)同一个机器,同一个毫秒内,以序列号区区分保证生成的ID是不同的

(4)将毫秒数放在最高位,保证生成的ID是趋势递增的

缺点

(1)由于“没有一个全局时钟”,每台服务器分配的ID是绝对递增的,但从全局看,生成的ID只是趋势递增的(有些服务器的时间早,有些服务器的时间晚)

最后一个容易忽略的问题

生成的ID,例如message-id/ order-id/ tiezi-id,在数据量大时往往需要分库分表,这些ID经常作为取模分库分表的依据,为了分库分表后数据均匀,ID生成往往有“取模随机性”的需求,所以我们通常把每秒内的序列号放在ID的最末位,保证生成的ID是随机的。

又如果,我们在跨毫秒时,序列号总是归0,会使得序列号为0的ID比较多,导致生成的ID取模后不均匀。解决方法是,序列号不是每次都归0,而是归一个0到9的随机数,这个地方。

下面附上C#.Net 实现snowflake算法实现

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CommonTools
{
public class SnowFlake
{
//机器ID
private static long workerId;
private static long twepoch = 687888001020L; //唯一时间,这是一个避免重复的随机量,自行设定不要大于当前时间戳
private static long sequence = 0L;
private static int workerIdBits = 4; //机器码字节数。4个字节用来保存机器码
public static long maxWorkerId = -1L ^ -1L << workerIdBits; //最大机器ID
private static int sequenceBits = 10; //计数器字节数,10个字节用来保存计数码
private static int workerIdShift = sequenceBits; //机器码数据左移位数,就是后面计数器占用的位数
private static int timestampLeftShift = sequenceBits + workerIdBits; //时间戳左移动位数就是机器码和计数器总字节数
public static long sequenceMask = -1L ^ -1L << sequenceBits; //一微秒内可以产生计数,如果达到该值则等到下一微妙在进行生成
private long lastTimestamp = -1L;

private static SnowFlake sigle = null;

private SnowFlake(long workerId)
{
if (workerId > maxWorkerId || workerId < 0)
throw new Exception(string.Format("worker Id can't be greater than {0} or less than 0 ", workerId));
SnowFlake.workerId = workerId;
}

public static long NewID()
{
if (sigle == null)
{
sigle = new SnowFlake(4L); //此处4L应该从配置文件里读取当前机器配置

}

return sigle.nextId();


}



private long nextId()
{
lock (this)
{
long timestamp = timeGen();
if(this.lastTimestamp == timestamp){ //同一微妙中生成ID
//用&运算计算该微秒内产生的计数是否已经到达上限
SnowFlake.sequence = (SnowFlake.sequence + 1) & SnowFlake.sequenceMask;
if (SnowFlake.sequence == 0)
{
//一微妙内产生的ID计数已达上限,等待下一微妙
timestamp = tillNextMillis(this.lastTimestamp);
}
}
else{ //不同微秒生成ID
SnowFlake.sequence = 0; //计数清0
}
if(timestamp < lastTimestamp)
{ //如果当前时间戳比上一次生成ID时时间戳还小,抛出异常,因为不能保证现在生成的ID之前没有生成过
throw new Exception(string.Format("Clock moved backwards. Refusing to generate id for {0} milliseconds",
this.lastTimestamp - timestamp));
}
this.lastTimestamp = timestamp; //把当前时间戳保存为最后生成ID的时间戳
long nextId = (timestamp - twepoch << timestampLeftShift)
| SnowFlake.workerId << SnowFlake.workerIdShift | SnowFlake.sequence;
return nextId;
}
}

/// <summary>
/// 获取下一微秒时间戳
/// </summary>
/// <param name="lastTimestamp"></param>
/// <returns></returns>
private long tillNextMillis(long lastTimestamp)
{
long timestamp = timeGen();
while(timestamp <= lastTimestamp)
{
timestamp = timeGen();
}
return timestamp;
}

/// <summary>
/// 生成当前时间戳
/// </summary>
/// <returns></returns>
private long timeGen()
{
return (long)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds;
}

}
}