0%

对很多朋友来说,这是一份迟来的教程。还好,经过几番摸索和尝试,FreeNAS 9.3 Jails中安装aira2的方法终于找到了。

使用最新的PluginJail模板

FreeNAS 9.3 系列的pluginjail模板有了更新,你发现了吗?模板的名称没有变化,但这可是2015年4月2日新鲜出炉的,赶快按照《FreeNAS:虚拟机(Jails)入门篇》提供的方法创建最新的pluginjail模板吧,注意,下方提供的都是64位模板。同时建议大家顺便把standardjail的模板也下载下来,添加到FreeNAS里面。

官方Jails模板下载地址http://download.freenas.org/jails/9.3/x64/

百度网盘分流下载地址http://pan.baidu.com/s/1gd7oAqB#path=%2FFreeNAS%2Fjails%2F9.3%2Fx64

20150414112844

如果你的FreeNAS 9.3更新到了FreeNAS-9.3-STABLE-201504100216或更新的版本,你会发现添加Jail模板的时候多出了一个名为Mtree的项,你在上面提供的下载项中能看到.mtree后缀的文件,这是与MD5类似的另外一种文件校验方式,一方面是检查Jail模板的完整性,另一方面也是防止模板文件被恶意篡改导致一些安全隐患。存在就有价值,建议大家同时下载.tgz和.mtree这两个后缀的文件。

20150414113645

创建Aria2 Jail

Jail的创建过程在《FreeNAS:虚拟机(Jails)入门篇》中已经有所介绍,这里不再赘述,需要提示的是如果你不指定Jail的模板类型,那么FreeNAS默认会使用standardjail模板创建Jail,这也是为什么前面会提到建议大家同时把standard模板也填进去的原因。如下图所示,这里我们创建了一个名为aria2的Jail,IP地址为192.168.1.50,模板类型为standard。

20150414114936

通过SSH访问Aria2 Jail

教程《如何通过SSH访问Jail虚拟机》中介绍了如何通过SSH访问指定的Jail,此处不再赘述。

更新Aria2 Jail的pkg和port

特别提示:接下来的操作均在Jail中进行。

更新pkg包管理器命令:pkg update

通过pkg升级旧版程序:pkg upgrade

下载最新的port树:portsnap fetch

解压port树:portsnap extract

更新pkg和port需要一些时间,这里建议大家在全部更新完毕以后给aira2 jail的数据集创建一个快照,这样一来,不论接下来的操作出现了何种错误,我们都可以通过回滚快照,将Jail恢复到创建快照时的状态,这样可以省去许多重复操作,节省时间。不过FreeNAS的ZFS仅支持回滚最新的快照,所以每操作一步创建一个快照的做法没有实际意义。

20150414120552

aria2的安装概述

在FreeNAS Jail中安装aria2,我们采用的是编译安装方式,因为pkg search压根找不到aria2的软件包。上面一步之所以既要更新pkg包管理,又要更新port树,正是因为我们需要用pkg的方式快速安装编译时依赖的工具和库(如果这些库和工具也用port编译方式安装的话人是会疯的),同时又要用port来编译安装aria2。接下来让我们开始安装吧。

安装aria2编译时需要的程序

既然要编译安装aria2,就需要编译工具以及aria2依赖的一些库文件,前面这句话不懂没有关系,照着做就是了。这里给你提供两种方法,一种方法是直接使用pkg命令,从freebsd服务器下载相关的程序进行安装。另一种方法是让pkg命令安装我们手动下载程序。两种方法要做的是同一件事,第一种最简单,但碰到服务器连接不稳定的时候比较抓狂,第二种稍微有点麻烦,但是速度非常快。

方法一:直接使用pkg命令安装

把下面的命令复制粘贴到SSH终端里面执行就可以了,里面加了 -y 参数,全自动下载安装。这一步会自动从pkg.freebsd.org下载程序的txz包,GCC的包很大,下载的过程很漫长。

pkg install -y lang/gcc security/openssl devel/pkgconf devel/gettext databases/sqlite3 textproc/libxml2

20150413195531

方法二:让pkg安装我们手动下载的程序

先去下载我们为你准备好的程序包,点此下载 aria2c-pkg.zip,下载到本地把他解压,通过Filezilla等支持SFTP的工具将文件夹里的.txz文件全部上传到Jail的**/var/cache/pkg** 目录。

20150414123312

把下面的命令复制粘贴到SSH终端里面执行就可以了:

cd /var/cache/pkg/ && pkg install -y gcc-4.8.4_1.txz openssl-1.0.2.txz pkgconf-0.9.8.txz gettext-0.19.4.txz libxml2-2.9.2_2-b106850595.txz mpfr-3.1.2_2.txz gmp-5.1.3_2.txz mpc-1.0.3.txz gcc-ecj-4.5.txz binutils-2.25.txz gettext-tools-0.19.4.txz gettext-runtime-0.19.4

20150414123650

(可选)创建openssl的配置文件,执行下面命令:

cp /usr/local/openssl/openssl.cnf.sample /usr/local/openssl/openssl.cnf

安装Aria2

终于到这一步了,执行下面的命令开始吧:

cd /usr/ports/www/aria2/ && make install clean

编译过程中会显示下图所示的设置界面,按回车保持默认即可,如果你想要使用CA_BUNDLE这项功能,可以用空格键选中。

20150413232003

编译安装需要一点时间,耐心等待吧,如下图所示,屏幕就这样不停的滚动着各种信息。

20150414124625

FreeNAS主机CPU的性能越好,相应编译的过程就越快,反之亦然。当你看到下图所示的内容,代表aria2已经安装完成。

20150414124746

试着使用aria2c 命令下载一些东西吧,它已经可以开始为你工作了。

20150414125113

配置Aria2 WebGUI

只是安装好了aria2在Jail里面还不足以称之为“下载机”,我们需要给aria2配上一个WebGUI,这样玩起来才愉快。话说aria2的webgui还真是不少,目前维护比较好的是webui-aria2yaaw。你可以分别试试看哪一个看着顺眼用着顺手,这里以Yaaw为例来介绍如何为Aria2配置WebGUI。

WebGUI顾名思义就是基于浏览器的用户管理界面,webui-aria2和yaaw都号称是无需www服务器也无需任何动态语言,用户只需要用浏览器直接访问即可使用。但是,它前提是aria2必须安装在本地主机,我们的aria2安装在FreeNAS的Jail里面,如果想访问,没有www服务器必然行不通的。

对我们FreeNAS用户而言,现在是时候让Nginx登场了,你还记得它吗?参照教程《FreeNAS Jail中安装Nginx服务器》,在aria2 jail里面安装好Nginx。

进入nginx的默认网站根目录:

cd /usr/local/www/nginx

使用下面的命令下载最新的yaaw:

aria2c –ca-certificate=/usr/local/share/certs/ca-root-nss.crt https://github.com/binux/yaaw/archive/master.zip

解压yaaw:

unzip yaaw-master.zip

修改nginx配置文件:

vi /usr/local/etc/nginx/nginx.conf

如下图所示,找到 location / {} 部分找到 root 一行,添加yaaw-master,不要漏掉末尾的半角”;”分号。

20150414132053

修改完成以后重启nginx服务器:

service nginx restart

用浏览器访问Aria2 Jail的IP地址,应该可以看到下图所示的页面,代表yaaw已经安装完成。但页面上会有错误提示,这是因为我们还没启动aria2的守护进程。

20150414132356

在配置aria2的守护进程之前,建议先在FreeNAS里面给Jail创建一个专门用来存储下载文件的数据集映射到Jail上面,这样就可以通过给这个数据集创建CIFS共享,随时用Windows主机查看下载好的文件了。

这里我们创建了一个名为 aria2 的 windows 类型数据集,所有者和所有者组均设置为nobody。原因是CIFS共享的默认匿名访客身份就是nobody,这样创建匿名的CIFS共享可以对这个数据集有完全的控制权限。

20150414132839

我们将aria2数据集添加给jail,并映射到jail的 /mnt 目录。如果你不了解如何给插件(Jail)添加存储,请查阅《FreeNAS中为插件添加映射存储空间》。

20150414133114

添加好存储后,我们在/mnt目录里面创建两个文件夹,conf文件夹用来存储aria2守护进程的配置文件,download文件夹用来存储下载的文件。

执行下面的命令,一次性完成两个文件夹的创建:

cd /mnt && mkdir conf download

接下来要创建aria2的配置文件,建议给aria2数据集创建CIFS匿名共享后直接在Windows上面操作,这样会比较方便。你可能会问为什么要给aria2创建配置文件,不创建就不能运行aria2吗?当然,不创建也可以以守护进程的方式运行aria2,不过在系统重启以后,你对aria2所做的配置就都会丢失。关于创建CIFS匿名共享的相关内容请参考《FreeNAS:创建 CIFSF 匿名共享》。

在conf文件夹中创建rpc.conf文件,贴上以下内容:

###########RPC相关选项########### enable-rpc #允许所有来源, web界面跨域权限需要 rpc-allow-origin-all=true #允许非外部访问 rpc-listen-all=true ##RCP认证 #rpc-user= #rpc-passwd= #RPC端口, 仅当默认端口6800被占用时修改 #rpc-listen-port=6900 ###############速度相关############# #最大服务器设置 -x max-connection-per-server=10 #分割选项 -s 几线程 split=10 #最大并行下载 max-concurrent-downloads=3 #分片的最小大小 min-split-size=20M # 启用断点续传 #Continue downloading a partially downloaded file if a corresponding control file exists. continue #下载速度限制 max-overall-download-limit=0 #单文件速度限制 max-download-limit=0 #上传速度限制 max-overall-upload-limit=0 #单文件速度限制 max-upload-limit=0 #断开速度过慢的连接 #lowest-speed-limit=0 #####文件和缓存相关的配置节########## #文件缓存, 使用内置的文件缓存, 如果你不相信Linux内核文件缓存和磁盘内置缓存时使用, 需要1.16及以上版本 #disk-cache=0 #另一种Linux文件缓存方式, 使用前确保您使用的内核支持此选项, 需要1.15及以上版本(?) #enable-mmap=true #文件预分配, 能有效降低文件碎片, 提高磁盘性能. 缺点是预分配时间较长 #所需时间 none < falloc ? trunc << prealloc, falloc和trunc需要文件系统和内核支持 #这里不预先申请磁盘空间,如果是ETX4之类的 可以改成 falloc file-allocation=none #下载目录 dir=/mnt/download #Session 文件相关 input-file=/mnt/conf/rpc.aria2.session save-session=/mnt/conf/rpc.aria2.session #定时保存会话,需要1.16.1之后的release版 save-session-interval=60 #######P2P相关####### #启用本地节点查找 bt-enable-lpd=true #添加额外的tracker #bt-tracker=,… #单种子最大连接数 #bt-max-peers=55 #强制加密, 防迅雷必备 #bt-require-crypto=true #当下载的文件是一个种子(以.torrent结尾)时, 自动下载BT follow-torrent=true #BT监听端口, 当端口屏蔽时使用 #listen-port=6881-6999 #开启用户交换 enable-peer-exchange=true #修改做种设置, 允许做种 seed-ratio=1 #保存会话 force-save=true bt-hash-check-seed=true bt-seed-unverified=true bt-save-metadata=true

在conf目录中创建名为rpc.aria2.session的空白文件。

20150414135228

因为aria2数据集已经映射到了Jail的/mnt目录,相应的,我们在Jail里面查看/mnt目录,可以看到新创建好的文件。

20150414135313

用创建好的配置文件启动aria2测试一下:

aria2c –conf-path=/mnt/conf/rpc.conf –ca-certificate=/usr/local/share/certs/ca-root-nss.crt

20150414135723

这时候在用浏览器刷新一下WebGUI,可以看到错误提示已经没有了,在设置中可以看到WebGUI已经检测到我们创建的配置文件信息,一切工作正常。

20150414135806

使用 ctrl + c 组合键停掉aria2进程,前面只是测试aria2是否工作正常,如果每次都要手动启动aria2实在麻烦,接下来我们要让aria2开机自启动。

让Aria2开机自启动

FreeBSD下面的Aria2很贴心,为我们准备了管理脚本,我们只需要简单配置一下就可以使用了。

使用下面命令备份aria2管理脚本:

cp /usr/local/etc/rc.d/aria2 /usr/local/etc/rc.d/aria2-bak

编辑aria2管理脚本:

vi /usr/local/etc/rc.d/aria2

修改aria2_config路径,粗体字部分,请根据你的实际配置文件路径和名称进行设置。

aria2_config=${aria2_config:-“**/mnt/conf/rpc.conf**“}

添加aria2_flags路径,粗体字部分。

aria2_flags=${aria2_flags:-“–ca-certificate=/usr/local/share/certs/ca-root-nss.crt“}

20150414150932

将aria2项添加到/etc/rc.conf

sysrc ‘aria2_enable=YES’

或直接编辑 /etc/rc.conf 文件,另起一行添加 aria2_enable=”YES”,如下图。

20150414151528

接下来可以使用service命令启动、重启、停止aria2了:

  • service aria2 start    // 启动aria2
  • service aria2 restart    // 重启aria2
  • service aria2 stop    // 停止aria2

一切就绪,FreeNAS Jail Aria2 下载机打造成功!

一些aria2相关的插件资源:

GMap.NET是什么?

来看看它的官方说明:GMap.NET is great and Powerful, Free, cross platform, open source .NET control. Enable use routing, geocoding, directions and maps from Coogle, Yahoo!, Bing, OpenStreetMap, ArcGIS, Pergo, SigPac, Yendux, Mapy.cz, Maps.lt, iKarte.lv, NearMap, OviMap, CloudMade, WikiMapia, MapQuest in Windows Forms & Presentation, supports caching and runs on windows mobile!

GMap.NET是一个强大、免费、跨平台、开源的.NET控件,它在Windows Forms 和WPF环境中能够使用来自Google, Yahoo!, Bing, OpenStreetMap, ArcGIS, Pergo, SigPac等地图,并可以实现寻找路径、地理编码以及地图展示功能,并支持缓存和运行在Mobile环境中。

项目主页:https://greatmaps.codeplex.com/

如何在WinForm中使用GMap.Net

下载GMap.Net,我下载的版本:greatmaps_81b71bf30091,编译三个核心项目:

GMap.Net.Core:核心DLL

GMap.Net.WindowsForms:WinForm中使用的DLL

GMap.NET.WindowsPresentation:WPF中使用的DLL

在WinForm项目中使用GMap:

1、新建一个Visual C# 的Windows窗口程序。添加对GMap.Net.Core.DLL和GMap.Net.WindowsForms.DLL的引用。

2、在项目中添加一个UserControl,这里取名为MapControl,修改这个UserControl,使其继承于GMapControl,这就是展示地图的控件。修改如下:

复制代码

using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Data; using System.Linq; using System.Text; using System.Windows.Forms; using GMap.NET.WindowsForms; namespace GMapWinFormDemo
{ public partial class MapControl : GMapControl
{ public MapControl()
{
InitializeComponent();
}
}
}

复制代码

3、编译项目,在我们的Form设计窗口下,在工具箱中(tool box)里就可以看到这个MapControl,将这个MapControl加到Form中。

4、在主Form中添加相关的代码如下

复制代码

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using GMap.NET; using GMap.NET.WindowsForms; using GMap.NET.MapProviders;
using GMap.NET.WindowsForms.Markers; namespace GMapDemo
{ public partial class MapForm : Form
{ private GMapOverlay markersOverlay = new GMapOverlay(“markers”); //放置marker的图层

    public MapForm()
    {
        InitializeComponent(); try {
            System.Net.IPHostEntry e \= System.Net.Dns.GetHostEntry("ditu.google.cn");
        } catch {
            mapControl.Manager.Mode \= AccessMode.CacheOnly;
            MessageBox.Show("No internet connection avaible, going to CacheOnly mode.", "GMap.NET Demo", MessageBoxButtons.OK, MessageBoxIcon.Warning);
        }

        mapControl.CacheLocation \= Environment.CurrentDirectory + "\\\\GMapCache\\\\"; //缓存位置
        mapControl.MapProvider = GMapProviders.GoogleChinaMap; //google china 地图
        mapControl.MinZoom = 2;  //最小比例
        mapControl.MaxZoom = 24; //最大比例
        mapControl.Zoom = 10;     //当前比例
        mapControl.ShowCenter = false; //不显示中心十字点
        mapControl.DragButton = System.Windows.Forms.MouseButtons.Left; //左键拖拽地图
        mapControl.Position = new PointLatLng(32.064,118.704); //地图中心位置:南京

mapControl.Overlays.Add(markersOverlay);

        mapControl.MouseClick += new MouseEventHandler(mapControl\_MouseClick);
    } void mapControl\_MouseClick(object sender, MouseEventArgs e)
    { if (e.Button == System.Windows.Forms.MouseButtons.Right)
        {
            PointLatLng point \= mapControl.FromLocalToLatLng(e.X, e.Y);
            GMapMarker marker \= new GMarkerGoogle(point, GMarkerGoogleType.green);
            markersOverlay.Markers.Add(marker);
        }
    }
}

复制代码

5、编译、运行项目就可以看到地图,这里使用的是在线的Google中国的地图,地图控件的几个主要属性:

MapProvider:地图服务的提供者。

MinZoom:最小缩放,最小可为1。

MaxZoom:最大缩放,最大为24.

Zoom:当前缩放。

ShowCenter:是否显示中心点(最好为false,否则地图中间会有一个红色的十字)。

DragButton:哪个键拖动地图。

Position:地图中心点位置。

地图显示如下,支持左键拖动,放大缩小,可以显示左键的点击经纬度。

如何在WPF中使用GMap.Net

1、新建一个Visual C# 的WPF程序。添加对GMap.Net.Core.DLL和GMap.NET.WindowsPresentation.DLL的引用。

2、由于WPF的UserControl不能修改继承的基类,所以添加一个新的类,为MapControl.cs,代码如下:

复制代码

using System; using System.Collections.Generic; using System.Linq; using System.Text; using GMap.NET.WindowsPresentation; namespace GMapWPFDemo
{ class MapControl : GMapControl
{
}
}

复制代码

只需要继承GMapControl就行了,基本功能都可以由GMapControl提供。

3、在我们的MainWindow.xaml中,添加项目的namespace:xmlns:src=”clr-namespace:GMapWPFDemo”,在XML代码中添加对MapControl.cs的使用:

复制代码

<Window x:Class=“GMapWPFDemo.MainWindow” xmlns=“http://schemas.microsoft.com/winfx/2006/xaml/presentation“ xmlns:x=“http://schemas.microsoft.com/winfx/2006/xaml“ xmlns:src=“clr-namespace:GMapWPFDemo” Title=“MainWindow” Height=“410” Width=“618”>
<Grid>
<GroupBox Name=“mapgroup” VerticalContentAlignment=“Stretch” HorizontalContentAlignment=“Stretch”>
<src:MapControl x:Name=“mapControl” Zoom=“13” MaxZoom=“24” MinZoom=“1” />
</GroupBox>
</Grid>
</Window>

复制代码

4、在MainWindow中添加相关的代码如下:

复制代码

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using GMap.NET; using GMap.NET.MapProviders; using GMap.NET.WindowsPresentation; namespace GMapWPFDemo
{ ///


/// MainWindow.xaml 的交互逻辑 ///

public partial class MainWindow : Window
{ public MainWindow()
{
InitializeComponent(); try {
System.Net.IPHostEntry e = System.Net.Dns.GetHostEntry(“ditu.google.cn”);
} catch {
mapControl.Manager.Mode = AccessMode.CacheOnly;
MessageBox.Show(“No internet connection avaible, going to CacheOnly mode.”, “GMap.NET Demo”, MessageBoxButton.OK, MessageBoxImage.Warning);
}

        mapControl.MapProvider \= GMapProviders.GoogleChinaMap; //google china 地图
        mapControl.MinZoom = 2;  //最小缩放
        mapControl.MaxZoom = 17; //最大缩放
        mapControl.Zoom = 5;     //当前缩放
        mapControl.ShowCenter = false; //不显示中心十字点
        mapControl.DragButton = MouseButton.Left; //左键拖拽地图
        mapControl.Position = new PointLatLng(32.064, 118.704); //地图中心位置:南京

mapControl.MouseLeftButtonDown += new MouseButtonEventHandler(mapControl_MouseLeftButtonDown);
} void mapControl_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Point clickPoint = e.GetPosition(mapControl);
PointLatLng point = mapControl.FromLocalToLatLng((int)clickPoint.X, (int)clickPoint.Y);
GMapMarker marker = new GMapMarker(point);
mapControl.Markers.Add(marker);
} }
}

复制代码

效果图如下:

和Winform代码差不多,一些响应事件不同,WPF的GMap中没有GMapOverlay这个“图层”的概念,所以没法加多个GMapOverlay,在GMapOverlay上再加GMapMarker(可以理解为图标标注),GMapMarker只能直接加在mapControl上面。

WPF的GMapMarker可以直接实例化(WinForm中的不行),但是貌似没有默认提供的效果,而要做出一些效果,需要自己设计实现,官方Demo中已经有了一些实现,WinForm中的GMapMarker可以用GMarkerGoogle去实例化(提供的有可选的效果,也可以自己传入bitmap作为自定义的图标)。

做过一段时间ArcGIS开发,由于ArcGIS的授权费用,为了节省成本,开始搞GMap.Net。。。刚开始研究,待续。。。

代码开源在GitHub上:

项目地址:https://github.com/luxiaoxun/MapDownloader

上一篇介绍了如何在GMap地图上添加多边形,这篇介绍下如何使用在线的地图服务进行“地址解析”和“路径查找”。

先看地址解析,GMap中的地址解析主要用到GeocodingProvider中的如下方法:

复制代码

//根据关键字得到一组坐标
GeoCoderStatusCode GetPoints(string keywords, out List pointList); //根据关键字得到一个坐标
PointLatLng? GetPoint(string keywords, out GeoCoderStatusCode status); //根据坐标得到一组地址
GeoCoderStatusCode GetPlacemarks(PointLatLng location, out List placemarkList); //根据坐标得到一个地址
Placemark GetPlacemark(PointLatLng location, out GeoCoderStatusCode status);

复制代码

先定义一些变量:

复制代码

    private GMapOverlay locations = new GMapOverlay("locations"); //放置搜索结果的图层
    private GeocodingProvider gp; //地址编码服务
    List<PointLatLng> searchResult = new List<PointLatLng>(); //搜索结果
    PointLatLng start = PointLatLng.Empty; //路径开始点
    PointLatLng end = PointLatLng.Empty;   //路径结束点

mapControl.Overlays.Add(locations); //不要忘了添加使用的图层

复制代码

地址解析的准确度和当前使用的地图服务有很大关系,一般使用什么MapProvider就使用该MapProvider提供的服务。

如我使用的是GoogleChinaMap,就使用google的地址解析:

复制代码

        mapControl.MapProvider = GMapProviders.GoogleChinaMap; //google china 地图

gp = mapControl.MapProvider as GeocodingProvider; if (gp == null) //地址转换服务,没有就使用OpenStreetMap
{
gp = GMapProviders.OpenStreetMap as GeocodingProvider;
}
GMapProvider.Language = LanguageType.ChineseSimplified; //使用的语言,默认是英文

复制代码

地址解析一般有2种情况,第一种就是根据坐标点得到该点对应的地址信息,在map的double click中,我们得到双击点的地址信息:

复制代码

    void mapControl\_MouseDoubleClick(object sender, MouseEventArgs e)
    { if (e.Button == System.Windows.Forms.MouseButtons.Left)
        {
            PointLatLng point \= mapControl.FromLocalToLatLng(e.X, e.Y);
            GeoCoderStatusCode statusCode \= GeoCoderStatusCode.Unknow;
            Placemark? place = gp.GetPlacemark(point, out statusCode); if (statusCode == GeoCoderStatusCode.G\_GEO\_SUCCESS)
            {
                GMapMarker marker \= new GMarkerGoogle(point, GMarkerGoogleType.green);
                marker.ToolTipText \= place.Value.Address;
                marker.ToolTipMode \= MarkerTooltipMode.Always;

                locations.Markers.Add(marker);
            }
        }
    }

复制代码

效果图如下:

第二种情况就是根据地址得到对应的坐标点,输入字符串地址,在地图上得到对应的位置点,可能有多个,就做个了comboBox保存所有查询得到的结果,每次选择一个地址的时候,将地图的中心位置移到对应的Marker的位置,代码如下:

复制代码

    private void buttonSearch\_Click(object sender, EventArgs e)
    {
        searchResult.Clear();
        locations.Markers.Clear(); this.comboBoxSearchResult.Items.Clear(); string searchStr = this.textBoxSearch.Text;
        GeoCoderStatusCode statusCode \= gp.GetPoints(searchStr, out searchResult); if (statusCode == GeoCoderStatusCode.G\_GEO\_SUCCESS)
        { foreach (PointLatLng point in searchResult)
            {
                GMarkerGoogle marker \= new GMarkerGoogle(point, GMarkerGoogleType.arrow);

                GeoCoderStatusCode placeMarkResult \= new GeoCoderStatusCode();
                Placemark? place = gp.GetPlacemark(point, out placeMarkResult);
                locations.Markers.Add(marker); this.comboBoxSearchResult.Items.Add(place.Value.Address);
            }
            mapControl.ZoomAndCenterMarkers(locations.Id);
        }
    } private void comboBoxSearchResult\_SelectedIndexChanged(object sender, EventArgs e)
    { if (this.comboBoxSearchResult.SelectedIndex < 0)
        { return;
        }
        locations.Clear();
        GMarkerGoogle marker \= new GMarkerGoogle(searchResult\[this.comboBoxSearchResult.SelectedIndex\], GMarkerGoogleType.red);
        locations.Markers.Add(marker);
        mapControl.Position \= this.searchResult\[this.comboBoxSearchResult.SelectedIndex\];
    }

复制代码

效果图:

只是简单的测了下,还是比较准确的。。

再来看看路径查找,GMap中的路径查找主要用到RoutingProvider的如下方法:

复制代码

//avoidHighways:是否避免走高速公路 //walkingMode:是否步行 //zoom:查找路径时的zoom,貌似越大路径越准确 //根据起止点start、end和当前的zoom查找路径
MapRoute GetRoute(PointLatLng start, PointLatLng end, bool avoidHighways, bool walkingMode, int Zoom); //根据出发点地址start、目的点地址end和当前的zoom查找路径
MapRoute GetRoute(string start, string end, bool avoidHighways, bool walkingMode, int Zoom);

复制代码

先在地图上添加2个Marker,确定“起点”和“终点”:

复制代码

    void mapControl\_OnMarkerClick(GMapMarker item, MouseEventArgs e)
    { if (item is GMapMarkerImage)
        {
            currentMarker \= item as GMapMarkerImage;
            currentMarker.Pen \= new Pen(Brushes.Red, 2);
        }
    } private void buttonSetStart\_Click(object sender, EventArgs e)
    { if (currentMarker != null)
        {
            start \= currentMarker.Position;
        }
    } private void buttonSetEnd\_Click(object sender, EventArgs e)
    { if (currentMarker != null)
        {
            end \= currentMarker.Position;
        }
    }

复制代码

再通过RoutingProvider得到起点和终点之间的路径:

复制代码

    private void buttonFindRoute\_Click(object sender, EventArgs e)
    {
        RoutingProvider rp \= mapControl.MapProvider as RoutingProvider; if (rp == null)
        {
            rp \= GMapProviders.OpenStreetMap; // use OpenStreetMap if provider does not implement routing

}

        MapRoute route \= rp.GetRoute(start, end, false, false, (int)mapControl.Zoom); if (route != null)
        { // add route
            GMapRoute r = new GMapRoute(route.Points, route.Name);
            r.IsHitTestVisible \= true;
            locations.Routes.Add(r); // add route start/end marks
            GMapMarker m1 = new GMarkerGoogle(start, GMarkerGoogleType.green\_big\_go);
            m1.ToolTipText \= "Start: " + route.Name;
            m1.ToolTipMode \= MarkerTooltipMode.Always;

            GMapMarker m2 \= new GMarkerGoogle(end, GMarkerGoogleType.red\_big\_stop);
            m2.ToolTipText \= "End: " + end.ToString();
            m2.ToolTipMode \= MarkerTooltipMode.Always;

            objects.Markers.Add(m1);
            objects.Markers.Add(m2);

            mapControl.ZoomAndCenterRoute(r);
        }
    }

复制代码

效果图:

这里使用的google的地图服务,却没有使用google的API,GMap的作者通过解析浏览器访问google地图服务器和地图服务的URL(其他地图也一样),得到了每次访问的URL的格式和传入参数的格式,并找到了规律,确定如何传入参数和解析返回结果而封装了这些类和API,做了这个开源的项目,有兴趣的可看其源代码,还是很有收获的。。。

项目地址:https://github.com/luxiaoxun/MapDownloader

更新:GMap默认提供的google地图的“路径查找”和“地址解析”功能已经无效,可以通过集成地图服务的API去做,需要申请开发者key。

一些地图开发者网站:

高德:https://lbs.amap.com/api/webservice/summary/

腾讯:http://lbs.qq.com/index.html

百度:http://developer.baidu.com/map/index.html

谷歌:http://maps.googleapis.com/

参考:

https://greatmaps.codeplex.com/

Gentoo(贱兔)Linux安装笔记

2018-02-23 13:42  親親宝贝  阅读(1686)  评论()  编辑  收藏

网上对于Gentoo Linux 的教程少之又少,所以这里我将自己的安装记录贴出来

希望对正在研究Gentoo 的小伙伴们有帮助!

1.确认连接到互联网,使用net-setup工具配置网络

2.分区

推荐分区方案:

分区

描述

/dev/sda1

boot引导分区

/dev/sda2

/根分区

/dev/sda3

swap交换分区

复制代码

Command (m for help):p
Disk /dev/sda: 30.0 GB, 30005821440 bytes 240 heads, 63 sectors/track, 3876 cylinders
Units = cylinders of 15120 * 512 = 7741440 bytes

Device Boot Start End Blocks Id System /dev/sda1 * 3 14 506520+ 83 Linux /dev/sda2 15 81 28690200 83 Linux /dev/sda3 82 3876 506520 82 Linux swap

复制代码

敲击 a 键来切换一个分区是否为可引导的标记,然后敲击 1。之后再一次敲 p键,您可以发现一个“*”被放置于相应分区的“boot”列。

3.格式化分区并挂载磁盘

复制代码

root# mkfs.ext4 /dev/sda1
root# mkfs.ext4 /dev/sda2
root# mkswap /dev/sda3
root# swapon /dev/sda3
root# mount /dev/sda2 /mnt/gentoo
root# mkdir /mnt/gentoo/boot
root# mount /dev/sda1 /mnt/gentoo/boot

复制代码

4.设置时间

root# date 050814302016 //05表示月份08表示号数1430表示14点30分2016表示年份

5.下载stage3,并解压tar包

stage3压缩包是一个包含有最小化Gentoo环境的文件

root# cd /mnt/gentoo
root# links https://www.gentoo.org/downloads/mirrors/
root# tar xvjpf stage3-*.tar.bz2 –xattrs

6.配置编译选项

复制代码

root# nano -w /mnt/gentoo/etc/portage/make.conf
CFLAGS=“-O2 -pipe” //-O2表示gcc优化级别,(已知O3会在全系统时有问题,所以推荐使用O2即可) -pipe表示使用管道而不是编译的各个阶段之间的通信的临时文件,这里我推荐大家就使用这两个标记即可,如果有需要可以查看GUN GCC的一些选项来自行设定

Use the same settings for both variables

CXXFLAGS=“${CFLAGS}” MAKEOPTS=“-j8” //根据你的CPU核心数来设置,表示编译安装包时并行执行的线程数

复制代码

7.配置镜像站点

root# mirrorselect -i -o >> /mnt/gentoo/etc/portage/make.conf
#选择一个你所在地理位置的镜像站点即可

8.创建主仓库

root# mkdir /mnt/gentoo/etc/portage/repos.conf
root# cp /mnt/gentoo/usr/share/portage/config/repos.conf /mnt/gentoo/etc/portage/repos.conf/gentoo.conf

9.复制DNS信息

root# cp -L /etc/resolv.conf /mnt/gentoo/etc/

10.挂载必要的文件系统

复制代码

root# mount -t proc proc /mnt/gentoo/proc
root# mount –rbind /sys /mnt/gentoo/sys
root# mount –make-rslave /mnt/gentoo/sys //如果你不打算安装systemd则可不必使用这句话
root# mount –rbind /dev /mnt/gentoo/dev
root# mount –make-rslave /mnt/gentoo/dev //如果你不打算安装systemd则可不必使用这句话
root# rm /dev/shm && mkdir /dev/shm
root# mount -t tmpfs -o nosuid,nodev,noexec shm /dev/shm
root# chmod 1777 /dev/shm

复制代码

--make-rslave操作是稍后安装systemd支持时所需要的。

11.进入新环境

root# chroot /mnt/gentoo /bin/bash
root# source /etc/profile
root# export PS1=“(chroot) $PS1”

12.安装Portage快照

root# emerge-webrsync //emerge-webrsync命令将安装一个最近的Portage快照(通常是24小时以内)
root# emerge –sync //使用rsync协议来更新Portage树(之前通过emerge-webrsync获得的)到最新状态。

13.选择正确的配置文件

root# eselect profile list
root# eselect profile set [number]
root# eselect profile list

14.更新你的系统

root# emerge –ask –update –deep –newuse @world

15.配置USE变量

USE是Gentoo为用户提供的最具威力的变量之一。很多程序通过它可以选择编译或者不编译某些可选的支持。例如,一些程 序可以在编译时加入对gtk或是对qt的支持。其它的程序可以在编译时加入或不加入对于SLL的支持。有些程序甚至可以在编译时加入对 framebuffer的支持(svgalib)以取代X11(X服务器)。

大多数的发行版会使用尽可能多的支持特性编译它们的软件包,这既增加了软件的大小也减慢了启动时间,而这些还没有算上可能会涉及到的大量依赖性问题。Gentoo可以让你自己定义软件编译的选项,而这正是USE要做的事。

在USE变量里你可以定义关键字,它被用来对应相应的编译选项。例如,ssl将会把ssl支持编译到程序中以支持它。-X会移除其对于X服务器的支持(注意前面的减号)。gnome gtk -kde -qt4将会以支持GNOME(和GTK)但不支持KDE(和Qt)的方式编译软件,使系统为GNOME做完全调整(如果架构支持)。

默认的USE设置全放在了系统所使用的Gentoo配置文件的make.defaults文件中。Gentoo对它的配置文件们使用了一个(复杂的)继承系统,在这个阶段我们不去深入。最简单的检查当前活动的USE标记的办法是运行emerge –info并选择以USE开头的那一行:

root# emerge –info | grep ^USE
USE=“X acl alsa amd64 berkdb bindist bzip2 cli cracklib crypt cxx dri …”

可以在系统的/usr/portage/profiles/use.desc中找到可用的USE标记的完整描述。

root# less /usr/portage/profiles/use.desc

这里我给推荐使用的几个初始USE变量,不包含任何桌面环境(如果要使用桌面环境请查阅相关文档)

复制代码

USE=”-gnome -kde -qt4 -minimal gtk dvd alsa cdr dbus X udev session lock jpeg startup-notification thunar policykit udisks” INPUT_DEVICES=“evdev synaptics” VIDEO_CARDS=“nouveau” //N卡用户
VIDEO_CARDS=”radeon” //Intel用户
CPU_FLAGS_X86=”” //CPU指令集,为了帮助用户能正确使用标志,提供了一个Python脚本生成使用/proc内/cpuinfo正确的值。它可以在找到app-portage/cpuinfo2cpuflags
root# emerge –ask app-portage/cpuinfo2cpuflags
root# cpuinfo2cpuflags-x86 >> /etc/portage/make.conf

复制代码

16.配置时区

为系统选择时区。在/usr/share/zoneinfo/中查找可用的时区,然后写进/etc/timezone文件。

root# ls /usr/share/zoneinfo
root# echo “Asia/Shanghai” > /etc/timezone
root# emerge --config sys-libs/timezone-data

17.配置地区

复制代码

root# nano -w /etc/locale.gen
en_US ISO-8859-1 en_US.UTF-8 UTF-8 zh_CN GBK
zh_CN GB18030
zh_CN.GB2312 GB2312
zh_CN.UTF-8 UTF-8 root# locale-gen
root# eselect locale list
root# eselect locale set [number]
root# env-update && source /etc/profile && export PS1=”(chroot) $PS1”

复制代码

可选操作,配置Systemd代替OpenRC,这一步做了下面安装源代码就不用做了

  • 编辑/etc/fstab文件来使包含有第二个值为/boot/的那条的第一个值指向到正确的设备。

root# nano -w /etc/fstab /dev/sda1 /boot ext4 defaults 0 2

  • 安装Systemd

复制代码

root# ln -sf /proc/self/mounts /etc/mtab
root# emerge --ask –unmerge sys-kernel/genkernel //如果以前安装过kernel那么卸载之
root# emerge –ask sys-kernel/dracut
root# nano -w /etc/dracut/dracut.conf
# Dracut modules to add to the default
add_dracutmodules+=”usrmount” root# emerge --ask sys-kernel/gentoo-sources
root# emerge --ask sys-kernel/genkernel-next /etc/genkernel.conf
UDEV=“yes” root# genkernel --install all
root# genkernel --udev –lvm –mdadm initramfs
当使用LVM时,lvmetad守护进程需要被同时启动。否则,systemd将无法挂载LVM卷。 lvmetad可以在/ etc/ LVM启用
root# nano -w /etc/lvm/lvm.conflvm.conf
# Set use_lvmetad to ‘1’ for systemd
use_lvmetad = 1 root# nano -w /etc/portage/profile/packages
# Remove OpenRC from the system profile when using systemd -*sys-apps/openrc
root# root #emerge -avDN @world
root# emerge --deselect sys-fs/udev

复制代码

配置使用systemd引导启动
当grub2-mkconfig 被使用时, 增加 init 这一行 GRUB_CMDLINE_LINUX:
/etc/default/grub GRUB2 systemd 配置示例
当使用 genkernel-next’s 引导时, 使用real_init 来替换init.

# Append parameters to the linux kernel command line
GRUB_CMDLINE_LINUX=“init=/usr/lib/systemd/systemd”

手动配置GRUB2文件时(仅限有经验的用户), 添加init=参数到 linux 或 linux16命令行。
/boot/grub/grub.cfg GRUB2 配置示例片段

linux /vmlinuz-3.10.9 root=UUID=508868e4-54c6-4e6b-84b0-b3b28b1656b6 init=/usr/lib/systemd/systemd

18.1.安装源代码(手动编译安装)

root# emerge –ask sys-kernel/gentoo-sources

当手动配置内核时,了解(硬件)系统是至关重要的。大多数信息可以通过安装包含lspci命令的sys-apps/pciutils来收集:

root# emerge –ask sys-apps/pciutils

另一个系统信息来源是运行lsmod来查看安装CD使用什么内核模块,它可能对启用什么提供了一个好的暗示。
现在进入内核源码目录并执行make menuconfig。这将启动一个菜单驱动的配置屏幕。

root# cd /usr/src/linux
root# make menuconfig
root# make && make modules_install
root# make install

可选:生成一个initramfs

在某些情况中需要建立一个initramfs——一个基于内存的初始化文件系统。最觉的原因是当重要的文件系统位置(如/usr/或/var/)在分离的分区。通过一个initramfs,这些分区可以使用initramfs里面的工具来完成挂载。

如果没有initramfs的,存在着巨大的风险,系统将无法正常开机,因为这是负责安装的文件系统工具需要驻留在这些文件系统的信息。 initramfs中的一个将在必要的文件拉进它的内核启动之后使用的档案,但控制被移交前转移到初始化工具。在initramfs的脚本,然后将确保分 区正确地安装在系统继续启动之前。

要安装一个initramfs,首先安装sys-kernel/genkernel,然后用它生成一个initramfs:

root# emerge –ask sys-kernel/genkernel
root# emerge genkernel
root# genkernel --lvm –mdadm –install initramfs

18.2.安装源代码(自动方式-推荐新手用)

如果手动配置看起来太恐怖,建议使用genkernel。它将自动配置并编译内核。

genkernel配置内核的工作原理几乎和安装CD配置的内核完全一致。也就是说当使用genkernel建立内核,系统通常将在引导时检测全部硬件,就像安装CD所做的。因为genkernel不需要任何手动内核配置,它对于那些不能轻松的编译他们自动内核的用户来说是一个理想的解决方案。

现在,我们来看看如何使用genkernel。

root# emerge –ask sys-kernel/gentoo-sources
root# emerge --ask sys-kernel/genkernel

接下来,编辑/etc/fstab文件来使包含有第二个值为/boot/的那条的第一个值指向到正确的设备。

root# nano -w /etc/fstab /dev/sda1 /boot ext4 defaults 0 2

现在,运行genkernel all来编译内核源码。值得注意的是,使用genkernel编译一个内核将支持几乎全部的硬件,这将使编译过程需要一阵子来完成!

19.可选操作,配置模块,安装固件

在/etc/conf.d/modules中列出需要自动加载的模块。如果有必要,附加选项也可以添加到模块中。

要查看所有可用模块,运行下面的find命令。不要忘记替换“”为刚刚编译的内核版本:

root# find /lib/modules// -type f -iname ‘*.o’ -or -iname ‘*.ko’ | less

比如,要自动加载3c59x.ko模块(3Com网卡家族的特定驱动),编辑/etc/conf.d/modules文件并在里面输入模块名字。

root# nano -w /etc/conf.d/modules
modules=“3c59x”

一些驱动需要先在系统上安装附加的固件才能工作。这经常用于网络接口,特别是无线网络接口。非常多的固件都打包在sys-kernel/linux-firmware里:

root# emerge –ask sys-kernel/linux-firmware

20.配置fatab

root# nano -w /etc/fstab /dev/sda1 /boot ext4 defaults 0 2
/dev/sda2 / ext4 defaults 0 1
/dev/sda3 none swap sw 0 0
/dev/cdrom /mnt/cdrom auto noauto,user 0 0

21.配置主机名

root# nano -w /etc/conf.d/hostname

22.配置hosts文件

23.配置网络

复制代码

root# emerge –ask –noreplace net-misc/netifrc
root# nano -w /etc/conf.d/net
#如果你要配置静态ip
config_eth0=“192.168.0.2 netmask 255.255.255.0 brd 192.168.0.255” routes_eth0=“default via 192.168.0.1” #否则如果需要动态ip
config_eth0=“dhcp”

复制代码

24.配置网卡开机自动启动

复制代码

root# cd /etc/init.d
root# ln -s net.lo net.eth0
root# rc-update add net.eth0 default
#对于笔记本你的网卡可能是enp0s3之类的,那么就需要改成如下命令
root# ln -s net.lo net.enp0s3
root# rc-update add net.enp0s3 default

复制代码

25.安装dhcp客户端

root# emerge –ask net-misc/dhcpcd

26.修改密码

27.编辑UTC时间,修改为使用本地时间

root# nano -w /etc/conf.d/hwclock

28.安装日志管理软件

root# emerge –ask app-admin/sysklogd
root# rc-update add sysklogd default

29.安装crontab任务计划软件

root# emerge –ask sys-process/cronie
root# rc-update add cronie default
root# crontab /etc/crontab

30.配置开机启动sshd

root# rc-update add sshd default

31.安装Grub2

root# emerge –ask sys-boot/grub:2 root# grub2-install /dev/sda
root# grub2-mkconfig -o /boot/grub/grub.cfg

32.重启系统

root# exit
root# cd ~ root# umount -l /mnt/gentoo/dev{/shm,/pts,}
root# umount /mnt/gentoo{/boot,/sys,/proc,}
root# reboot

33.创建日常管理用户

root# useradd -m -G users,wheel,audio -s /bin/bash [username]
root# passwd [username]

34.清理tar包

root# rm /stage3-*.tar.bz2*

到这里安装就结束了!!

可选操作:安装Xorg-x11图形化服务器

首先确保你的/etc/portage/make.conf USE中包含X标记

USE=”-gnome -kde -qt4 -minimal gtk dvd alsa cdr dbus X udev session lock jpeg startup-notification thunar policykit udisks”

然后安装所需软件

root# emerge –ask x11-base/xorg-x11
root# env-update && source /etc/profile

可选操作:安装Xfce4桌面环境

首先,确定你已经安装Xorg 如果没有的话那么本指南下面的步骤可能无法正常工作。
接着,请反复检查 /etc/portage/make.conf 文件里的 USE 标记;多数用户需要设置下面的 USE flags:

USE=”-gnome -kde -minimal -qt4 dbus jpeg lock session startup-notification thunar udev X”

接下来进行安装

root# emerge –ask xfce-base/xfce4-meta

如果需要,把系统上的一般用户(们)加到cdrom,cdrw 和 usb 组里,这样他/她们便能挂载和使用照相机、光驱和U盘等之类的设备。

root# for x in cdrom cdrw usb ; do gpasswd -a username $x ; done

更新系统环境变量

root# env-update && source /etc/profile

安装额外软件

root# emerge –ask x11-terms/xfce4-terminal

配置启动xfce

root# echo “exec startxfce4” > ~/.xinitrc
root# startx

我心里一直有个梦,想去嵩山少林学武功… QQ:1976883731

2014.05.06 17:23:45字数 196阅读 3,886

Gentoo Linux改为采用滚动更新。Gentoo Linux的更新频密度可达到每周皆提供更新版。

升级系统的标准步骤:

emerge --sync 
emerge portage 
emerge -avuDN world 

参数说明:
--ask (-a) 控制Portage显示它要更新的软件包列表,并让您决定是否继续更新
--verbose (-v) 在屏幕上输出完整的文件列表
--update (-u) 更新包的最佳版本
--deep (-D) 更新系统中的每个软件包
--newuse (-N) USE标记变更后,要使Portage检查USE标记的变动是否导致需要安装新的软件包或者将现有的包重新编译

emerge -av --depclean 
revdep-rebuild 

revdep-rebuild gentoolkit包里面的一个软件,用来检查系统的依赖关系是否都满足,自动安装没有满足关系.

env-update && source /etc/profile //如有必要,更新环境变量

“小礼物走一走,来简书关注我”

还没有人赞赏,支持一下

孤逐王- 传承Unix哲学思想,关注Linux开源技术,情有独钟Gentoo! - 分享投资理财知…

总资产1 (约0.10元)共写了9.1W字获得798个赞共318个粉丝

被以下专题收入,发现更多相似内容

推荐阅读更多精彩内容

  • 各大Linux发行版软件包管理参考(http://www.freeoa.net/osuport/botinstal

  • 当前顶级发行版概览   对于Linux新手来说,在各发行版之间困惑得进行选择和不断增加的数量实在令人头晕。这就是写…

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智…

  • Ubuntu的发音 Ubuntu,源于非洲祖鲁人和科萨人的语言,发作 oo-boon-too 的音。了解发音是有意…

  • 文 | 墨书 图 | 来源于网络 一个爱我的人,如果爱得讲话结结巴巴,语无伦次,我就知道他爱我。凡是他说不上来只能…

2014.05.15 18:11:14字数 581阅读 977

前期准备

远程登录:

  • 开启ssh服务:

  • 设置密码:

以便使用putty、ssh client远程登录上传stage等(有时在线下载很慢,而局域网上传很快)

准备磁盘:

  • 分区:

/dev/sda1 : /boot 100M(32-100M) 设启动笔记-a
/dev/sda2 : / 20G
/dev/sda3 : /home 20G
/dev/sda5 : /swap 1G (内存< 512 MB,分区分配2倍内存大小的空间;> 1024 MB,可以分配较少的空间甚至不需要swap 分区。)-t 82

  • 创建文件系统:
1
2
3
4
mkfs.ext4 /dev/sda1
mkfs.ext4 /dev/sda2
mkfs.ext4 /dev/sda3
mkswap /dev/sda5
  • 挂载分区:
1
2
3
4
5
6
mount /dev/sda2 /mnt/gentoo
mkdir /mnt/gentoo/boot
mount /dev/sda1 /mnt/gentoo/boot
mkdir /mnt/gentoo/home
mount /dev/sda3 /mnt/gentoo/home
swapon /dev/sda5

安装系统

安装stage及portage:

  • 正确设置日期/时间:

如果显示的日期/时间不正确,可以使用date MMDDhhmmYYYY命令

  • 下载Stage3 Tarbll:
1
2
cd /mnt/gentoo
links http://www.gentoo.org/main/en/mirrors.xml

选择国内速度较快的镜像,进入releases/x86/autobuilds/目录里。你将会看到所有适合你的计算机体系结构的stage文件(它们也可能放在各个独立的子体系名称的子目录里)。选择一个,然后按D来下载。下载完以后,再按Q退出浏览器。

或使用SSH Secure Shell登录上传stage3文件

  • 解开Stage3 Tarball:
1
tar xvjpf stage3-*.tar.bz2
  • 下载Portage:

打开links(或者lynx)然后到我们的Gentoo镜像列表。选择一个离你最近的镜像,打开snapshots/目录。然后选择最新的Portage快照(portage-latest.tar.bz2)并按D来下载它。

1
links http://www.gentoo.org/main/en/mirrors.xml

或使用SSH Secure Shell登录上传portage文件

  • 解压Portage:
1
tar -xvjf /mnt/gentoo/portage-latest.tar.bz2 -C /mnt/gentoo/usr (install a Portage snapshot)

编译前准备:

  • 配置编译选项:
1
nano -w /mnt/gentoo/etc/portage/make.conf

CFLAGS=”-march=native -O2 -pipe”

CXXFLAGS=”${CFLAGS}” # 两个变量使用相同的设置
MAKEOPTS=”-j3” #MAKEOPTS定义在安装软件的时候同时可以产生并行编译的数目,CPU数目加一是个不错的选择

查看cpu信息:

  • 选择镜像站点:
1
2
mirrorselect -i -o >> /mnt/gentoo/etc/portage/make.conf
mirrorselect -i -r -o >> /mnt/gentoo/etc/portage/make.conf

**Warning:**app-portage/mirrorselect has not been updated to handle modifying the target chrootsrepos.conf/gentoo.conf file yet. Also, the SYNC variable in make.conf is deprecated and no longer used by portage. This section needs to be updated, please skip for the time being…

  • 拷贝DNS信息:
1
cp -L /etc/resolv.conf /mnt/gentoo/etc/

Chroot进入新系统环境:

Chroot:

  • 挂载 /proc, /dev, /sys文件系统:
1
2
3
mount -t proc none /mnt/gentoo/proc
mount --rbind /dev /mnt/gentoo/dev
mount --rbind /sys /mnt/gentoo/sys
  • 进入新的系统环境:
1
2
3
chroot /mnt/gentoo /bin/bash
source /etc/profile
export PS1="(chroot) $PS1"

新环境配置:

  • 更新portage树:
1
2
3
emerge --sync (Updating the Portage tree)

emerge-webrsync(fetch the latest portage snapshot)
  • 选择Profile:
1
2
eselect profile list
eselect profile set ×
  • 设置时区:
1
2
3
ls /usr/share/zoneinfo
echo "Europe/Brussels" > /etc/timezone
emerge --config sys-libs/timezone-data
  • 设置locale:
1
2
3
4
nano -w /etc/locale.gen
locale-gen
eselect locale list
eselect locale set x
  • 更新环境变量:
1
env-update && source /etc/profile

更多精彩内容下载简书APP

“小礼物走一走,来简书关注我”

还没有人赞赏,支持一下

孤逐王- 传承Unix哲学思想,关注Linux开源技术,情有独钟Gentoo!
- 分享投资理…

总资产1 (约0.10元)共写了9.3W字获得803个赞共326个粉丝

被以下专题收入,发现更多相似内容

推荐阅读更多精彩内容

  • Ubuntu的发音 Ubuntu,源于非洲祖鲁人和科萨人的语言,发作 oo-boon-too 的音。了解发音是有意…

  • linux资料总章2.1 1.0写的不好抱歉 但是2.0已经改了很多 但是错误还是无法避免 以后资料会慢慢更新 大…

  • 先读一下前面这段话,以决定是否要看下去: 这是写给纯小白的Arch Linux安装步骤。Arch的可配置自由度太高…

    沉沦的菩提阅读 28,228评论 73赞 194

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智…

  • Linux系统磁盘基本管理命令挂载文件系统――mount mount命令语法: mount [参数] [设备名称]…

Gentoo是一个非常著名的发行版,在Linux高手中非常流行。之前我也是被Gentoo的威名震慑了,所以一直没有尝试安装,最近感觉可以尝试一下了。所以今天来看看如何在虚拟机中安装Gentoo吧。本文参考了Gentoo 安装手册,如果想了解更多关于安装Gentoo的知识,可以直接查看官方文档。

当然有一说一,Gentoo的安装确实比Arch复杂多了。Arch其实安装起来并不算复杂,就是第一次操作命令行不熟悉,而Gentoo相比之下复杂多了,感觉难度是Arch安装5倍不止。特别是配置内核这一项的复杂程度,可能就顶了Arch整个安装的复杂度。

准备工作

下载镜像

首先到Gentoo官网下载所需的网络安装镜像,和Arch一样,Gentoo没有提供完整安装包,只有一个网络安装镜像。当然硬要说的话,Gentoo还是有完整镜像的,不过这个镜像已经三四年没有更新了,所以我们完全不用管它。

下载网络安装镜像

启动虚拟机

首先新建虚拟机的过程就不说了,我选择的CPU是2核,内存2G,硬盘空间32G,启动方式EFI。除了启动方式必须设置成EFI以外,其他设置全部按大家实际情况来就行了。设置好之后用网络镜像启动虚拟机,应该会看到如图所示的界面。

启动界面

非常有趣的一点就是Gentoo的Live环境了,它虽然是命令行终端,但是却非常神奇的支持鼠标操作,。鼠标拖动选择文本等操作全部支持,真的是让人惊叹。

进入Live界面

开启SSH

虽然虚拟机是可以支持剪贴板复制等操作的,但是在系统安装过程中是没办法支持的。所以接下来要输入的大量命令会让人感到十分困扰。所以为了方便最好开启SSH,这样我们好歹大部分命令可以直接复制粘贴。

默认root密码为空,为了能够SSH,还有手动设置一下root密码。

开启成功之后,输入ip a命令查看一下虚拟机的IP,然后我们就可以SSH到虚拟机内部了。

查看IP地址

这样一来就不需要手打命令了。当然并不是说所有命令都能无脑复制粘贴,有些地方还是需要根据自己需求进行修改的。使用SSH还有一个好处就是在下面配置区域的时候,如果你只指定了中文区域,在终端中汉字会显示成方块,但是在SSH中可以正常显示。如果你准备直接在终端中输入命令的话,记得同时启用英文区域。

成功登录

安装Stage3文件

磁盘分区

当然,因为是虚拟机安装,所以烧录镜像、配置网络等复杂步骤不需要进行,我们直接从磁盘分区开始。因为我是EFI方式启动,所以要分两个区,ESP以及根分区。

1
2
3
4
5
6
7
8
parted /dev/sda mklabel gpt
parted /dev/sda mkpart efi fat32 0% 512M
parted /dev/sda mkpart root ext4 512M 100%
parted /dev/sda set 1 esp on

# 格式化分区
mkfs.fat -F32 /dev/sda1
mkfs.ext4 /dev/sda2

最后将根目录分区挂载为/mnt/gentoo,就可以进行下一步的工作了。

1
mount /dev/sda2 /mnt/gentoo

安装Stage3

这里简单按我的理解介绍一下Gentoo的包,详细信息请参考关于Stage Tarball的官方文档,简单说来可以分为Stage 1/2/3/4四个类型:Stage 1可以简单理解成源代码的包;Stage 2同样包含了Stage 1的所有文件,但是同时还包含了Stage 1编译出来的文件,也就说是Stage 2是自包含的包(自己可以编译出自己);Stage 3则是包含了系统必备工具的集合,也就是我们这里要安装的系统基础组件(不包括内核和引导器);Stage 4除了Stage 3的文件之外,还包含了内核和系统引导器,所以Stage 4就是一个可运行的包。这里我们要学习如何安装Gentoo,所以选择下载Stage 3就可以了,Stage 4主要是用于虚拟机快速运行之类的。

我们到清华镜像寻找最新的Stage 3的包,可以看到这里有很多文件,x32的我们不需要,带nomultilib的也不需要,iso的也不需要。我们要找的就是tar.xz格式的归档文件,除了包以外,还有三个附加文件分别包含了归档包的内容信息(CONTENTS)、校验信息(DIGESTS)以及加密的校验信息(DIGESTS.asc)。我们要把文件和三个校验文件全部下载下来。

寻找Stage 3包

下载命令如下,当然在下载之前,必须将工作目录切换到/mnt/gentoo也就是我们的根目录下,这样做的目的很简单,因为一会要将系统文件解压到根目录下。这里的下载链接是我目前复制出来的,假如大家以后看到本文的话,需要自己打开浏览器重新复制一下文件下载链接。

1
2
3
4
5
6
7
# 必须先切换到/mnt/gentoo
cd /mnt/gentoo

wget https://mirrors.tuna.tsinghua.edu.cn/gentoo/releases/amd64/autobuilds/current-stage3-amd64/stage3-amd64-20200223T214502Z.tar.xz
wget https://mirrors.tuna.tsinghua.edu.cn/gentoo/releases/amd64/autobuilds/current-stage3-amd64/stage3-amd64-20200223T214502Z.tar.xz.CONTENTS
wget https://mirrors.tuna.tsinghua.edu.cn/gentoo/releases/amd64/autobuilds/current-stage3-amd64/stage3-amd64-20200223T214502Z.tar.xz.DIGESTS
wget https://mirrors.tuna.tsinghua.edu.cn/gentoo/releases/amd64/autobuilds/current-stage3-amd64/stage3-amd64-20200223T214502Z.tar.xz.DIGESTS.asc

如果条件不允许,还可以使用另外一种方式避免手动输入这么一大长串链接。这就是利用文本浏览器link来上网并下载文件,光标选择,回车点击超链接,d开始下载,q退出浏览器。我们要做的就是用文本浏览器访问清华镜像网站,然后下载文件。

1
links https://mirrors.tuna.tsinghua.edu.cn/gentoo/

文本浏览器界面如下,使用起来也不算麻烦,将文件本体以及三个校验文件全部下载下来。

使用文本浏览器下载

然后用下面的命令来校验文件。

1
sha512sum -c stage3-amd64-*.tar.xz.DIGESTS

文件正确的话,应该会显示两个OK两个FIALED。失败的是另一种校验算法,只要有两个OK,就说明我们下载的文件是正确无误的。

校验文件

最后用下面命令来解压文件,后面的两个选项的作用是保留归档文件中所有文件正确的权限和命名空间关系。这样Gentoo的Stage3文件就算安装完成了。

1
tar xpvf stage3-*.tar.xz --xattrs-include='*.*' --numeric-owner

配置基本系统

设置编译选项

Gentoo是一个源代码发行版,安装大多数软件的时候,其实都是安装的源代码,然后在本地编译的。为了能够更快更好的编译软件,还需要配置一下portage的编译选项。

1
2
3
4
5
6
7
8
9
10
11
12
# 编辑配置文件
nano /mnt/gentoo/etc/portage/make.conf

# 修改COMMAN_FLAGS
COMMON_FLAGS="-march=native -O2 -pipe"
CFLAGS="${COMMON_FLAGS}"
CXXFLAGS="${COMMON_FLAGS}"
FCFLAGS="${COMMON_FLAGS}"
FFLAGS="${COMMON_FLAGS}"

# 添加MAKEOPTS,数字改成虚拟机设置的总内核数+1
MAKEOPTS="-j5"

设置镜像源

用下面的命令设置镜像源,推荐选择清华大学镜像源,按空格选中,然后回车确认。设置成功后,应该可以在文件最后面看到清华大学的镜像源。

1
2
3
4
mirrorselect -i -o >> /mnt/gentoo/etc/portage/make.conf

# 或者你懒得选择,直接把清华镜像源加到配置文件后面也可以
echo 'GENTOO_MIRRORS="https://mirrors.tuna.tsinghua.edu.cn/gentoo"' >> /mnt/gentoo/etc/portage/make.conf

然后创建Portage配置文件目录,并将Gentoo安装镜像里的配置文件复制给我们的系统。

1
2
mkdir --parents /mnt/gentoo/etc/portage/repos.conf
cp /mnt/gentoo/usr/share/portage/config/repos.conf /mnt/gentoo/etc/portage/repos.conf/gentoo.conf

另外还要复制DNS信息,这里--dereference参数是必须的,不然可能复制到的只是一个链接而已。

1
cp --dereference /etc/resolv.conf /mnt/gentoo/etc/

挂载必要的文件系统

为了让新系统能够正常运行,需要挂载以下文件系统。

1
2
3
4
5
mount --types proc /proc /mnt/gentoo/proc
mount --rbind /sys /mnt/gentoo/sys
mount --make-rslave /mnt/gentoo/sys
mount --rbind /dev /mnt/gentoo/dev
mount --make-rslave /mnt/gentoo/dev

切换进入新环境

用下面的命令切换到新的环境中,最后一条命令不是必须的,但是它会在命令提示符前面添加一个(chroot)提示我们处在新环境中。

1
2
3
chroot /mnt/gentoo /bin/bash
source /etc/profile
export PS1="(chroot) ${PS1}"

挂载boot分区

这里很多发行版都使用了不同的挂载点,最常用的挂载点是/boot/efi,ArchWiki里推荐使用/efi,而这里Gentoo文档中介绍的是使用/boot,我们按照文档走就可以了。

配置Portage

Portage是Gentoo的软件包管理器,首先我们从网络上下载数据库快照。

然后选择列出可用的profile,从中选择一个。这里又用到了另外一个工具eselect,它是Gentoo的通用管理界面,让我们方便的完成系统的各种配置和操作。因为Gentoo安装比较复杂,而且又是编译安装,所以这里选择其他profile的话,可能编译时间会非常长。因此这里大家看看就好,保持默认即可。

1
2
3
4
# 先列出可用的profile,带星号是目前选择的
eselect profile list
# 假如你想选择的话
eselect profile set 2

下面是我的profile输出。

查看profile

更新@world set

@world集合是Gentoo所有软件和配置的集合,要进行下一步的安装和配置,必须先更新@world集合。

1
emerge --ask --verbose --update --deep --newuse @world

配置USE环境变量

作为一个源代码编译的Linux发行版,Gentoo的很多软件包并不是直接安装二进制版本,而是下载源代码由用户选择编译。USE环境变量就是Gentoo提供给用户的一个工具,利用它可以指定一些环境变量,在编译的时候启用或者禁用某些功能。假如我现在要安装的是Gnome桌面,就可以禁用Qt的编译选项,这样编译出来的软件就不包含Qt的功能,软件包更加小巧,运行起来也会更快一点。

当然新手用户就没有必要更改这些高级选项了,默认的就完全够用了。如果你想要看看USE变量里面有什么东西的话,可以用下面的命令。

1
emerge --info | grep ^USE

如果想查看USE变量里面所有可用的选项,可以用下面的命令。

1
less /var/db/repos/gentoo/profiles/use.desc

配置时间和区域

首先要配置时区。

1
2
echo Asia/Shanghai > /etc/timezone
emerge --config sys-libs/timezone-data

然后配置区域。

1
2
3
4
5
6
7
8
9
echo 'zh_CN.UTF-8 UTF-8' >> /etc/locale.gen
# 如果你不用SSH,需要添加英文区域
echo 'en_US.UTF-8 UTF-8' >> /etc/locale.gen
locale-gen

# 列出可用的区域
eselect locale list
# 选择中文区域的编号
eselect locale set 2

最后重新加载一下环境变量。注意一下上面的设置,如果你是SSH到虚拟机系统的话,可以放心配置中文区域,但是如果你是直接在虚拟机终端里操作的话,需要在eselect locale set那里选择英文区域,不然汉字是无法正确显示的。

1
env-update && source /etc/profile && export PS1="(chroot) ${PS1}"

安装和配置内核

官方文档这里详细介绍了如何配置和优化内核,开启必须的功能,关闭不必要的功能。虽然对于初学者来说貌似这是很高深的知识,但是如果你照着做的话,会发现配置内核也不是这么困难的工作。当然如果你还是有点心虚的话,Gentoo也可以自动配置内核。

首先安装内核源代码和内核编译工具。因为Gentoo的安装是在本地编译的,所以用时比较长。假如你发现安装瞬间完成,是因为没有更新配置文件的原因。需要输入etc-update并选择-3自动更新配置文件,然后重新输入命令即可编译安装。

1
2
emerge --ask sys-kernel/gentoo-sources
emerge --ask sys-kernel/genkernel

配置fstab

fstab是一个很重要的文件,在系统启动的时候,会读取fstab文件并挂载fstab中记录的分区。这里我们有boot分区和根目录分区两个要挂载的分区。而genkernel工具要安装内核的话,自然需要知道我们的启动分区在哪里,因此首先要配置fstab文件。

首先用lsblk -f命令查看分区信息,要注意UUID和标签。另外要注意如果重新分区或者格式化,这些信息就会发生变化。

知道了标签和UUID,就可以填写fstab信息了,虽然也可以用/dev/sda1这样的块设备名称,但是不够安全。所以我们用UUID来编写fstab。第一个是分区名,推荐使用标签或者UUID;第二个是挂载点,也就是分区挂载的位置;第三个是分区的文件系统类型;第四个是挂载选项;第五个dump参数,默认0即可;第六个pass参数,对于根分区要设置成1,其他分区设置成2。最后fstab应该类似这样。当然UUID或者标签应该改成你自己实际的值。你的fstab文件应该类似下面这样。

1
2
UUID=1f3f0313-cfc3-47d3-90e3-52c6d1b67757       /       ext4     noatime 0 1
UUID=2E77-ED51 /boot vfat defaults,noatime 0 2

手动配置内核

在开始手动配置内核之前,我们需要了解硬件的各项信息,这需要安装另一个包来做到。

1
emerge --ask sys-apps/pciutils

在配置之前,还可以通过lsmod命令来查看当前所处的Gentoo Live镜像开启的功能, 这可以作为我们在配置内核时的重要参考。

一切准备好之后,就可以切换到内核源代码目录并打开配置菜单。

1
2
cd /usr/src/linux
make menuconfig

这样就会打开一个终端界面的图形化配置工具,和其他一些终端工具操作方法类似,光标键选择,空格选择,按两下Esc退回到上一个界面,/是搜索。

内核配置工具界面

必选配置

有些内核选项是必须的,必须编译到内核中,而不是作为模块加载。*表示包括到内核中,M表示作为模块加载,[]只有包括到内核中和排除在外两种选项,<>则有包括到内核中、排除在外和以模块加载三种选项。下面这些选项都必须以*方式编译到内核中。

devtmpfs支持。

1
2
3
4
Device Drivers --->
Generic Driver Options --->
[*] Maintain a devtmpfs filesystem to mount at /dev
[*] Automount devtmpfs at /dev, after the kernel mounted the rootfs

SCSI磁盘支持。

1
2
3
Device Drivers --->
SCSI device support --->
<*> SCSI disk support

选择支持的文件系统。因为前面我用的FAT32格式化的ESP,EXT4格式化的根目录,所以这里这两项(FAT32也就是VFAT)必须包括到内核中,虚拟内存和proc文件系统也是必选的。其实这里还可以取消掉不需要的文件系统,但是对于新手我不建议取消任何自己不明白的东西,很容易弄的最后内核没办法启动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
File systems --->
< > Second extended fs support
< > The Extended 3 (ext3) filesystem
<*> The Extended 4 (ext4) filesystem
< > Reiserfs support
< > JFS filesystem support
< > XFS filesystem support
< > Btrfs filesystem support
DOS/FAT/NT Filesystems --->
<*> MSDOS fs support
<*> VFAT (Windows-95) fs support

Pseudo Filesystems --->
[*] /proc file system support
[*] Tmpfs virtual memory file system support (former shm fs)

假如你使用PPPoE拨号的话,还需要启用以下功能。但是因为我们是虚拟机嘛,所以就不用动下面的设置了。

1
2
3
4
5
Device Drivers --->
Network device support --->
<*> PPP (point-to-point protocol) support
<*> PPP support for async serial ports
<*> PPP support for sync tty ports

如果你的处理器是多核的,还需要开启SMP(对称多处理器支持)。

1
2
Processor type and features  --->
[*] Symmetric multi-processing support

就算是虚拟机,也经常遇到使用USB的情况,所以USB也必须启用。

1
2
3
4
5
6
7
8
9
10
11
Device Drivers --->
HID support --->
-*- HID bus support
<*> Generic HID driver
[*] Battery level reporting for HID devices
USB HID support --->
<*> USB HID transport layer
[*] USB support --->
<*> xHCI HCD (USB 3.0) support
<*> EHCI HCD (USB 2.0) support
<*> OHCI HCD (USB 1.1) support

系统体系相关的内核配置

因为我们选择了multlib,所以32和64位的程序都会安装。为了支持32位程序,必须启用32位程序模拟功能。这里其实倒是不用怎么改,默认已经都选上了。

1
2
3
4
5
6
7
8
9
10
11
12
Processor type and features  --->
[*] Machine Check / overheating reporting
[*] Intel MCE Features
[*] AMD MCE Features
Processor family (AMD-Opteron/Athlon64) --->
( ) Opteron/Athlon64/Hammer/K8
( ) Intel P4 / older Netburst based Xeon
( ) Core 2/newer Xeon
( ) Intel Atom
(*) Generic-x86-64
Binary Emulations --->
[*] IA32 Emulation

启用GPT支持,因为前面我用的GPT分区表,EFI启动方式,所以这两项也必须启用。

1
2
3
4
-*- Enable the block layer --->
Partition Types --->
[*] Advanced partition selection
[*] EFI GUID Partition support

EFI的支持。

1
2
3
4
5
6
7
8
Processor type and features  --->
[*] EFI runtime service support
[*] EFI stub support
[*] EFI mixed-mode support

Firmware Drivers --->
EFI (Extensible Firmware Interface) Support --->
<*> EFI Variable Support via sysfs

虚拟机相关内核配置

这里我用的是VMware,所以下列内核选项也必须选择。某些选项依赖其它选项,使用搜索功能查看具体的依赖项,然后依次启用。

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
[*] Networking support  --->
Networking options --->
<*> Virtual Socket protocol
<*> VMware VMCI transport for Virtual Sockets
Device Drivers --->
Misc devices --->
<*> VMware Balloon Driver
<*> VMware VMCI Driver
SCSI device support --->
[*] SCSI low-level drivers --->
<*> VMware PVSCSI driver support
[*] Network device support --->
[*] Ethernet driver support --->
[*] AMD devices
<*> AMD 8111 (new PCI LANCE) support
<*> AMD PCnet32 PCI support
[*] Intel devices
<*> Intel(R) PRO/1000 Gigabit Ethernet support
<*> Intel(R) PRO/1000 PCI-Express Gigabit Ethernet support
<*> VMware VMXNET3 ethernet driver
Input device support --->
[*] Keyboards --->
<*> AT keyboard
File systems --->
<*> FUSE (Filesystem in Userspace) support

把上面所有必选项都配置好之后,选择save保存配置文件,然后exit退出。默认配置文件名是.config,但是最好手动复制一个备份,有时候默认名称的配置文件会莫名丢失,只能重头再配置一次。然后就可以开始编译和安装了。这个过程可能持续十分钟到几个小时,主要取决于你选择的功能大小以及电脑配置。

1
2
3
4
# 编译内核
make && make modules_install
# 编译完成后安装内核到boot分区
make install

自动编译内核

如果你感觉手动配置内核比较麻烦,或者辛辛苦苦配了半天,结果内核没配对系统进不去了。可以考虑第二种方式,就是自动编译内核。这种方式有个缺点就是会开启大部分选项,所以编译内核用时比较长。

网上查了一下可能需要几十分钟到两三个小时,在虚拟机中编译的话可能用时更长,但是考虑到自己研究如何把内核跑通可能也得用这么长时间,所以自动编译内核也不是不可以。假如你在手动配置内核的时候遭受了很多失败,不妨考虑考虑电脑挂在那里让它慢慢编译。

安装firmware和initrampfs

虽然官方文档说这是可选的,但是为了保险起见我们还是安装firmware。

1
emerge --ask sys-kernel/linux-firmware

别忘了安装initramfs,如果你是自动编译内核的话,可以不用安装initramfs,因为genkernel all会自动安装内核以及initramfs。

1
genkernel --install initramfs

内核编译完毕之后,查看/boot分区内容,应该会看到initramfs和vmlinuz文件,这样内核就准备就绪了。

配置系统

接下来就是一些系统配置工作。

网络配置

设置主机名

1
2
3
nano -w /etc/conf.d/hostname
# 设置主机名
hostname="mygentoo"

配置网络

先安装软件包。

1
emerge --ask --noreplace net-misc/netifrc

然后配置DHCP。

1
2
3
4
nano -w /etc/conf.d/net

# 添加DHCP配置
config_eth0="dhcp"

配置网络自启动。

1
2
3
cd /etc/init.d
ln -s net.lo net.eth0
rc-update add net.eth0 default

等到成功进入系统以后,可能会出现没网的情况,因为默认的网络接口名字不一定是eth0,这时候需要使用ip a查看一下实际的名称,然后重新编辑一下网络配置。

1
2
3
4
5
6
7
8
9
10
# 添加正确的网络配置
cd /etc/init.d
ln -s net.lo net.eno16777736
rc-update add net.16777736 default

# 删除错误的配置
rm /etc/init.d/net.eth0
rc-update del net.eth0 default
# 启动网络
rc-service net.16777736 start

实际网络接口名

设置root密码

别忘了设置root密码。

安装工具

还有一些系统工具也需要安装。

日志工具

安装和启动日志工具。

1
2
emerge --ask app-admin/sysklogd
rc-update add sysklogd default

远程登录

因为前面设置了中文区域,所以终端会显示方块。最好这里直接预先开启SSH服务,方便以后远程登录。

1
rc-update add sshd default

文件系统工具

因为系统使用了FAT32和EXT4两个文件系统,所以至少这两种工具必须安装。

1
emerge --ask sys-fs/dosfstools sys-fs/e2fsprogs

网络工具

安装DHCP工具。

1
emerge --ask net-misc/dhcpcd

添加用户

root用户是特权用户,日常使用并不安全,所以需要添加新用户。

1
2
useradd -m -G users,wheel,audio -s /bin/bash yitian
passwd yitian

磁盘清理

是不是想起来有些地方不对劲,没错,还有一开始下载的stage3文件,假如最后系统成功启动了的话,他们就没用了,可以安全的删除掉。

安装系统引导器

最后一步就是安装系统引导器了,推荐使用grub,功能齐全。

安装grub

1
emerge --ask --verbose sys-boot/grub:2

将grub安装到系统中

1
2
grub-install --target=x86_64-efi --efi-directory=/boot
grub-mkconfig -o /boot/grub/grub.cfg

安装完毕之后,输入exit退出chroot环境。

然后就umount分区,然后重启系统。

1
2
3
4
cd
umount -l /mnt/gentoo/dev{/shm,/pts,}
umount -R /mnt/gentoo
reboot

系统成功启动好了吗?

重启之后应该会看到grub的界面,但是别以为这就成功了。因为重点是手动配置编译的内核是否能够正常启动。所以接下来继续观察,看看是否可以成功启动内核进入系统。如果你一次性安装成功,那么恭喜了。

但是我前后总共装了三四天才算成功。第一天照着官方文档慢慢看慢慢敲命令,第二天卡在了配置内核这一步了,然后前后反复测试,最后把前面的各种步骤弄得非常熟悉,可以直接复制粘贴命令。最后我放弃了手动配置内核,做好了编译大半天的准备,于是改用genkernel自动编译内核。然后最悲催的事情发生了,genkernel半个小时以后失败了。

最后没办法我又开始了手动配置内核的步骤,终于慢慢靠着搜索把文档列出来的所有项都找到并配置好了。而且为了保险起见,这次我没有关闭任何默认内核配置,只按着文档把所有新的项加上去了。终于奇迹发生了,重启之后终于成功了!

安装screenfetch截个图,做个纪念。

1
emerge app-misc/screenfetch

screenfetch

故障排除

block device is not a valid root device gentoo

我一开始用的是XFS做根分区的文件系统,也确实在内核中将XFS相关选项设置为编译到内核中,但是奈何不知道哪里漏了,所以最后启动系统的时候,出现了这个错误。理论上找到所有XFS、驱动程序等相关选项,然后添加到内核配置中重新编译一下内核,就可以解决问题了。可惜的是我对内核配置一窍不通,百般之下只好认栽,重新用EXT4安装了一遍,终于成功了。

之前介绍过关于FastDFS单机部署,详见博文:FastDFS+Nginx(单点部署)事例

下面来玩下FastDFS集群部署,实现高可用(HA)

服务器规划:

跟踪服务器1【主机】(Tracker Server):192.100.139.121

跟踪服务器2【备机】(Tracker Server):192.100.139.122

存储服务器1(Storage Server):192.100.139.121

存储服务器2(Storage Server):192.100.139.123

存储服务器3(Storage Server):192.100.139.124

操作系统:CentOS7 

用户:root 

数据存储目录:

应用

安装目录

Nginx

/opt/nginx

Fastdfs

/usr/bin

Keepalived

/usr/local

安装包

/home/yxgly/resources

è /usr/local/src

Tracker_data

/fastdfs/tracker

Storage_data

/fastdfs/storage

  1. FastDFS_v5.08.tar.gz:FastDFS源码 
  2. libfastcommon-master.zip:(从 FastDFS 和 FastDHT 中提取出来的公共 C 函数库) 
  3. fastdfs-nginx-module-master.zip:storage节点http服务nginx模块 
  4. nginx-1.10.0.tar.gz:Nginx安装包 
  5. ngx_cache_purge-2.3.tar.gz:图片缓存清除Nginx模块(集群环境会用到)

 

点击这里下载所有安装包

下载完成后,将压缩包解压到/usr/local/src目录下

1、安装所需的依赖包

yum install make cmake gcc gcc-c++

2、安装libfatscommon

复制代码

cd /usr/local/src
#安装unzip 命令: yum install -y unzip zip
unzip libfastcommon-master.zip
cd libfastcommon-master

编译、安装

./make.sh
./make.sh install

复制代码

3、安装FastDFS

cd /usr/local/src
tar -xzvf FastDFS_v5.08.tar.gz
cd FastDFS
./make.sh
./make.sh install

 采用默认安装方式,相应的文件与目录检查如下:

1> 服务脚本:

/etc/init.d/fdfs_storaged /etc/init.d/fdfs_trackerd

2> 配置文件(示例配置文件):

ll /etc/fdfs/
-rw-r–r– 1 root root 1461 1月 4 14:34 client.conf.sample -rw-r–r– 1 root root 7927 1月 4 14:34 storage.conf.sample -rw-r–r– 1 root root 7200 1月 4 14:34 tracker.conf.sample

 3> 命令行工具(/usr/bin****目录下)

复制代码

ll /usr/bin/fdfs_*
-rwxr-xr-x 1 root root 260584 1月 4 14:34 fdfs_appender_test -rwxr-xr-x 1 root root 260281 1月 4 14:34 fdfs_appender_test1 -rwxr-xr-x 1 root root 250625 1月 4 14:34 fdfs_append_file -rwxr-xr-x 1 root root 250045 1月 4 14:34 fdfs_crc32 -rwxr-xr-x 1 root root 250708 1月 4 14:34 fdfs_delete_file -rwxr-xr-x 1 root root 251515 1月 4 14:34 fdfs_download_file -rwxr-xr-x 1 root root 251273 1月 4 14:34 fdfs_file_info -rwxr-xr-x 1 root root 266401 1月 4 14:34 fdfs_monitor -rwxr-xr-x 1 root root 873233 1月 4 14:34 fdfs_storaged -rwxr-xr-x 1 root root 266952 1月 4 14:34 fdfs_test -rwxr-xr-x 1 root root 266153 1月 4 14:34 fdfs_test1 -rwxr-xr-x 1 root root 371336 1月 4 14:34 fdfs_trackerd -rwxr-xr-x 1 root root 251651 1月 4 14:34 fdfs_upload_appender -rwxr-xr-x 1 root root 252781 1月 4 14:34 fdfs_upload_file

复制代码

1、复制tracker样例配置文件,并重命名

cp /etc/fdfs/tracker.conf.sample /etc/fdfs/tracker.conf

2、修改tracker配置文件

复制代码

vim /etc/fdfs/tracker.conf

修改的内容如下:

disabled=false # 启用配置文件
port=22122 # tracker服务器端口(默认22122)
base_path=/fastdfs/tracker # 存储日志和数据的根目录
store_group=group1
其它参数保留默认配置, 具体配置解释可参考官方文档说明:http://bbs.chinaunix.net/thread-1941456-1-1.html

复制代码

3、创建base_path指定的目录

mkdir -p /fastdfs/tracker

4、防火墙中打开tracker服务器端口( 默认为 22122)

复制代码

vi /etc/sysconfig/iptables
附加:若/etc/sysconfig 目录下没有iptables文件可随便写一条iptables命令配置个防火墙规则:如:
iptables -P OUTPUT ACCEPT
然后用命令:service iptables save 进行保存,默认就保存到 /etc/sysconfig/iptables 文件里。这时既有了这个文件。防火墙也可以启动了。接下来要写策略,也可以直接写在/etc/sysconfig/iptables 里了。
添加如下端口行: -A INPUT -m state –state NEW -m tcp -p tcp –dport 22122 -j ACCEPT
重启防火墙
service iptables restart

复制代码

5、启动tracker服务器

/etc/init.d/fdfs_trackerd start
初次启动,会在/fastdfs/tracker目录下生成logs、data两个目录。
drwxr-xr-x 2 root root 4096 1月 4 15:00 data
drwxr-xr-x 2 root root 4096 1月 4 14:38 logs
检查FastDFS Tracker Server是否启动成功:
ps -ef | grep fdfs_trackerd

1、复制storage样例配置文件,并重命名

cp /etc/fdfs/storage.conf.sample /etc/fdfs/storage.conf

2、编辑配置文件

复制代码

vi /etc/fdfs/storage.conf

修改的内容如下:

disabled=false # 启用配置文件
port=23000 # storage服务端口
base_path=/fastdfs/storage # 数据和日志文件存储根目录
store_path0=/fastdfs/storage # 第一个存储目录
tracker_server=192.100.139.121:22122 # tracker服务器IP和端口
tracker_server=192.100.139.122:22122 #tracker服务器IP2和端口[Microsof1]
http.server_port=8888 # http访问文件的端口
其它参数保留默认配置, 具体配置解释可参考官方文档说明:http://bbs.chinaunix.net/thread-1941456-1-1.html

复制代码

3、创建基础数据目录

mkdir -p /fastdfs/storage

4、防火墙中打开storage服务器端口( 默认为 23000)

vi /etc/sysconfig/iptables
#添加如下端口行: -A INPUT -m state –state NEW -m tcp -p tcp –dport 23000 -j ACCEPT
重启防火墙
service iptables restart

追踪+存储节点操作步骤一、步骤二、步骤三

存储节点只做存储则只操作步骤三

5、启动storage服务器

/etc/init.d/fdfs_storaged start
初次启动,会在/fastdfs/storage目录下生成logs、data两个目录。
drwxr-xr-x 259 root root 4096 Mar 31 06:22 data
drwxr-xr-x 2 root root 4096 Mar 31 06:22 logs

检查FastDFS Tracker Server是否启动成功:

[root@gyl-test-t9 ~]# ps -ef | grep fdfs_storaged
root 1336 1 3 06:22 ? 00:00:01 /usr/bin/fdfs_storaged /etc/fdfs/storage.conf
root 1347 369 0 06:23 pts/0 00:00:00 grep fdfs_storaged

1、修改Tracker服务器客户端配置文件

cp /etc/fdfs/client.conf.sample /etc/fdfs/client.conf
vim /etc/fdfs/client.conf

修改以下配置,其它保持默认

base_path=/fastdfs/tracker
tracker_server=192.100.139.121:22122 # tracker服务器IP和端口
tracker_server=192.100.139.122:22122 #tracker服务器IP2和端口

2、执行文件上传命令

#/usr/local/src/test.png 是需要上传文件路径 /usr/bin/fdfs_upload_file /etc/fdfs/client.conf /usr/local/src/test.png
返回文件ID号:group1/M00/00/00/tlxkwlhttsGAU2ZXAAC07quU0oE095.png
(能返回以上文件ID,说明文件已经上传成功)
Or : /usr/bin/fdfs_test /etc/fdfs/client.conf upload client.conf

1、fastdfs-nginx-module 作用说明

FastDFS 通过 Tracker 服务器**,将文件放在 Storage 服务器存储,但是同组存储服务器之间需要进入文件复制****,有同步延迟的问题。假设 Tracker 服务器将文件上传到了 *ip01,上传成功后文件 ID 已经返回给客户端。此时 FastDFS 存储集群机制会将这个文件同步到同组存储 *ip02,在文件还没有复制完成的情况下*,客户端如果用这个文件 ID ip02 上取文件,就会出现文件无法访问的错误。而* fastdfs-nginx-module 可以重定向文件连接到源服务器取文件,避免客户端由于复制延迟导致的文件无法访问错误。****(解压后的 fastdfs-nginx-module nginx 安装时使用)**

2、解压 fastdfs-nginx-module_v1.16.tar.gz

cd /usr/local/src
tar -xzvf fastdfs-nginx-module_v1.16.tar.gz

3、修改 fastdfs-nginx-module 的 config 配置文件

cd fastdfs-nginx-module/src
vim config

CORE_INCS=“$CORE_INCS /usr/local/include/fastdfs /usr/local/include/fastcommon/“ 修改为:
CORE_INCS=“$CORE_INCS /usr/include/fastdfs /usr/include/fastcommon/“

4、安装编译 Nginx 所需的依赖包

yum install gcc gcc-c++ make automake autoconf libtool pcre* zlib openssl openssl-devel

5、编译安装 Nginx (添加 fastdfs-nginx-module 模块)

cd /usr/local/src/ tar -zxvf nginx-1.10.0.tar.gz
tar –zxvf ngx_cache_purge_2.3.tar.gz
cd nginx-1.10.0 ./configure –prefix=/opt/nginx –add-module=/usr/local/src/fastdfs-nginx-module/src –add-module=/usr/local/src/ngx_cache_purge-2.3 make && make install

7、复制 fastdfs-nginx-module 源码中的配置文件到/etc/fdfs 目录,并修改

复制代码

cp /usr/local/src/fastdfs-nginx-module/src/mod_fastdfs.conf /etc/fdfs/ vi /etc/fdfs/mod_fastdfs.conf
修改以下配置:
connect_timeout=10 base_path=/tmp
tracker_server=192.100.139.121:22122 # tracker服务器IP和端口
tracker_server=192.100.139.122:22122 #tracker服务器IP2和端口
url_have_group_name=true #url中包含group名称
#在最后添加 [group1]
group_name=group1
storage_server_port=23000 store_path_count=1 store_path0=/fastdfs/storage

复制代码

 8、复制 FastDFS 的部分配置文件到/etc/fdfs 目录

cd /usr/local/src/FastDFS/conf
cp http.conf mime.types /etc/fdfs/

9、在/fastdfs/storage 文件存储目录下创建软连接,将其链接到实际存放数据的目录

ln -s /fastdfs/storage/data/ /fastdfs/storage/data/M00

10、配置 Nginx

复制代码

user nobody;
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 8888;
server_name 192.100.139.121;
location ~/group1/M00 {
ngx_fastdfs_module;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
upstream storage_server_group1{
server 192.100.139.121:8888 weight=10;
server 192.100.139.123:8888 weight=10;
server 192.100.139.124:8888 weight=10;
}
}

复制代码

说明: 

 A、8888 端口值是要与/etc/fdfs/storage.conf 中的 http.server_port=8888 相对应, 因为 http.server_port 默认为 8888,如果想改成 80,则要对应修改过来。

 B、Storage 对应有多个 group 的情况下,访问路径带 group 名,如/group1/M00/00/00/xxx, 对应的 Nginx 配置为:

     location ~/group([0-9])/M00 {

         ngx_fastdfs_module;

}

C、如查下载时如发现老报 404,将 nginx.conf 第一行 user nobody 修改为 user root 后重新启动。

11、防火墙中打开 Nginx 的 8888 端口

复制代码

vi /etc/sysconfig/iptables
添加: -A INPUT -m state –state NEW -m tcp -p tcp –dport 8888 -j ACCEPT
#重启防火墙
service iptables restart

启动nginx : /opt/nginx/sbin/nginx
(重启 Nginx 的命令为:/opt/nginx/sbin/nginx -s reload)

复制代码

切换追踪服务器IP同样可以访问

http://192.100.139.121:8888/group1/M00/00/00/CmSKtFj13gyAen4oAAH0yXi-HW8296.png

http://192.100.139.122:8888/group1/M00/00/00/CmSKtFj13gyAen4oAAH0yXi-HW8296.png

1、前往GitHub下载Java_client代码。https://github.com/fzmeng/fastdfs.client

2.在你的项目src/java/resources 下加入文件 fastdfs_client.conf

注意修改tracker服务器Ip地址

复制代码

connect_timeout = 2 network_timeout = 30 charset = ISO8859-1 http.tracker_http_port = 8888 http.anti_steal_token = no
tracker_server=192.100.139.121:22122 tracker_server=192.100.139.122:22122 default_group_name=group1

复制代码

一、简介

介绍FluentValidation的文章不少,零度编程的介绍我引用下:FluentValidation 是一个基于 .NET 开发的验证框架,开源免费,而且优雅,支持链式操作,易于理解,功能完善,还是可与 MVC5、WebApi2 和 ASP.NET CORE 深度集成,组件内提供十几种常用验证器,可扩展性好,支持自定义验证器,支持本地化多语言。

其实它也可以用于WPF属性验证,本文主要也是讲解该组件在WPF中的使用,FluentValidation官网是: https://fluentvalidation.net/ 。

二、本文需要实现的功能

提供WPF界面输入验证,采用MVVM方式,需要以下功能:

  1. 能验证ViewModel中定义的简单属性;
  2. 能验证ViewModel中定义的复杂属性,比如对象属性的子属性,如VM有个学生属性Student,需要验证他的姓名、年龄等;
  3. 能简单提供两种验证样式;
  4. 没有了,就是前面3点…

先看实现效果图:

FluentValidation在C# WPF中的应用

三、调研中遇到的问题

简单属性:验证ViewModel的普通属性比较简单,可以参考FluentValidation官网:链接 ,或者国外holymoo大神的代码: UserValidator.cs 。

复杂属性:我遇到的问题是,怎么验证ViewModel中对象属性的子属性?见第二个功能描述,FluentValidation的官网有Complex Properties的例子,但是我试了没效果,贴上官方源码截图:

FluentValidation在C# WPF中的应用

最后我Google到这篇文章,根据该链接代码,ViewModel和子属性都实现IDataErrorInfo接口,即可实现复杂属性验证,文章中没有具体实现,但灵感是从这来的,就不具体说该链接代码了,有兴趣可以点击链接阅读,下面贴上代码。

四、开发步骤

4.1、创建工程、引入库

创建.Net Core WPF模板解决方案(.Net Framework模板也行):WpfFluentValidation,引入Nuget包FluentValidation(8.5.1)。

4.2、创建测试实体类学生:Student.cs

此类用作ViewModel中的复杂属性使用,学生类包含3个属性:名字、年龄、邮政编码。此实体需要继承IDataErrorInfo接口,触发FluentValidation必须的。

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
using System.ComponentModel;
using System.Linq;
using WpfFluentValidation.Validators;

namespace WpfFluentValidation.Models
{





public class Student : BaseClass, IDataErrorInfo
{
private string name;
public string Name
{
get { return name; }
set
{
if (value != name)
{
name = value;
OnPropertyChanged(nameof(Name));
}
}
}
private int age;
public int Age
{
get { return age; }
set
{
if (value != age)
{
age = value;
OnPropertyChanged(nameof(Age));
}
}
}
private string zip;
public string Zip
{
get { return zip; }
set
{
if (value != zip)
{
zip = value;
OnPropertyChanged(nameof(Zip));
}
}
}

public string Error { get; set; }

public string this[string columnName]
{
get
{
if (validator == null)
{
validator = new StudentValidator();
}
var firstOrDefault = validator.Validate(this)
.Errors.FirstOrDefault(lol => lol.PropertyName == columnName);
return firstOrDefault?.ErrorMessage;
}
}

private StudentValidator validator { get; set; }
}
}

4.3、创建学生验证器:StudentValidator.cs

验证属性的写法有两种:

  1. 可以在实体属性上方添加特性(本文不作特别说明,百度文章介绍很多);
  2. 通过代码的形式添加,如下方,创建一个验证器类,继承自AbstractValidator,在此验证器构造函数中写规则验证属性,方便管理。

本文使用第二种,见下方学生验证器代码:

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
using FluentValidation;
using System.Text.RegularExpressions;
using WpfFluentValidation.Models;

namespace WpfFluentValidation.Validators
{
public class StudentValidator : AbstractValidator<Student>
{
public StudentValidator()
{
RuleFor(vm => vm.Name)
.NotEmpty()
.WithMessage("请输入学生姓名!")
.Length(5, 30)
.WithMessage("学生姓名长度限制在5到30个字符之间!");

RuleFor(vm => vm.Age)
.GreaterThanOrEqualTo(0)
.WithMessage("学生年龄为整数!")
.ExclusiveBetween(10, 150)
.WithMessage($"请正确输入学生年龄(10-150)");

RuleFor(vm => vm.Zip)
.NotEmpty()
.WithMessage("邮政编码不能为空!")
.Must(BeAValidZip)
.WithMessage("邮政编码由六位数字组成。");
}

private static bool BeAValidZip(string zip)
{
if (!string.IsNullOrEmpty(zip))
{
var regex = new Regex(@"\d{6}");
return regex.IsMatch(zip);
}
return false;
}
}
}

4.4、 创建ViewModel类:StudentViewModel.cs

StudentViewModel与Student实体类结构类似,都需要实现IDataErrorInfo接口,该类由一个简单的string属性(Title)和一个复杂的Student对象属性(CurrentStudent)组成,代码如下:

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
using System;
using System.ComponentModel;
using System.Linq;
using WpfFluentValidation.Models;
using WpfFluentValidation.Validators;

namespace WpfFluentValidation.ViewModels
{





public class StudentViewModel : BaseClass, IDataErrorInfo
{
private string title;
public string Title
{
get { return title; }
set
{
if (value != title)
{
title = value;
OnPropertyChanged(nameof(Title));
}
}
}

private Student currentStudent;
public Student CurrentStudent
{
get { return currentStudent; }
set
{
if (value != currentStudent)
{
currentStudent = value;
OnPropertyChanged(nameof(CurrentStudent));
}
}
}

public StudentViewModel()
{
CurrentStudent = new Student()
{
Name = "李刚的儿",
Age = 23
};
}


public string this[string columnName]
{
get
{
if (validator == null)
{
validator = new ViewModelValidator();
}
var firstOrDefault = validator.Validate(this)
.Errors.FirstOrDefault(lol => lol.PropertyName == columnName);
return firstOrDefault?.ErrorMessage;
}
}
public string Error
{
get
{
var results = validator.Validate(this);
if (results != null && results.Errors.Any())
{
var errors = string.Join(Environment.NewLine, results.Errors.Select(x => x.ErrorMessage).ToArray());
return errors;
}

return string.Empty;
}
}

private ViewModelValidator validator;
}
}

仔细看上方代码,对比Student.cs,重写自IDataErrorInfo的Error属性定义有所不同。Student.cs对Error基本未做修改,而StudentViewModel.cs有变化,get器中验证属性(简单属性Title和复杂属性CurrentStudent),返回错误提示字符串,诶,CurrentStudent的验证器怎么生效的?有兴趣的大佬可以研究FluentValidation库源码一探究竟,我没有深究。

4.5 StudentViewModel的验证器ViewModelValidator.cs

ViewModel的验证器,相比Student的验证器StudentValidator,就简单的多了,因为只需要编写验证一个简单属性Title的代码。而复杂属性CurrentStudent的验证器StudentValidator,将被WPF属性系统自动调用,即在StudentViewModel的索引器this[string columnName]和Error属性中调用,界面触发规则时自动调用,具体是什么机制我没有仔细研究,有兴趣的大佬可以研究源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using FluentValidation;
using WpfFluentValidation.ViewModels;

namespace WpfFluentValidation.Validators
{
public class ViewModelValidator:AbstractValidator<StudentViewModel>
{
public ViewModelValidator()
{
RuleFor(vm => vm.Title)
.NotEmpty()
.WithMessage("标题长度不能为空!")
.Length(5, 30)
.WithMessage("标题长度限制在5到30个字符之间!");
}
}
}

4.6 辅助类BaseClass.cs

简单封装INotifyPropertyChanged接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using System.ComponentModel;

namespace WpfFluentValidation
{
public class BaseClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}

4.7 、视图StudentView.xaml

用户直接接触的视图文件来了,比较简单,提供简单属性标题(Title)、复杂属性学生姓名(CurrentStudent.Name)、学生年龄( CurrentStudent .Age)、学生邮政编码( CurrentStudent .Zip)验证,xaml代码如下:

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
<UserControl x:Class="WpfFluentValidation.Views.StudentView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfFluentValidation.Views"
xmlns:vm="clr-namespace:WpfFluentValidation.ViewModels"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.DataContext>
<vm:StudentViewModel/>
</UserControl.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>

<GroupBox Header="ViewModel直接属性验证">
<StackPanel Orientation="Horizontal">
<Label Content="标题:"/>
<TextBox Text="{Binding Title, UpdateSourceTrigger=PropertyChanged,ValidatesOnDataErrors=True}"
Style="{StaticResource ErrorStyle1}"/>
</StackPanel>
</GroupBox>
<GroupBox Header="ViewModel对象属性CurrentStudent的属性验证" Grid.Row="1">
<StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="姓名:"/>
<TextBox Text="{Binding CurrentStudent.Name, UpdateSourceTrigger=PropertyChanged,ValidatesOnDataErrors=True}"
Style="{StaticResource ErrorStyle2}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="年龄:"/>
<TextBox Text="{Binding CurrentStudent.Age, UpdateSourceTrigger=PropertyChanged,ValidatesOnDataErrors=True}"
Style="{StaticResource ErrorStyle2}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="邮编:" />
<TextBox Text="{Binding CurrentStudent.Zip, UpdateSourceTrigger=PropertyChanged,ValidatesOnDataErrors=True}"
Style="{StaticResource ErrorStyle2}"/>
</StackPanel>
</StackPanel>
</GroupBox>
</Grid>
</UserControl>

4.8 、错误提示样式

本文提供了两种样式,具体效果见前面的截图,代码如下:

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
<Application x:Class="WpfFluentValidation.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfFluentValidation"
StartupUri="MainWindow.xaml">
<Application.Resources>
<Style TargetType="StackPanel">
<Setter Property="Margin" Value="0 5"/>
</Style>

<Style TargetType="{x:Type TextBox}" x:Key="ErrorStyle1">
<Setter Property="Width" Value="200"/>
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel>
<Grid DockPanel.Dock="Right" Width="16" Height="16"
VerticalAlignment="Center" Margin="3 0 0 0">
<Ellipse Width="16" Height="16" Fill="Red"/>
<Ellipse Width="3" Height="8"
VerticalAlignment="Top" HorizontalAlignment="Center"
Margin="0 2 0 0" Fill="White"/>
<Ellipse Width="2" Height="2" VerticalAlignment="Bottom"
HorizontalAlignment="Center" Margin="0 0 0 2"
Fill="White"/>
</Grid>
<Border BorderBrush="Red" BorderThickness="2" CornerRadius="2">
<AdornedElementPlaceholder/>
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource=
{x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>


<Style TargetType="{x:Type TextBox}" x:Key="ErrorStyle2">
<Setter Property="Width" Value="200"/>
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<StackPanel Orientation="Horizontal">
<AdornedElementPlaceholder x:Name="textBox"/>
<TextBlock Margin="10" Text="{Binding [0].ErrorContent}" Foreground="Red"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource=
{x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</Application.Resources>
</Application>

五、 介绍完毕

码农就是这样,文章基本贴代码,哈哈。

6、源码同步

本文代码已同步gitee: https://gitee.com/lsq6/FluentValidationForWpf

github: https://github.com/dotnet9/FluentValidationForWPF

CSDN: https://download.csdn.net/download/HenryMoore/11984265

版权声明:本文为Dotnet9的博客博主「沙漠尽头的狼」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。

原文链接:https://dotnet9.com/?p=853

Flume+Kafka+Storm+Redis实时分析系统基本架构_基于flume+kafka+strom+redis整合项目-CSDN博客

Excerpt

文章浏览阅读292次。防丢拷贝一份–。原文:https://blog.csdn.net/ymh198816/article/details/51998085今天作者要在这里通过一个简单的电商网站订单实时分析系统和大家一起梳理一下大数据环境下的实时分析系统的架构模型。当然这个架构模型只是实时分析技术的一 个简单的入门级架构,实际生产环境中的大数据实时分析技术还涉及到很多细节的处理, 比如使用Storm的ACK…_基于flume+kafka+strom+redis整合项目


防丢拷贝一份–。

原文:https://blog.csdn.net/ymh198816/article/details/51998085 

今天作者要在这里通过一个简单的电商网站订单实时分析系统和大家一起梳理一下大数据环境下的实时分析系统的架构模型。当然这个架构模型只是实时分析技术的一 个简单的入门级架构,实际生产环境中的大数据实时分析技术还涉及到很多细节的处理, 比如使用Storm的ACK机制保证数据都能被正确处理, 集群的高可用架构, 消费数据时如何处理重复数据或者丢失数据等问题,根据不同的业务场景,对数据的可靠性要求以及系统的复杂度的要求也会不同。这篇文章的目的只是带大家入个门,让大家对实时分析技术有一个简单的认识,并和大家一起做学习交流。
文章的最后还有Troubleshooting,分享了作者在部署本文示例程序过程中所遇到的各种问题和解决方案。

系统基本架构

整个实时分析系统的架构就是先由电商系统的订单服务器产生订单日志, 然后使用Flume去监听订单日志,并实时把每一条日志信息抓取下来并存进Kafka消息系统中, 接着由Storm系统消费Kafka中的消息,同时消费记录由Zookeeper集群管理,这样即使Kafka宕机重启后也能找到上次的消费记录,接着从上次宕机点继续从Kafka的Broker中进行消费。但是由于存在先消费后记录日志或者先记录后消费的非原子操作,如果出现刚好消费完一条消息并还没将信息记录到Zookeeper的时候就宕机的类似问题,或多或少都会存在少量数据丢失或重复消费的问题, 其中一个解决方案就是Kafka的Broker和Zookeeper都部署在同一台机子上。接下来就是使用用户定义好的Storm Topology去进行日志信息的分析并输出到Redis缓存数据库中(也可以进行持久化),最后用Web APP去读取Redis中分析后的订单信息并展示给用户。之所以在Flume和Storm中间加入一层Kafka消息系统,就是因为在高并发的条件下, 订单日志的数据会井喷式增长,如果Storm的消费速度(Storm的实时计算能力那是最快之一,但是也有例外, 而且据说现在Twitter的开源实时计算框架Heron比Storm还要快)慢于日志的产生速度,加上Flume自身的局限性,必然会导致大量数据滞后并丢失,所以加了Kafka消息系统作为数据缓冲区,而且Kafka是基于log File的消息系统,也就是说消息能够持久化在硬盘中,再加上其充分利用Linux的I/O特性,提供了可观的吞吐量。架构中使用Redis作为数据库也是因为在实时的环境下,Redis具有很高的读写速度。

业务背景
各大电商网站在合适的时间进行各种促销活动已是常态,在能为网站带来大量的流量和订单的同时,对于用户也有不小的让利,必然是大家伙儿喜闻乐见的。在促销活动期间,老板和运营希望能实时看到订单情况,老板开心,运营也能根据实时的订单数据调整运营策略,而让用户能实时看到网站的订单数据,也会勾起用户的购买欲。但是普通的离线计算系统已然不能满足在高并发环境下的实时计算要求,所以我们得使用专门实时计算系统,如:Storm, Heron, Spark Stream等,去满足类似的需求。
既然要分析订单数据,那必然在订单产生的时候要把订单信息记录在日志文件中。本文中,作者通过使用log4j2,以及结合自己之前开发电商系统的经验,写了一个订单日志生成模拟器,代码如下,能帮助大家随机产生订单日志。下面所展示的订单日志文件格式和数据就是我们本文中的分析目标,本文的案例中用来分析所有商家的订单总销售额并找出销售额钱20名的商家。

订单数据格式:
orderNumber: XX | orderDate: XX | paymentNumber: XX | paymentDate: XX | merchantName: XX | sku: [ skuName: XX skuNum: XX skuCode: XX skuPrice: XX totalSkuPrice: XX;skuName: XX skuNum: XX skuCode: XX skuPrice: XX totalSkuPrice: XX;] | price: [ totalPrice: XX discount: XX paymentPrice: XX ]

订单日志生成程序:
使用log4j2将日志信息写入文件中,每小时滚动一次日志文件


     
       
         
       

        
            
               
                
              

       
                 
     

     
       
         
         
       

     


生成器代码:
package com.guludada.ordersInfo;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;

// Import log4j classes.
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class ordersInfoGenerator {

         public enum paymentWays {

        Wechat,Alipay,Paypal
    }
    public enum merchantNames {

        优衣库,天猫,淘宝,咕噜大大,快乐宝贝,守望先峰,哈毒妇,Storm,Oracle,Java,CSDN,跑男,路易斯威登,
        暴雪公司,Apple,Sumsam,Nissan,Benz,BMW,Maserati
    }

         public enum productNames {

        黑色连衣裙, 灰色连衣裙, 棕色衬衫, 性感牛仔裤, 圆脚牛仔裤,塑身牛仔裤, 朋克卫衣,高腰阔腿休闲裤,人字拖鞋,
        沙滩拖鞋
    }

         float[] skuPriceGroup = {299,399,699,899,1000,2000};
    float[] discountGroup = {10,20,50,100};
    float totalPrice = 0;
    float discount = 0;
    float paymentPrice = 0;

         private static final Logger logger = LogManager.getLogger(ordersInfoGenerator.class);
    private int logsNumber = 1000;

         public void generate() {

                         for(int i = 0; i <= logsNumber; i++) {            
            logger.info(randomOrderInfo());            
        }
    }

         public String randomOrderInfo() {

                 SimpleDateFormat sdf = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”);        
        Date date = new Date();        

                 String orderNumber = randomNumbers(5) + date.getTime();

                 String orderDate = sdf.format(date);

                 String paymentNumber = randomPaymentWays() + “-“ + randomNumbers(8);

                 String paymentDate = sdf.format(date);

                 String merchantName = randomMerchantNames();

                 String skuInfo = randomSkus();

                 String priceInfo = calculateOrderPrice();

                 return “orderNumber: “ + orderNumber + “ | orderDate: “ + orderDate + “ | paymentNumber: “ +
            paymentNumber + “ | paymentDate: “ + paymentDate + “ | merchantName: “ + merchantName + 
            “ | sku: “ + skuInfo + “ | price: “ + priceInfo;
    }

             private String randomPaymentWays() {

                 paymentWays[] paymentWayGroup = paymentWays.values();
        Random random = new Random();
        return paymentWayGroup[random.nextInt(paymentWayGroup.length)].name();
    }

         private String randomMerchantNames() {

                 merchantNames[] merchantNameGroup = merchantNames.values();
        Random random = new Random();
        return merchantNameGroup[random.nextInt(merchantNameGroup.length)].name();
    }

         private String randomProductNames() {

                 productNames[] productNameGroup = productNames.values();
        Random random = new Random();
        return productNameGroup[random.nextInt(productNameGroup.length)].name();
    }

              private String randomSkus() {

                 Random random = new Random();
        int skuCategoryNum = random.nextInt(3);

                 String skuInfo =”[“;

                 totalPrice = 0;
        for(int i = 1; i <= 3; i++) {

                         int skuNum = random.nextInt(3)+1;
            float skuPrice = skuPriceGroup[random.nextInt(skuPriceGroup.length)];
            float totalSkuPrice = skuPrice * skuNum;            
            String skuName = randomProductNames();
            String skuCode = randomCharactersAndNumbers(10);
            skuInfo += “ skuName: “ + skuName + “ skuNum: “ + skuNum + “ skuCode: “ + skuCode
                    + “ skuPrice: “ + skuPrice + “ totalSkuPrice: “ + totalSkuPrice + “;”;        
            totalPrice += totalSkuPrice;
        }

                          skuInfo += “ ]“;

                 return skuInfo;
    }

         private String calculateOrderPrice() {

                 Random random = new Random();
        discount = discountGroup[random.nextInt(discountGroup.length)];
        paymentPrice = totalPrice - discount;

                 String priceInfo = “[ totalPrice: “ + totalPrice + “ discount: “ + discount + “ paymentPrice: “ + paymentPrice +” ]“;

                 return priceInfo;
    }

         private String randomCharactersAndNumbers(int length) {

                 String characters = “abcdefghijklmnopqrstuvwxyz0123456789”;
        String randomCharacters = “”;  
                Random random = new Random();  
                for (int i = 0; i < length; i++) {  
              randomCharacters += characters.charAt(random.nextInt(characters.length()));  
                }  
                return randomCharacters;  
    }

         private String randomNumbers(int length) {

                 String characters = “0123456789”;
        String randomNumbers = “”;   
                Random random = new Random();  
                for (int i = 0; i < length; i++) {  
             randomNumbers += characters.charAt(random.nextInt(characters.length()));  
                }  
               return randomNumbers;        
    }

         public static void main(String[] args) {

                 ordersInfoGenerator generator = new ordersInfoGenerator();
        generator.generate();
    }
}

收集日志数据
采集数据的方式有多种,一种是通过自己编写shell脚本或Java编程采集数据,但是工作量大,不方便维护,另一种就是直接使用第三方框架去进行日志的采集,一般第三方框架的健壮性,容错性和易用性都做得很好也易于维护。本文采用第三方框架Flume进行日志采集,Flume是一个分布式的高效的日志采集系统,它能把分布在不同服务器上的海量日志文件数据统一收集到一个集中的存储资源中,Flume是Apache的一个顶级项目,与Kafka也有很好的兼容性。不过需要注意的是Flume并不是一个高可用的框架,这方面的优化得用户自己去维护。

Flume的agent是运行在JVM上的,所以各个服务器上的JVM环境必不可少。每一个Flume agent部署在一台服务器上,Flume会收集web server产生的日志数据,并封装成一个个的事件发送给Flume Agent的Source,Flume Agent Source会消费这些收集来的数据事件(Flume Event)并放在Flume Agent Channel,Flume Agent Sink会从Channel中收集这些采集过来的数据,要么存储在本地的文件系统中要么作为一个消费资源分给下一个装在分布式系统中其它服务器上的Flume Agent进行处理。Flume提供了点对点的高可用的保障,某个服务器上的Flume Agent Channel中的数据只有确保传输到了另一个服务器上的Flume Agent Channel里或者正确保存到了本地的文件存储系统中,才会被移除。

在本文中,Flume的Source我们选择的是Exec Source,因为是实时系统,直接通过tail 命令来监听日志文件,而在Kafka的Broker集群端的Flume我们选择Kafka Sink 来把数据下沉到Kafka消息系统中。

下图是来自Flume官网里的Flume拉取数据的架构图:

    图片来源:http://flume.apache.org/FlumeUserGuide.html

订单日志产生端的Flume配置文件如下:
agent.sources = origin
agent.channels = memorychannel
agent.sinks = target

agent.sources.origin.type = exec
agent.sources.origin.command = tail -F /export/data/trivial/app.log
agent.sources.origin.channels = memorychannel

agent.sources.origin.interceptors = i1
agent.sources.origin.interceptors.i1.type = static
agent.sources.origin.interceptors.i1.key = topic
agent.sources.origin.interceptors.i1.value = ordersInfo

agent.sinks.loggerSink.type = logger
agent.sinks.loggerSink.channel = memorychannel

agent.channels.memorychannel.type = memory
agent.channels.memorychannel.capacity = 10000

agent.sinks.target.type = avro
agent.sinks.target.channel = memorychannel
agent.sinks.target.hostname = 172.16.124.130
agent.sinks.target.port = 4545

Kafka消息系统端Flume配置文件
agent.sources = origin
agent.channels = memorychannel
agent.sinks = target

agent.sources.origin.type = avro
agent.sources.origin.channels = memorychannel
agent.sources.origin.bind = 0.0.0.0
agent.sources.origin.port = 4545

agent.sinks.loggerSink.type = logger
agent.sinks.loggerSink.channel = memorychannel

agent.channels.memorychannel.type = memory
agent.channels.memorychannel.capacity = 5000000
agent.channels.memorychannel.transactionCapacity = 1000000

agent.sinks.target.type = org.apache.flume.sink.kafka.KafkaSink
#agent.sinks.target.topic = bigdata
agent.sinks.target.brokerList=localhost:9092
agent.sinks.target.requiredAcks=1
agent.sinks.target.batchSize=100
agent.sinks.target.channel = memorychannel

这里需要注意的是,在日志服务器端的Flume agent中我们配置了一个interceptors,这个是用来为Flume Event(Flume Event就是拉取到的一行行的日志信息)的头部添加key为“topic”的K-V键值对,这样这条抓取到的日志信息就会根据topic的值去到Kafka中指定的topic消息池中,当然还可以为Flume Event额外配置一个key为“Key”的键值对,Kafka Sink会根据key“Key”的值将这条日志信息下沉到不同的Kafka分片上,否则就是随机分配。在Kafka集群端的Flume配置里,有几个重要的参数需要注意,“topic”是指定抓取到的日志信息下沉到Kafka哪一个topic池中,如果之前Flume发送端为Flume Event添加了带有topic的头信息,则这里可以不用配置;brokerList就是配置Kafka集群的主机地址和端口;requireAcks=1是配置当下沉到Kafka的消息储存到特定partition的leader中成功后就返回确认消息,requireAcks=0是不需要确认消息成功写入Kafka中,requireAcks=-1是指不光需要确认消息被写入partition的leander中,还要确认完成该条消息的所有备份;batchSize配置每次下沉多少条消息,每次下沉的数量越多延迟也高。

Kafka消息系统
这一部分我们将谈谈Kafka的配置和使用,Kafka在我们的系统中实际上就相当于起到一个数据缓冲池的作用, 有点类似于ActiveQ的消息队列和Redis这样的缓存区的作用,但是更可靠,因为是基于log File的消息系统,数据不容易丢失,以及能记录数据的消费位置并且用户还可以自定义消息消费的起始位置,这就使得重复消费消息也可以得以实现,而且同时具有队列和发布订阅两种消息消费模式,十分灵活,并且与Storm的契合度很高,充分利用Linux系统的I/O提高读写速度等等。另一个要提的方面就是Kafka的Consumer是pull-based模型的,而Flume是push-based模型。push-based模型是尽可能大的消费数据,但是当生产者速度大于消费者时数据会被覆盖。而pull-based模型可以缓解这个压力,消费速度可以慢于生产速度,有空余时再拉取那些没拉取到的数据。

Kafka是一个分布式的高吞吐量的消息系统,同时兼有点对点和发布订阅两种消息消费模式。Kafka主要由Producer,Consumer和Broker组成。Kafka中引入了一个叫“topic”的概念,用来管理不同种类的消息,不同类别的消息会记录在到其对应的topic池中,而这些进入到topic中的消息会被Kafka写入磁盘的log文件中进行持久化处理。Kafka会把消息写入磁盘的log file中进行持久化对于每一个topic里的消息log文件,Kafka都会对其进行分片处理,而每一个消息都会顺序写入中log分片中,并且被标上“offset”的标量来代表这条消息在这个分片中的顺序,并且这些写入的消息无论是内容还是顺序都是不可变的。所以Kafka和其它消息队列系统的一个区别就是它能做到分片中的消息是能顺序被消费的,但是要做到全局有序还是有局限性的,除非整个topic只有一个log分片。并且无论消息是否有被消费,这条消息会一直保存在log文件中,当留存时间足够长到配置文件中指定的retention的时间后,这条消息才会被删除以释放空间。对于每一个Kafka的Consumer,它们唯一要存的Kafka相关的元数据就是这个“offset”值,记录着Consumer在分片上消费到了哪一个位置。通常Kafka是使用Zookeeper来为每一个Consumer保存它们的offset信息,所以在启动Kafka之前需要有一个Zookeeper集群;而且Kafka默认采用的是先记录offset再读取数据的策略,这种策略会存在少量数据丢失的可能。不过用户可以灵活设置Consumer的“offset”的位置,在加上消息记录在log文件中,所以是可以重复消费消息的。log的分片和它们的备份会分散保存在集群的服务器上,对于每一个partition,在集群上都会有一台这个partition存在的服务器作为leader,而这个partitionpartition的其它备份所在的服务器做为follower,leader负责处理关于这个partition的所有请求,而follower负责这个partition的其它备份的同步工作,当leader服务器宕机时,其中一个follower服务器就会被选举为新的leader。

一般的消息系统分为两种模式,一种是点对点的消费模式,也就是queuing模式,另一种是发布订阅模式,也就是publish-subscribe模式,而Kafka引入了一个Consumer Group的概念,使得其能兼有两种模式。在Kafka中,每一个consumer都会标明自己属于哪个consumer group,每个topic的消息都会分发给每一个subscribe了这个topic的所有consumer group中的一个consumer实例。所以当所有的consumers都在同一个consumer group中,那么就像queuing的消息系统,一个message一次只被一个consumer消费。如果每一个consumer都有不同consumer group,那么就像public-subscribe消息系统一样,一个消息分发给所有的consumer实例。对于普通的消息队列系统,可能存在多个consumer去同时消费message,虽然message是有序地分发出去的,但是由于网络延迟的时候到达不同的consumer的时间不是顺序的,这时就失去了顺序性,解决方案是只用一个consumer去消费message,但显然不太合适。而对于Kafka来说,一个partiton只分发给每一个consumer group中的一个consumer实例,也就是说这个partition只有一个consumer实例在消费,所以可以保证在一个partition内部数据的处理是有序的,不同之处就在于Kafka内部对消息进行了分片处理,虽然看上去也是单consumer的做法,但是分片机制保证了并发消费。如果要做到全局有序,那么整个topic中的消息只有一个分片,并且每一个consumer group中只能有一个consumer实例。这实际上就是彻底牺牲了消息消费时的并发度。

Kafka的配置和部署十分简单
1. 首先启动Zookeeper集群,Kafka需要Zookeeper集群来帮助记录每一个Consumer的offset
2. 为集群上的每一台Kafka服务器单独配置配置文件,比如我们需要设置有两个节点的Kafka集群,那么节点1和节点2的最基本的配置如下:
config/server-1.properties:
    broker.id=1
    listeners=PLAINTEXT://:9093
    log.dir=export/data/kafka
    zookeeper.connect=localhost:2181
config/server-2.properties:
    broker.id=2
    listeners=PLAINTEXT://:9093
    log.dir=/export/data/kafka
    zookeeper.connect=localhost:2181
broker.id是kafka集群上每一个节点的单独标识,不能重复;listeners可以理解为每一个节点上Kafka进程要监听的端口,使用默认的就行; log.dir是Kafka的log文件(记录消息的log file)存放目录; zookeeper.connect就是Zookeeper的URI地址和端口。
3. 配置完上面的配置文件后,只要分别在节点上输入下面命令启动Kafka进程就可以使用了
 > bin/kafka-server-start.sh config/server-1.properties &

> bin/kafka-server-start.sh config/server-2.properties &

Storm实时计算框架
接下来开始介绍本篇文章要使用的实时计算框架Storm。Strom是一个非常快的实时计算框架,至于快到什么程度呢?官网首页给出的数据是每一个Storm集群上的节点每一秒能处理一百万条数据。相比Hadoop的“Mapreduce”计算框架,Storm使用的是”Topology”;Mapreduce程序在计算完成后最终会停下来,而Topology则是会永远运行下去除非你显式地使用“kill -9 XXX”命令停掉它。和大多数的集群系统一样,Storm集群也存在着Master节点和Worker节点,在Master节点上运行的一个守护进程叫“Nimbus”,类似于Hadoop的“JobTracker”的功能,负责集群中计算程序的分发,任务的分发,监控任务和工作节点的运行情况等;Worker节点上运行的守护进程叫“Supervisor”,负责接收Nimbus分发的任务并运行,每一个Worker上都会运行着Topology程序的一部分,而一个Topology程序的运行就是由集群上多个Worker一起协同工作的。值得注意的是Nimubs和Supervisor之间的协调工作也是通过Zookeeper来管理的,Nimbus和Supervisor自己本身在集群上都是无状态的,它们的状态都保存在Zookeeper上,所以任何节点的宕机和动态扩容都不会影响整个集群的工作运行,并支持fast-fail机制。

Storm有一个很重要的对数据的抽象概念,叫做“Stream”,我们姑且称之为数据流,数据流Stream就是由之间没有任何关系的松散的一个一个的数据元组“tuples”所组成的序列。要在Storm上做实时计算,首先你得有一个计算程序,这就是“Topology”,一个Topology程序由“Spout”和“Bolt”共同组成。Storm就是通过Topology程序将数据流Stream通过可靠(ACK机制)的分布式计算生成我们的目标数据流Stream,就比如说把婚恋网站上当日注册的所有用户信息数据流Stream通过Topology程序计算出月收入上万年龄在30岁以下的新的用户信息流Stream。在我们的文章中,Spout就是实现了特定接口的Java类,它相当于数据源,用于产生数据或者从外部接收数据;而Bolt就是实现了Storm Bolt接口的Java类,用于消费从Spout发送出来的数据流并实现用户自定义的数据处理逻辑;对于复杂的数据处理,可以定义多个连续的Bolt去协同处理。最后在程序中通过Spout和Bolt生成Topology对象并提交到Storm集群上执行。

tuples是Storm的数据模型,,由值和其所对应的field所组成,比如说在Spout或Bolt中定义了发出的元组的field为:(name,age,gender),那么从这个Spout或Bolt中发出的数据流的每一个元组值就类似于(‘’咕噜大大”,27,”中性”)。在Storm中还有一个Stream Group的概念,它用来决定从Spout或或或Bolt组件中发出的tuples接下来应该传到哪一个组件中或者更准确地说在程序里设置某个组件应该接收来自哪一个组件的tuples; 并且在Storm中提供了多个用于数据流分组的机制,比如说shuffleGrouping,用来将当前组件产生的tuples随机分发到下一个组件中,或者 fieldsGrouping,根据tuples的field值来决定当前组件产生的tuples应该分发到哪一个组件中。

另一部分需要了解的就是Storm中tasks和workers的概念。每一个worker都是一个运行在物理机器上的JVM进程,每个worker中又运行着多个task线程,这些task线程可能是Spout任务也可能是Bolt任务,由Nimbus根据RoundRobin负载均衡策略来分配,而至于在整个Topology程序里要起几个Spout线程或Bolt线程,也就是tasks,由用户在程序中设置并发度来决定。

Storm集群的配置文件如下:
Storm的配置文件在项目的conf目录下,也就是:conf/storm.yaml
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# “License”); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at

# http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an “AS IS” BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

########### These MUST be filled in for a storm configuration
storm.zookeeper.servers:
  - “ymhHadoop”
  - “ymhHadoop2”
  - “ymhHadoop3”    

storm.local.dir: “/export/data/storm/workdir”

  nimbus.host: “ymhHadoop”

supervisor.slots.ports:
  -6700
  -6701
  -6702
  -6703 

  storm.zookeeper.servers自然就是用来配置我们熟悉的Zookeeper集群中各个节点的URI地址和端口的
storm.local.dir 是用来配置storm节点相关文件的存储目录的,每一个storm集群的节点在本地服务器上都要有一个目录存储少量的和该节点有关的一些信息。记得要开发这个目录的读写权限哦
nimbus.host 自然就是用来指定nimbus服务器的URI的
supervisor.slots.ports 这个是用来配置supervisor服务器启动的worker所监听的端口,每一个worker就是一个物理的JVM进程。

上面这些是基本配置,并且要严格按照上面的格式来,少一个空格都会报错。
接下来就是将配置文件拷贝到集群的各个机器上,然后在分别在nimbus和supervisor机器上通过$bin/storm nimbus 和 $bin/storm supervisor命令来启动集群上的机子。最后在nimbus上通过$bin/storm UI 命令可以启动Storm提供的UI界面,功能十分强大,可以监控集群上各个节点的运行状态,提交Topology任务,监控Topology任务的运行情况等。这个UI界面可以通过http://{nimbus host}:8080的地址访问到。

Redis数据库
Redis是一个基于内存的多种数据结构的存储工具,经常有人说Redis是一个基于key-value数据结构的缓存数据库,这种说法必然是不准确的,Key-Value只是其中的一种数据结构的实现,Redis支持Strings,hashes,lists,sets,sorted sets等多种常见的数据结构,并提供了功能强大的范围查询,以及提供了INCR,INCRBY,DECR,DECRBY等多种原子命令操作,保证在并发的环境下不会出现脏数据。虽然Redis是基于内存的数据库,但也提供了多种硬盘持久化策略,比如说RDB策略,用来将某个时间点的Redis的数据快照存储在硬盘中,或者是AOF策略,将每一个Redis操作命令都不可变的顺序记录在log文件中,恢复数据时就将log文件中的所有命令顺序执行一遍等等。Redis不光可以作为网站热点数据的缓存服务器,还可以用来做数据库,或者消息队列服务器的broker等。在本文中选择Redis作为订单分析结果的存储工具,一方面是其灵活的数据结构和强大的数据操作命令,另一方面就是在大数据的实时计算环境下,需要Redis这样的具备高速I/O的数据库。

在本文的例子中,作者使用Sorted Sets数据结构来存储各个商家的总订单销售额,Sorted Sets数据结构由Key, Score,element value 三部分组成,Set的数据结构保证同一个key中的元素值不会重复,而在Sorted Sets结构中是通过 Score来为元素值排序,这很自然地就能将各个商家的总订单销售额设置为Score,然后商家名称为element value,这样就能根据总订单销售额来为商家排序。在Storm程序中,我们通过Jedis API来调用Redis的
$ZINCRBY KEY INCREMENT MEMBER
的命令来统计商家总销售额, ZINCRBY是一个原子命令,能保证在Storm的并发计算的环境下,正确地增加某个商家的Score的值,也就是它们的订单总销售额。而对于两个商家同名这种情况应该在业务系统中去避免而不应该由我们的数据分析层来处理。最后提一个小trips,就是如果所有商家的Score都设置成相同的分数,那么Redis就会默认使用商家名的字母字典序来排序。

Kafka+Storm+Redis的整合
当数据被Flume拉取进Kafka消息系统中,我们就可以使用Storm来进行消费,Redis来对结果进行存储。Storm对Kafka有很好的兼容性,我们可以通过Kafka Spout来从Kafka中获取数据;在Bolt处理完数据后,通过Jedis API在程序中将数据存储在Redis数据库中。

下面就是Kafka Spout和创建Topology的程序代码:

BrokerHosts hosts = new ZkHosts(“ymhHadoop:2181,ymhHadoop2:2181,ymhHadoop3:2181”);
zkHosts是用来指定Zookeeper集群的节点的URI和端口,而Zookeeper集群是用来记录Spout对Kafka消息消费的offset位置

spoutConfig.scheme = new SchemeAsMultiScheme(new StringScheme());
主要是用来将Spout从Kafka拉取来的byte[]数组格式的数据转化为Storm的tuples

package com.guludada.ordersanalysis;

import java.util.UUID;

import backtype.storm.Config;
import backtype.storm.LocalCluster;
import backtype.storm.StormSubmitter;
import backtype.storm.generated.AlreadyAliveException;
import backtype.storm.generated.InvalidTopologyException;
import backtype.storm.spout.SchemeAsMultiScheme;
import backtype.storm.topology.TopologyBuilder;
import backtype.storm.tuple.Fields;
import storm.kafka.Broker;
import storm.kafka.BrokerHosts;
import storm.kafka.KafkaSpout;
import storm.kafka.SpoutConfig;
import storm.kafka.StaticHosts;
import storm.kafka.StringScheme;
import storm.kafka.ZkHosts;
import storm.kafka.trident.GlobalPartitionInformation;

public class ordersAnalysisTopology {

         private static String topicName = “ordersInfo”;
    private static String zkRoot = “/stormKafka/“+topicName;

         public static void main(String[] args) {

                 BrokerHosts hosts = new ZkHosts(“ymhHadoop:2181,ymhHadoop2:2181,ymhHadoop3:2181”);

        

        SpoutConfig spoutConfig = new SpoutConfig(hosts,topicName,zkRoot,UUID.randomUUID().toString());
        spoutConfig.scheme = new SchemeAsMultiScheme(new StringScheme());
        KafkaSpout kafkaSpout = new KafkaSpout(spoutConfig);

                 TopologyBuilder builder = new TopologyBuilder();        
        builder.setSpout(“kafkaSpout”,kafkaSpout);        
        builder.setBolt(“merchantsSalesBolt”, new merchantsSalesAnalysisBolt(), 2).shuffleGrouping(“kafkaSpout”);

        Config conf = new Config();
        conf.setDebug(true);

                 if(args != null && args.length > 0) {

            conf.setNumWorkers(1);
            try {

                StormSubmitter.submitTopologyWithProgressBar(args[0], conf, builder.createTopology());
            } catch (AlreadyAliveException e) {

                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (InvalidTopologyException e) {

                // TODO Auto-generated catch block
                e.printStackTrace();
            }

                     } else {

                         conf.setMaxSpoutPending(3);

                         LocalCluster cluster = new LocalCluster();
            cluster.submitTopology(“ordersAnalysis”, conf, builder.createTopology());

                                  }

    }
}

下面是Bolt程序,主要是用来处理从Kafka拉取到的订单日志信息, 并计算出所有商家的总订单收入,然后使用Jedis API将计算结果存入到Redis数据库中。

package com.guludada.domain;

import java.util.ArrayList;
import java.util.Date;

public class ordersBean {

    Date createTime = null;
    String number = “”;
    String paymentNumber = “”;
    Date paymentDate = null;
    String merchantName = “”;
    ArrayList skuGroup = null;
    float totalPrice = 0;
    float discount = 0;
    float paymentPrice = 0;

         public Date getCreateTime() {

        return createTime;
    }
    public void setCreateTime(Date createTime) {

        this.createTime = createTime;
    }
    public String getNumber() {

        return number;
    }
    public void setNumber(String number) {

        this.number = number;
    }
    public String getPaymentNumber() {

        return paymentNumber;
    }
    public void setPaymentNumber(String paymentNumber) {

        this.paymentNumber = paymentNumber;
    }
    public Date getPaymentDate() {

        return paymentDate;
    }
    public void setPaymentDate(Date paymentDate) {

        this.paymentDate = paymentDate;
    }
    public String getMerchantName() {

        return merchantName;
    }
    public void setMerchantName(String merchantName) {

        this.merchantName = merchantName;
    }
    public ArrayList getSkuGroup() {

        return skuGroup;
    }
    public void setSkuGroup(ArrayList skuGroup) {

        this.skuGroup = skuGroup;
    }
    public float getTotalPrice() {

        return totalPrice;
    }
    public void setTotalPrice(float totalPrice) {

        this.totalPrice = totalPrice;
    }
    public float getDiscount() {

        return discount;
    }
    public void setDiscount(float discount) {

        this.discount = discount;
    }
    public float getPaymentPrice() {

        return paymentPrice;
    }
    public void setPaymentPrice(float paymentPrice) {

        this.paymentPrice = paymentPrice;
    }

          }
本文例子中用不到skusbean,所以这里作者就没有写偷懒一下下
package com.guludada.domain;

public class skusBean {
      ………………
}

logInfoHandler用来过滤订单的日志信息,并保存到ordersBean和skusBean中,方便Bolt获取日志数据的各项属性进行处理
package com.guludada.common;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.guludada.domain.ordersBean;

public class logInfoHandler {

         SimpleDateFormat sdf_final = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”);

         public ordersBean getOrdersBean(String orderInfo) {

                 ordersBean order = new ordersBean();

                 //从日志信息中过滤出订单信息
        Pattern orderPattern = Pattern.compile(“orderNumber:.+”);
        Matcher orderMatcher = orderPattern.matcher(orderInfo);
        if(orderMatcher.find()) {

                         String orderInfoStr = orderMatcher.group(0);
            String[] orderInfoGroup = orderInfoStr.trim().split(“\\|”);

                         //获取订单号
            String orderNum = (orderInfoGroup[0].split(“:”))[1].trim();
            order.setNumber(orderNum);

                                     //获取创建时间
            String orderCreateTime = orderInfoGroup[1].trim().split(“ “)[1] + “ “ + orderInfoGroup[1].trim().split(“ “)[2];
            try {

                order.setCreateTime(sdf_final.parse(orderCreateTime));
            } catch (ParseException e) {

                // TODO Auto-generated catch block
                e.printStackTrace();
            }

                         //获取商家名称
            String merchantName = (orderInfoGroup[4].split(“:”))[1].trim();
            order.setMerchantName(merchantName);

                         //获取订单总额
            String orderPriceInfo = (orderInfoGroup[6].split(“price:”))[1].trim();
            String totalPrice = (orderPriceInfo.substring(2, orderPriceInfo.length()-3).trim().split(“ “))[1];
            order.setTotalPrice(Float.parseFloat(totalPrice));

                                     return order;

                                 } else {

            return order;
        }
    }
}

package com.guludada.ordersanalysis;

import java.util.Map;

import com.guludada.common.logInfoHandler;
import com.guludada.domain.ordersBean;

import backtype.storm.task.OutputCollector;
import backtype.storm.task.TopologyContext;
import backtype.storm.topology.OutputFieldsDeclarer;
import backtype.storm.topology.base.BaseRichBolt;
import backtype.storm.tuple.Tuple;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

public class merchantsSalesAnalysisBolt extends BaseRichBolt {

         private OutputCollector _collector;
    logInfoHandler loginfohandler;
    JedisPool pool;

    public void execute(Tuple tuple) {

        String orderInfo = tuple.getString(0);
        ordersBean order = loginfohandler.getOrdersBean(orderInfo);

                 //store the salesByMerchant infomation into Redis
        Jedis jedis = pool.getResource();
        jedis.zincrby(“orderAna:topSalesByMerchant”, order.getTotalPrice(), order.getMerchantName());
    }

    public void prepare(Map arg0, TopologyContext arg1, OutputCollector collector) {

        this._collector = collector;
        this.loginfohandler = new logInfoHandler();
        this.pool = new JedisPool(new JedisPoolConfig(), “ymhHadoop”,6379,2 * 60000,”12345”);

             }

    public void declareOutputFields(OutputFieldsDeclarer arg0) {

        // TODO Auto-generated method stub

             }

}

Topology项目的Maven配置文件

  4.0.0
  com.guludada
  Storm_OrdersAnalysis
  war
  0.0.1-SNAPSHOT
  Storm_OrdersAnalysis Maven Webapp
  http://maven.apache.org
 
   
        org.apache.storm
        storm-core
        0.9.6
        provided
    

    
        org.apache.storm
        storm-kafka
        0.9.6
   

   
        org.apache.kafka
        kafka_2.10
        0.9.0.1
           
               
                    org.apache.zookeeper
                    zookeeper
               

               
                    log4j
                    log4j
               

               
                    org.slf4j
                    slf4j-log4j12
               

           

   

   
        redis.clients
        jedis
        2.8.1
    
    
 

 
    Storm_OrdersAnalysis
   
        
            maven-assembly-plugin
            
                  
                    jar-with-dependencies
                

                
                  
                     com.guludada.ordersanalysis.ordersAnalysisTopology
                  

                

            

          

      

 

maven配置文件中配置了一个官方推荐的maven-assembly-plugin插件,用来帮助用户方便地打包Topology程序的。只需要进入到项目的根路径,然后运行
$mvn assembly:assembly
命令就可以打包好Topology的jar包了。

最后我带大家梳理一下整个项目的部署流程
1.  启动Zookeeper
2. 启动Kafka
3. 启动Flume将程序拉取到Kafka中
4. 启动Storm集群
5. 启动Redis服务端  通过命令
$ src/redis-server
6. 提交打包好的Topology程序到Storm集群中通过Storm UI 或者命令$storm jar path/to/allmycode.jar org.me.MyTopology arg1 arg2 arg3
7. 启动Redis的CLI客户端查看结果通过命令
$ src/redis-cli –raw
$  zrange key 0 -1 withscores

如下图:

Troubleshooting
在使用maven同时导入storm-core, storm-kaka和kafka的依赖包的时候可能会出现jar包冲突导致无法初始化Log4jLoggerFactory,并无法启动Storm程序.解决方法也很简单,按照红字提示,把多余的jar包移除就行了,通过在maven的pom文件中kafka的依赖设置部分加入下面的设置org.slf4jslf4j-log4j12
第一次执行Storm建立Topology时,作者遇到了一个十分低级的问题,就是发现明明Kafka的topic里有数据,可是Storm程序怎么都无法读取到数据,后来才从下面的文章中明白了问题的所在 http://m.blog.csdn.net/article/details?id=18615761  原因就在于Topology第一次启动前还没有在zookeeper中的zkRoot创建offset信息,Storm取不到offset信息就会使用默认的offset,也就是log文件中从最后一个元素开始读取信息,所以之前在kafka中的数据都无法读出来。Storm启动后,再往broker中写数据,这些后写的数据就能正确被Storm处理。                                  
当Storm的topology传到Nimbus的时候,或者说你的Storm程序刚开始启动的时候可能会报关于JedisPool是一个无法序列化的对象而导致的错误:java.lang.RuntimeException:java.io.NotSerializableException: redis.clients.jedis.JedisPool 解决方案就是将Bolt类中外部的JedisPool初始化代码放入Bolt的prepare()方法中,如本文的代码示例所示
在Storm启动并开始连接Redis的时候,会报出连接被拒绝,因为Redis运行在protect mode模式下的错误。这是因为Storm程序是远程连接Redis的服务器端,如果Redis服务器端没有设置密码的话是拒绝远程连接的。解决方法也十分简单,关闭protect mode模式(强烈不推荐),或者使用下面命令为Redis设置密码就可以了$config set requirepass 123
向Storm提交Topology以后, Supervisor端会一直报“Kill XXXX No Such process”的错误,多数原因是提交的topology没有正确被执行,而Storm的日记中不会显示topology程序里的错误。解决方法就是启动Storm UI, 通过这个Storm自带的UI界面查看topology的运行情况,并且程序中的错误也会在UI界面中显示出来,能方便地查看topology程序的错误。

    6.kafka使用的时候的小问题:
        当在一台机子上启动kafka producer客户端的时候,是无法在同一台机子上继续启动kafka的consumer客户端的,因为这两个进程可能占用的同一个端口,需要在另外一台机子上启动kafka consumer程序,这样就能看见正确的结果了

最后,感谢所有耐心看完这篇文章的人,楼主也深感自己的技术水平和语言表达还有很多需要提高的地方,希望能和大家一起交流学习共同进步,欢迎大家留下宝贵的意见和评论!还有再最后吐槽一下,CSDN的文章编辑器在我的MAC系统的火狐浏览器下十分十分十分十分难用,字体格式等根本不受控制,各种莫名其妙的BUG…………

-—————————————————–

原文:https://blog.csdn.net/ymh198816/article/details/51998085