Misc Notes

Chromium学习笔记:程序启动入口分析(Windows)

以下笔记内容均为Windows版本。

本篇笔记跟踪记录了Chromium的启动过程,主要关注 Browser 进程和 Renderer 进程。根据 Chromium 项目的分层设计,我们把 Content API 称作为 Content 层,而把调用 Content API 实现浏览器程序的部分称作为 Embedder 层。在项目中,Embedder 层有 chromecontent_shell 等多种实现。

1、main() 函数

Chromium的main函数在 chrome\app\chrome_exe_main_win.cc,具体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// chrome\app\chrome_exe_main_win.cc

#if !defined(WIN_CONSOLE_APP)
int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE prev, wchar_t*, int) {
#else
int main() {
HINSTANCE instance = GetModuleHandle(nullptr);
#endif
install_static::InitializeFromPrimaryModule();
SignalInitializeCrashReporting();

......

// Load and launch the chrome dll. *Everything* happens inside.
VLOG(1) << "About to load main DLL.";
MainDllLoader* loader = MakeMainDllLoader();
int rc = loader->Launch(instance, exe_entry_point_ticks);
loader->RelaunchChromeBrowserWithNewCommandLineIfNeeded();
delete loader;
return rc;
}

在main函数中,最重要的一步,就是 int rc = loader->Launch(instance, exe_entry_point_ticks); 载入 chrome.dll运行

2、载入 chrome.dll

在这里首先调用了 MakeMainDllLoader() 函数,这是一个静态函数,在chrome\app\main_dll_loader.cc 中,内容如下:

1
2
3
4
5
6
7
8
9
// chrome\app\main_dll_loader.cc

MainDllLoader* MakeMainDllLoader() {
#if defined(GOOGLE_CHROME_BUILD)
return new ChromeDllLoader();
#else
return new ChromiumDllLoader();
#endif
}

函数创建并返回一个 ChromiumDllLoader,紧接着再调用它的 Launch 函数,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// chrome\app\main_dll_loader.cc

int MainDllLoader::Launch(HINSTANCE instance,
base::TimeTicks exe_entry_point_ticks) {
const base::CommandLine& cmd_line = *base::CommandLine::ForCurrentProcess();
process_type_ = cmd_line.GetSwitchValueASCII(switches::kProcessType);

......

dll_ = Load(&file);
if (!dll_)
return chrome::RESULT_CODE_MISSING_DATA;

OnBeforeLaunch(cmd_line, process_type_, file);
DLL_MAIN chrome_main =
reinterpret_cast<DLL_MAIN>(::GetProcAddress(dll_, "ChromeMain"));
int rc = chrome_main(instance, &sandbox_info,
exe_entry_point_ticks.ToInternalValue());
OnBeforeExit(file);
return rc;
}

这里完成了 chrome.dll 的载入,并且执行里面的 ChromeMain 函数。

3、ChromeMain() 函数

ChromeMain 函数负责 Embedder 层的实现类创建,并传递给 Content 层,定义在 chrome\app\chrome_main.cc 中,内容如下:

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
// chrome\app\chrome_main.cc

extern "C" {
DLLEXPORT int __cdecl ChromeMain(HINSTANCE instance,
sandbox::SandboxInterfaceInfo* sandbox_info,
int64_t exe_entry_point_ticks);
}

......

#if defined(OS_WIN)
DLLEXPORT int __cdecl ChromeMain(HINSTANCE instance,
sandbox::SandboxInterfaceInfo* sandbox_info,
int64_t exe_entry_point_ticks) {
#elif defined(OS_POSIX)
int ChromeMain(int argc, const char** argv) {
int64_t exe_entry_point_ticks = 0;
#endif

#if defined(OS_WIN)
install_static::InitializeFromPrimaryModule();
#endif

ChromeMainDelegate chrome_main_delegate(
base::TimeTicks::FromInternalValue(exe_entry_point_ticks));
content::ContentMainParams params(&chrome_main_delegate);

......

int rv = content::ContentMain(params);

return rv;
}

在ChromeMain中,最终执行到了 content::ContentMain 这个函数。

4、content::ContentMain() 函数

代码执行到这里,进入了 Content 层,并且传入参数 content::ContentMainParams 类型的参数 params,它是由 Embedder 层传递过来的重要参数,里面包含了 Embedder 层的具体实现信息,此结构体在 content\public\app\content_main.h 中定义如下:

1
2
3
4
5
6
7
8
9
// content\public\app\content_main.h

struct ContentMainParams {
explicit ContentMainParams(ContentMainDelegate* delegate)
: delegate(delegate) {}

ContentMainDelegate* delegate;

......

其中有一个重要的成员变量 delegate,其类型为 content::ContentMainDelegate,它在 content\public\app\content_main_delegate.cc 中定义如下:

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
// content\public\app\content_main_delegate.cc

class CONTENT_EXPORT ContentMainDelegate {
public:
virtual ~ContentMainDelegate() {}

virtual bool BasicStartupComplete(int* exit_code);
virtual void PreSandboxStartup() {}
virtual void SandboxInitialized(const std::string& process_type) {}
virtual int RunProcess(
const std::string& process_type,
const MainFunctionParams& main_function_params);
virtual void ProcessExiting(const std::string& process_type) {}

......

virtual void PreCreateMainMessageLoop() {}

......

protected:
friend class ContentClientInitializer;

virtual ContentBrowserClient* CreateContentBrowserClient();
virtual ContentGpuClient* CreateContentGpuClient();
virtual ContentRendererClient* CreateContentRendererClient();
virtual ContentUtilityClient* CreateContentUtilityClient();
};

可以看到,这里定义了一系列与启动相关的操作,并且通过几个 CreateXXX 的函数,获取 ContentBrowserClientContentRendererClient 等接口具体的实现,这也是 content API 的巧妙设计,通过这种方式,将浏览器的实现放入了 content 中。

继续往下看,content::ContentMain() 中调用了 content\app\content_main.cc 中的 service_manager::Main()

1
2
3
4
5
6
7
8
9
10
11
// content\app\content_main.cc

int ContentMain(const ContentMainParams& params) {
ContentServiceManagerMainDelegate delegate(params);
service_manager::MainParams main_params(&delegate);
#if !defined(OS_WIN) && !defined(OS_ANDROID)
main_params.argc = params.argc;
main_params.argv = params.argv;
#endif
return service_manager::Main(main_params);
}

在这里,使用一个 content::ContentServiceManagerMainDelegate 对象来构建了 main_params,并传入了 service_manager::Main()

5、service_manager::Main 函数

service_manager::Main 函数位于 services\service_manager\embedder\main.cc,接收一个 MainParams 类型的参数,具体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
// services\service_manager\embedder\main.cc

int Main(const MainParams& params) {
MainDelegate* delegate = params.delegate;

......

ProcessType process_type = delegate->OverrideProcessType();

......
// A flag to indicate whether Main() has been called before. On Android, we
// may re-run Main() without restarting the browser process. This flag
// prevents initializing things more than once.
static bool is_initialized = false;
#if !defined(OS_ANDROID)
DCHECK(!is_initialized);
#endif
if (!is_initialized) {
is_initialized = true;

......

#if defined(OS_WIN)
base::win::RegisterInvalidParamHandler();
ui::win::CreateATLModuleIfNeeded();
#endif // defined(OS_WIN)

......

base::CommandLine::Init(argc, argv);

......

const auto& command_line = *base::CommandLine::ForCurrentProcess();

#if defined(OS_WIN)
base::win::SetupCRT(command_line);
#endif

MainDelegate::InitializeParams init_params;

......
mojo::core::Init(mojo_config);

......

exit_code = delegate->Initialize(init_params);

......

}

const auto& command_line = *base::CommandLine::ForCurrentProcess();
if (process_type == ProcessType::kDefault) {
std::string type_switch =
command_line.GetSwitchValueASCII(switches::kProcessType);
if (type_switch == switches::kProcessTypeServiceManager) {
process_type = ProcessType::kServiceManager;
} else if (type_switch == switches::kProcessTypeService) {
process_type = ProcessType::kService;
} else {
process_type = ProcessType::kEmbedder;
}
}
switch (process_type) {
case ProcessType::kDefault:
NOTREACHED();
break;

case ProcessType::kServiceManager:
exit_code = RunServiceManager(delegate);
break;

case ProcessType::kService:
CommonSubprocessInit();
exit_code = RunService(delegate);
break;

case ProcessType::kEmbedder:
if (delegate->IsEmbedderSubprocess())
CommonSubprocessInit();
exit_code = delegate->RunEmbedderProcess();
break;
}

......

if (process_type == ProcessType::kEmbedder)
delegate->ShutDownEmbedderProcess();

return exit_code;
}

这里截取的代码比较长,也非常重要,我们主要关注这四个部分:

  • 根据传入的 delegatecommand_line 决定进程的类型
  • 运行环境的初始化,比如 CreateATLModuleIfNeededSetupCRT 并用 is_initialized 来防止重复执行
  • 通过传入的 delegate 进行程序的初始化操作,delegate->Initialize(init_params)
  • 根据进程类型启动相应的工作

这里的 delegate 类型为 service_manager::MainDelegate*,是在 services/service_manager/embedder/main_delegate.h 中定义的抽象类,在这里我们主要关注它的 InitializeRunEmbedderProcessShutDownEmbedderProcess,其中 Initialize 为被声明为纯虚函数,RunEmbedderProcessShutDownEmbedderProcess 又是什么都不做的,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// services/service_manager/embedder/main_delegate.h

class COMPONENT_EXPORT(SERVICE_MANAGER_EMBEDDER) MainDelegate {
public:
// Perform early process initialization. Returns -1 if successful, or the exit
// code with which the process should be terminated due to initialization
// failure.
virtual int Initialize(const InitializeParams& params) = 0;

......

// Runs the embedder's own main process logic. Called exactly once after a
// successful call to Initialize(), and only if the Service Manager core does
// not know what to do otherwise -- i.e., if it is not starting a new Service
// Manager instance or launching an embedded service.
//
// Returns the exit code to use when terminating the process after
// RunEmbedderProcess() (and then ShutDown()) completes.
virtual int RunEmbedderProcess();

......

// Called just before process exit if RunEmbedderProcess() was called.
virtual void ShutDownEmbedderProcess();
1
2
3
4
5
6
7
8
9
// services/service_manager/embedder/main_delegate.cc

int MainDelegate::RunEmbedderProcess() {
return 0;
}

...

void MainDelegate::ShutDownEmbedderProcess() {}

回到 service_manager::Main(),我们看到第一句 MainDelegate* delegate = params.delegate; 中的 params.delegate 就是前面在 content::ContentMain 中构建 main_params 所使用的 content::ContentServiceManagerMainDelegate 对象,因此,上述的三个函数 InitializeRunEmbedderProcessShutDownEmbedderProcess 是由 ContentServiceManagerMainDelegate 来最终实现的,来看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// content\app\content_service_manager_main_delegate.cc

int ContentServiceManagerMainDelegate::Initialize(
const InitializeParams& params) {

......

return content_main_runner_->Initialize(content_main_params_);
}

......

int ContentServiceManagerMainDelegate::RunEmbedderProcess() {
return content_main_runner_->Run(start_service_manager_only_);
}

......

void ContentServiceManagerMainDelegate::ShutDownEmbedderProcess() {
#if !defined(OS_ANDROID)
content_main_runner_->Shutdown();
#endif
}

在这三个函数的定义中,都使用了 content_main_runner_ 这个成员变量来具体执行,它的定义为 std::unique_ptr<ContentMainRunnerImpl>

6、整个程序的Runner,content::ContentMainRunnerImpl

这个 content::ContentMainRunnerImplcontent::ContentMainRunner 接口的一个实现,先来看接口的声明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// content\app\content_main_runner_impl.h

class CONTENT_EXPORT ContentMainRunner {
public:
virtual ~ContentMainRunner() {}

// Create a new ContentMainRunner object.
static ContentMainRunner* Create();

// Initialize all necessary content state.
virtual int Initialize(const ContentMainParams& params) = 0;

// Perform the default run logic.
virtual int Run(bool start_service_manager_only) = 0;

// Shut down the content state.
virtual void Shutdown() = 0;
};

再来看实现类的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// content\app\content_main_runner_impl.h

class ContentMainRunnerImpl : public ContentMainRunner {
public:
static ContentMainRunnerImpl* Create();

ContentMainRunnerImpl();
~ContentMainRunnerImpl() override;

int TerminateForFatalInitializationError();

// ContentMainRunner:
int Initialize(const ContentMainParams& params) override;
int Run(bool start_service_manager_only) override;
void Shutdown() override;

......

}

7、ContentMainRunner::Initialize() 函数

先来看 Initialize 函数:

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
// content\app\content_main_runner_impl.cc

int ContentMainRunnerImpl::Initialize(const ContentMainParams& params) {
ui_task_ = params.ui_task;
created_main_parts_closure_ = params.created_main_parts_closure;

#if defined(OS_WIN)
sandbox_info_ = *params.sandbox_info;
#else // !OS_WIN

......

is_initialized_ = true;
delegate_ = params.delegate;

......

int exit_code = 0;
if (delegate_->BasicStartupComplete(&exit_code))
return exit_code;
completed_basic_startup_ = true;

......

delegate_->PreSandboxStartup();
#if defined(OS_WIN)
if (!InitializeSandbox(
service_manager::SandboxTypeFromCommandLine(command_line),
params.sandbox_info))
return TerminateForFatalInitializationError();
#elif defined(OS_MACOSX)

......

#endif

delegate_->SandboxInitialized(process_type);

......

// Return -1 to indicate no early termination.
return -1;
}

大致看一下,在这个 Initialize 中,主要是根据 command_line 启动了相应的 sandbox service,并在启动前后都触发了 delegate_->PreSandboxStartup()delegate_->SandboxInitialized(process_type),这个 delegate_ 来自于传入的 content::ContentMainParams 结构体,这个结构体是在 chrome_main.cc 中调用 content::ContentMain(params) 时所创建,所以这个 delegate_ 正是前面所提到的巧妙设计中,继承自 content::ContentMainDelegateChromeMainDelegate 对象,通过这一系列的调用,content 层就把创建 sandbox service 前后的事件触发了出来,具体实现者只要在 ChromeMainDelegate 中填充这两个时间点要做的事即可。

8、进程入口,ContentMainRunner::Run() 函数

再来看 Run 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// // content\app\content_main_runner_impl.cc

int ContentMainRunnerImpl::Run(bool start_service_manager_only) {

......

const base::CommandLine& command_line =
*base::CommandLine::ForCurrentProcess();
std::string process_type =
command_line.GetSwitchValueASCII(switches::kProcessType);

......

MainFunctionParams main_params(command_line);
main_params.ui_task = ui_task_;
main_params.created_main_parts_closure = created_main_parts_closure_;

......

if (process_type.empty())
return RunServiceManager(main_params, start_service_manager_only);

return RunOtherNamedProcessTypeMain(process_type, main_params, delegate_);
}

此处先判断 process_type 是否为空,为空则代表当前执行的是默认进程(一般情况下为 Browser 进程),则调用 RunServiceManager(),否则调用 RunOtherNamedProcessTypeMain 根据process_type 来执行相应的进程。先来看 RunServiceManager

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
// content\app\content_main_runner_impl.cc

int ContentMainRunnerImpl::RunServiceManager(MainFunctionParams& main_params,
bool start_service_manager_only) {

......

if (!service_manager_context_) {

......

delegate_->PreCreateMainMessageLoop();

......

delegate_->PostEarlyInitialization(main_params.ui_task != nullptr);

......

}

if (should_start_service_manager_only)
return -1;

is_browser_main_loop_started_ = true;
startup_data_ = std::make_unique<StartupDataImpl>();
startup_data_->thread = std::move(service_manager_thread_);
startup_data_->service_manager_context = service_manager_context_.get();
main_params.startup_data = startup_data_.get();
return RunBrowserProcessMain(main_params, delegate_);
}

同样,这里通过 delegate_ 做了一些操作之后,最后调用了 RunBrowserProcessMain() 函数,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// content\app\content_main_runner_impl.cc

int RunBrowserProcessMain(const MainFunctionParams& main_function_params,
ContentMainDelegate* delegate) {
int exit_code = delegate->RunProcess("", main_function_params);
#if defined(OS_ANDROID)
// In Android's browser process, the negative exit code doesn't mean the
// default behavior should be used as the UI message loop is managed by
// the Java and the browser process's default behavior is always
// overridden.
return exit_code;
#else
if (exit_code >= 0)
return exit_code;
return BrowserMain(main_function_params);
#endif
}

非常简单明了,首先通过 delegate->RunProcess 把执行默认进程的优先权交由 Embedder 层,如果 Embedder 层成功执行了进程并最终返回了成功标志(exit_code >= 0),那么就退出函数;如果 Embedder 层对默认进程没有定义,就继续执行 content::BrowserMain,由此,Browser 进程开始执行。

再来看 RunOtherNamedProcessTypeMain 函数:

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
// content\app\content_main_runner_impl.cc

int RunOtherNamedProcessTypeMain(const std::string& process_type,
const MainFunctionParams& main_function_params,
ContentMainDelegate* delegate) {
static const MainFunction kMainFunctions[] = {

......

{switches::kUtilityProcess, UtilityMain},
{switches::kRendererProcess, RendererMain},
{switches::kGpuProcess, GpuMain},
};

for (size_t i = 0; i < base::size(kMainFunctions); ++i) {
if (process_type == kMainFunctions[i].name) {
int exit_code = delegate->RunProcess(process_type, main_function_params);
if (exit_code >= 0)
return exit_code;
return kMainFunctions[i].function(main_function_params);
}
}

......

// If it's a process we don't know about, the embedder should know.
return delegate->RunProcess(process_type, main_function_params);
}

先建立了一个进程类型和入口函数指针的对应数组,再根据进程类型去具体执行,执行的过程与 Browser 进程一样,先通过 delegate->RunProcess 交由 Embedder 层处理,如果未处理再调用默认的进程入口函数,可以看到分别提供了 UtilityMainRendererMainGpuMain 这三个进程的入口,其中 RendererMain 则是我们关注的 Renderer 进程的入口函数,Renderer 进程从此处开始执行。最后一句,如果进程类型不在以上范围内,则交由 Embedder 去处理。

9、程序结束

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
void ContentMainRunnerImpl::Shutdown() {
DCHECK(is_initialized_);
DCHECK(!is_shutdown_);

if (completed_basic_startup_) {
const base::CommandLine& command_line =
*base::CommandLine::ForCurrentProcess();
std::string process_type =
command_line.GetSwitchValueASCII(switches::kProcessType);

delegate_->ProcessExiting(process_type);
}

#if !defined(CHROME_MULTIPLE_DLL_CHILD)
// The BrowserTaskExecutor needs to be destroyed before |exit_manager_|.
BrowserTaskExecutor::Shutdown();
#endif // !defined(CHROME_MULTIPLE_DLL_CHILD)

#if defined(OS_WIN)
#ifdef _CRTDBG_MAP_ALLOC
_CrtDumpMemoryLeaks();
#endif // _CRTDBG_MAP_ALLOC
#endif // OS_WIN

exit_manager_.reset(nullptr);

delegate_ = nullptr;
is_shutdown_ = true;
}

首先通过 delegate_->ProcessExiting(process_type) 通知 Embedder 层处理,然后做了一些善后释放的工作,最后将 is_shutdown_ 标记置为 true

10、总结

前面分析了这么多,其实结合类图来看一下还是很简单明了的,主要起到作用的就是图中标红的三个,service_manager::Main 通过 content::ContentServiceManagerMainDelegate 的实例调用了 content::ContentMainRunnerImpl 实例中的 Initialize()Run()Shutdown() 函数,而在这个Runner中,又通过 content::ContentMainDelegate 接口指针调用到了由 Embedder 层创建的 ChromeMainDelegate 实例中的函数,由此完成了程序的启动以及 Content 层对 Embedder 的交互。

CentOS 6.7 安装 Oracle 11g2

一、准备工作

1 配hosts文件

1
$ sudo vi /etc/hosts

添加以下内容:

1
192.168.1.58 oracle

2 关闭SELINUX

1
2
$ sudo setenforce 0
$ sudo vi /etc/selinux/config

修改文件内容:

1
SELINUX=disabled

3 关闭防火墙

1
$ sudo service iptables stop

4 安装依赖包

1
$ sudo yum install binutils compat-libstdc++ cpp elfutils elfutils-libelf elfutils-libelf-devel elfutils-libs gcc gcc-c++ glibc glibc-common glibc-devel glibc-headers ksh libaio libaio-devel libgcc libgomp libstdc++ libstdc++-devel make mpfr nss-softokn-freebl sysstat tzdata-java unixODBC unixODBC-devel cloog-ppl kernel-headers libtool-ltdl ppl

5 创建用户、组

没有建特别的用户组,直接用了默认的,并把oracle加入sudo

1
2
$ sudo adduser oracle
$ sudo passwd oracle

6 创建安装目录

把oracle安装在 /opt/oracle 下面

1
2
3
4
5
6
7
$ sudo mkdir -p /opt/oracle
$ sudo mkdir -p /opt/oracle/product/112010/db_1
$ sudo mkdir /opt/oracle/oradata
$ sudo mkdir /opt/oracle/inventory
$ sudo mkdir /opt/oracle/flash_recovery_area
$ sudo chown -R oracle:oracle /opt/oracle
$ sudo chmod -R 775 /opt/oracle

7 修改内核参数

1
$ sudo vi /etc/sysctl.conf

修改文件内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
fs.aio-max-nr = 1048576  # 指的是可以同时拥有异步I/O请求的数目,Oracle推荐的值为1048576(1024×1024),也就是1024Kb个。
fs.file-max = 6815744 # 系统所有进程一共可以打开的文件数量 64bytes cat /proc/sys/fs/file-nr 在'高负荷'的情况,看看有多少文件描述符都在使用。
kernel.shmmni = 4096 # 整个系统共享内存段的最大数量。
kernel.sem = 250 32000 100 128
# 1.同一类信号的最多数量(semmsl)。
# 2.系统中信号的最多数目,=semmni*semmsl (semmns)。
# 3.每个semop系统调用所包含的最大的操作数(能调用的信号量的最多次数) (semopm)。
# 4.系统中信号类型的数目的最大值,一个信号量标识符代表一个类型(semmni)。
net.ipv4.ip_local_port_range = 32768 61000 # 表示TCP/UDP协议打开的本地端口号。 默认设置:1024 4999 建议设置:32768 61000。
net.core.rmem_default = 262144 # 设置接收socket的缺省缓存大小(字节)。
net.core.rmem_max = 4194304 # 设置接收socket的最大缓存大小(字节)
net.core.wmem_default = 212992 # 设置发送的socket缺省缓存大小(字节)
net.core.wmem_max = 1048576 # 设置发送的socket最大缓存大小(字节)

保存后执行:

1
$ sudo sysctl -p

8 修改用户限制文件(配置项未理解)

1
$ sudo vi /etc/security/limits.conf

添加以下内容:

1
2
3
4
5
oracle          soft    nproc   2047
oracle hard nproc 16384
oracle soft nofile 1024
oracle hard nofile 65536
oracle soft stack 10240

9 关联设置(暂时不知道干嘛用的)

1
$ sudo vi /etc/pam.d/login

添加以下内容:

1
2
session required  /lib64/security/pam_limits.so
session required pam_limits.so

10 修改/etc/profile(暂时不知道干嘛用的)

1
$ sudo vi /etc/profile

添加以下内容:

1
2
3
4
5
6
7
8
if [ $USER = "oracle" ]; then
if [ $SHELL = "/bin/ksh" ]; then
ulimit -p 16384
ulimit -n 65536
else
ulimit -u 16384 -n 65536
fi
fi

切换至root账号下使之生效:

1
2
$ su root
$ source /etc/profile

11 修改oracle用户环境变量

1
2
$ cd
$ vi .bash_profile

添加以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# for oracle
export ORACLE_BASE=/opt/oracle;
export ORACLE_HOME=/opt/oracle/product/112010/db_1
export ORACLE_SID=orcl;
export PATH=$PATH:$HOME/bin:$ORACLE_HOME/bin
export LD_LIBRARY_PATH=$ORACLE_HOME/lib:/usr/lib
if [ $USER = "oracle" ]; then
if [ $SHELL = "/bin/ksh" ]; then
ulimit -p 16384
ulimit -n 65536
else
ulimit -u 16384 -n 65536
fi
umask 022
fi

使之生效:

1
$ source .bash_profile

此时若命令执行失败,提示:-bash: ulimit: open files: cannot modify limit: Operation not permitted,是因为ssh登录的缘故,需要修改sshd配置如下:

1
$ sudo vi /etc/ssh/sshd_config

把UseLogin改为yes,重启ssh:

1
$ sudo service sshd restart

重点,这里重启完sshd之后,一定要exit后重新用ssh连接,否则依然会报错

二、正式开始安装

1 准备安装文件

把解压后的database文件夹放到 /opt/oralce/

2 编辑oracle数据库安装应答文件

修改db_install.rsp

1
$ vi db_install.rsp

修改文件内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
oracle.install.option=INSTALL_DB_SWONLY  # 29行 安装类型
ORACLE_HOSTNAME=chances # 37行 主机名称
UNIX_GROUP_NAME=oracle # 42行 安装组
INVENTORY_LOCATION=/opt/oracle/inventory # 47行 INVENTORY目录
SELECTED_LANGUAGES=en,zh_CN # 78行 选择语言,可能因为linux是en的,所以这里一定要加上en
ORACLE_HOME=/opt/oracle/product/112010/db_1 # 83行 oracle_home
ORACLE_BASE=/opt/oracle # 88行 oracle_base
oracle.install.db.InstallEdition=EE # 99行 oracle版本
oracle.install.db.DBA_GROUP=oracle # 142行 dba用户组
oracle.install.db.OPER_GROUP=oracle # 147行 oper用户组
oracle.install.db.config.starterdb.type=GENERAL_PURPOSE # 160行 数据库类型
oracle.install.db.config.starterdb.globalDBName=orcl # 165行 globalDBName
oracle.install.db.config.starterdb.SID=orcl # 170行 SID
oracle.install.db.config.starterdb.memoryLimit=800 # 200行 自动管理内存的最小内存(M)
oracle.install.db.config.starterdb.password.ALL=xxxx # 233行 设定所有数据库用户使用同一个密码
DECLINE_SECURITY_UPDATES=true # 385行 设置安全更新

3 开始安装

用oracle账号进入 /opt/oracle/database/

1
2
3
$ su oracle
$ cd /opt/oracle/database/
$ ./runInstaller -silent -responseFile /opt/oracle/database/response/db_install.rsp -ignorePrereq

在日志里看到这个就成功了:

1
2
3
4
5
6
7
/opt/oracle/inventory/orainstRoot.sh
/opt/oracle/product/112010/db_1/root.sh
To execute the configuration scripts:
1. Open a terminal window
2. Log in as "root"
3. Run the scripts
4. Return to this window and hit "Enter" key to continue

用root用户登录按提示执行脚本:

1
2
$ /opt/oracle/inventory/orainstRoot.sh
$ /opt/oracle/product/112010/db_1/root.sh

4 配置监听

1
$ vi /opt/oracle/database/response/netca.rsp

检查这些参数:

1
2
3
4
5
INSTALL_TYPE=""custom""  #安装的类型
LISTENER_NUMBER=1 #监听器数量
LISTENER_NAMES={"LISTENER"} #监听器的名称列表
LISTENER_PROTOCOLS={"TCP;1521"} #监听器使用的通讯协议列表
LISTENER_START=""LISTENER"" #监听器启动的名称

保存后运行:

1
$ netca -silent -responseFile /opt/oracle/database/response/netca.rsp

三、添加数据库实例

1 检查配置

1
2
3
4
5
6
7
8
9
10
11
12
13
RESPONSEFILE_VERSION = "11.2.0"  #不能更改
OPERATION_TYPE = "createDatabase"
GDBNAME = "orcl" #数据库的名字
SID = "ORCL" #对应的实例名字
TEMPLATENAME = "General_Purpose.dbc" #建库用的模板文件
SYSPASSWORD = "oracle" #SYS管理员密码
SYSTEMPASSWORD = "sys" #SYSTEM管理员密码
SYSMANPASSWORD = "sys"
DBSNMPPASSWORD = "sys"
DATAFILEDESTINATION = /opt/oracle/oradata #数据文件存放目录
RECOVERYAREADESTINATION = /opt/oracle/flash_recovery_area #恢复数据存放目录
CHARACTERSET = "ZHS16GBK" #字符集,重要!!!建库后一般不能更改,所以建库前要确定清楚。
TOTALMEMORY = "10240" #10240MB,物理内存10G。

2 安装实例

进入oracle的bin目录运行:

1
$ dbca -silent -responseFile /opt/oracle/database/response/dbca.rsp

查看实例进程:

1
$ ps -ef | grep ora_ | grep -v grep

查看监听状态:

1
$ lsnrctl status

修改以下几个文件:

1
$ vi /opt/oracle/product/112010/db_1/bin/dbstart
1
ORACLE_HOME_LISTNER=$ORACLE_HOME
1
$ vi /opt/oracle/product/112010/db_1/bin/dbshut
1
ORACLE_HOME_LISTNER=$ORACLE_HOME
1
$ vi /etc/oratab
1
orcl:/opt/oracle/product/112010/db_1:Y

使用 dbshut、dbstart 可以测试,查看监听状态

四、结束

打开selinux

1
$ sudo vi /etc/selinux/config

ubuntu相关笔记

挂载硬盘

1
2
3
$ sudo mkfs.ext4  /dev/sdb
$ sudo blkid
$ sudo vi /etc/fstab

修改文件内容:

1
UUID=a450131d-8c83-4092-a028-887b7f6495b5 /mnt/xxx ext4 defaults 0 1
1
2
$ sudo mount -a
$ sudo chown -R xxx:xxx /mnt/xxx

18.04配置网络

不再使用 /etc/network/interfaces

修改 /etc/netplan/xxxxx.yaml

1
2
3
4
5
6
7
8
9
network:
ethernets:
ens33:
dhcp4: no
addresses: [192.168.1.30/24]
gateway4: 192.168.1.1
nameservers:
addresses: [114.114.114.114,8.8.8.8]
version: 2

改完后 sudo netplay apply

禁用 USB-Storage

  1. 修改下列文件:
    sudo vi /etc/modprobe.d/blacklist.conf
    在文件头部添加以下内容
    1
    2
    blacklist uas
    blacklist usb-storage
  2. 修改下列文件:
    sudo vi /etc/rc.local
    exit 0 上方添加
    1
    2
    modprobe -r uas
    modprobe -r usb-storage
  3. 重启
    sudo reboot

安装签名

1. 安装用到的软件包

1
sudo apt install debsigs

2. 生成GPG密钥

在GUI的终端窗口里输入以下命令

1
gpg --gen-key

三个选择都按默认,最后一个选y

1
2
3
4
Real name: AIOYTJ
Email address: manage@aioytj.com
Comment: AIO
Change (N)ame, (C)omment, (E)mail or (O)key/(Q)uit? O

Passphrase 输入密码,共两次,后面对安装名签名时会使用到,此时会出现

1
Not enough random bytes available.

这时候需要随便做一些操作,比如打开文件夹,打开某个程序输入点东西再关掉,时不时地切回终端窗口看一下,直到出现

1
2
3
4
5
6
7
8
9
10
gpg: key D189DD93 marked as ultimately trusted
public and secret key created and signed.

gpg: checking the trustdb
gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
gpg: depth: 0 valid: 1 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 1u
pub 2048R/D189DD93 2021-06-10
Key fingerprint = E3BD 6698 7C40 E362 741C E398 96F4 3D0F D189 DD93
uid AIOYTJ (AIO) <manage@aioytj.com>
sub 2048R/C1F4D81B 2021-06-10

这个时候运行

1
gpg --list-secret-keys

可以看到已经生成的gpg key

1
gpg --list-key --keyid-format LONG

输出如下

1
2
3
4
5
/home/yangxc/.gnupg/pubring.gpg
-------------------------------
pub 2048R/96F43D0FD189DD93 2021-06-10
uid AIOYTJ (AIO) <manage@aioytj.com>
sub 2048R/9B4B8F01C1F4D81B 2021-06-10

以上输出内容中,第一行pub后面的 96F43D0FD189DD93 即为该密钥的指纹,第二行uid后面的是 USER ID,下面导入密钥环中会用到。

3. 把密钥导入密钥环

1
2
3
4
gpg --armor --output public-key.txt --export "AIOYTJ (AIO) <manage@aioytj.com>"
sudo mkdir /usr/share/debsig/keyrings/96F43D0FD189DD93
sudo touch /usr/share/debsig/keyrings/96F43D0FD189DD93/pubring.gpg
sudo gpg --no-default-keyring --keyring /usr/share/debsig/keyrings/96F43D0FD189DD93/pubring.gpg --import public-key.txt

添加策略配置文件

1
2
sudo mkdir /etc/debsig/policies/96F43D0FD189DD93
sudo vi /etc/debsig/policies/96F43D0FD189DD93/pubring.pol

文件内容如下

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0"?>
<!DOCTYPE Policy SYSTEM "https://www.debian.org/debsig/1.0/policy.dtd">
<Policy>
<Origin Name="AIOYTJ" id="96F43D0FD189DD93" Description="AIO"/>
<Selection>
<Required Type="origin" File="pubring.gpg" id="96F43D0FD189DD93"/>
</Selection>
<Verification MinOptional="0">
<Required Type="origin" File="pubring.gpg" id="96F43D0FD189DD93"/>
</Verification>
</Policy>

4. 给deb包签名

1
debsigs --sign=origin -k 96F43D0FD189DD93 aiodriver_2.2.0.deb

输入上面生成key时的密码。

5. 打开强制签名

1
sudo vi /etc/dpkg/dpkg.cfg

注释掉 no-debsig,即

1
# no-debsig

5. 给lvm扩容

默认安装的情况下,lvm不会把磁盘用完,通过 sudo vgdisplay 查看剩余磁盘如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
--- Volume group ---
VG Name ubuntu-vg
System ID
Format lvm2
Metadata Areas 1
Metadata Sequence No 2
VG Access read/write
VG Status resizable
MAX LV 0
Cur LV 1
Open LV 1
Max PV 0
Cur PV 1
Act PV 1
VG Size <498.00 GiB
PE Size 4.00 MiB
Total PE 127487
Alloc PE / Size 25600 / 100.00 GiB
Free PE / Size 101887 / <398.00 GiB
VG UUID NxG1O4-6H7r-M76X-5tM9-wBZp-wEHs-nSTwGD

这里的 Free PE / Size 101887 / <398.00 GiB 就是还可以分配的量。
通过 lvdisplay 查看 lvm 名字如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
--- Logical volume ---
LV Path /dev/ubuntu-vg/ubuntu-lv
LV Name ubuntu-lv
VG Name ubuntu-vg
LV UUID a0sk4C-TtAv-QqBN-0bZN-QEAx-zhpw-nyTyeG
LV Write Access read/write
LV Creation host, time ubuntu-server, 2022-11-12 23:33:17 +0800
LV Status available
# open 1
LV Size 100.00 GiB
Current LE 25600
Segments 1
Allocation inherit
Read ahead sectors auto
- currently set to 256
Block device 253:0

扩容:

1
lvresize -l +101887 /dev/ubuntu-vg/ubuntu-lv

df -h 看到 /dev/mapper/ubuntu--vg-ubuntu--lv 还是只有100G,扩之:

1
sudo resize2fs /dev/mapper/ubuntu--vg-ubuntu--lv

记点Oracle的东西

表空间用户相关的东西

遇到一个删除表空间失败的情况,先删用户,再删表空间:

1
2
drop user xxx cascade;
drop tablespace xxx including contents and datafiles;

建表空间:

1
2
3
4
5
create tablespace XXX
datafile 'D:\Data\oradata\orcl\XXX.DBF' size 50M
autoextend on
next 10M
extent management local;

建用户:

1
2
3
create user XXX identified by xxx
default tablespace XXX
temporary tablespace TEMP;

给用户赋权:

1
grant connect, resource, dba to xxx;

有一次把表空间文件给删了,但是表空间还在,得想办法把表空间删掉:

1
alter database datafile 'XXX' offline drop;

非常简单的一句

下面是关于Oracle 11g导出空表的方法

Oracle 11g开始,exp的时候,默认情况下是不会导出无数据的空表的,这时候要分两部分解决,其一修改设置,让以后建立的新表都能导出空表,另外一个则是对已有的数据表进行设置,具体做法如下:

1、设置deferred_segment_creation 参数

查看当前设置:

1
show parameter deferred_segment_creation

修改参数:

1
alter system set deferred_segment_creation=false;

2、处理已有的空表:

先找到所有的空表:

1
select table_name from user_tables where NUM_ROWS=0;

把结果存到一个临时表里:

1
select 'alter table '||table_name||' allocate extent;' from user_tables where num_rows=0;

把查询结果作为语句执行一遍就O了。

CentOS 7 利用 systemctl 安装自动启服务

/usr/lib/systemd/system 文件夹下建立文件 shadowsocks.service,文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[Unit]
Description=Shadowsocks Server
After=network.target remote-fs.target nss-lookup.target

[Service]
Type=forking
PIDFile=/var/shadowsocks/pid
ExecStart=/usr/bin/ssserver -c /var/shadowsocks/conf.json -d start
ExecReload=/usr/bin/ssserver -c /var/shadowsocks/conf.json -d restart
ExecStop=/usr/bin/ssserver -d stop
PrivateTmp=true

[Install]
WantedBy=multi-user.target

给文件加上754权限:chmod +754 shadowsocks.service

  • systemctl start shadowsocks.service 就可以启动服务。
  • systemctl enable shadowsocks.service 就可以启用自动启服务。

hbase使用笔记

记完hive后来点hbase的

1
$ sqoop import --connect 'jdbc:sqlserver://192.168.1.xxx;username=sa;password=xxx;DatabaseName=xxx' --table sourcetablename --hbase-table hbasetablename --column-family cfname --split-by keycolumn --hbase-row-key rowkeycolumn --hbase-create-table

Sqoop使用笔记

CDH装完后想通过sqoop2来把数据导入hive,结果发现导数据的过程中,虽然生成了10个map,但其中9个map都是空的,导致最后生成了9个0大小的文件和一个巨大的文件,没找到原因,只在官网找到这么一句话 Note that 1.99.7 is not compatible with 1.4.6 and not feature complete, it is not intended for production deployment.

好吧,那我就不挣扎了,用Sqoop 1.4.6吧。把使用过程记录下来,以备后查:

  • 首先,去微软官网下载个sqlserver的jdbc驱动jar包,把它放在/var/lib/sqoop/下面。
  • 查看数据库
    1
    $ sqoop list-databases --connect 'jdbc:sqlserver://192.168.1.xxx;username=sa;password=xxx'
  • 导入一个库中的所有表,不过前提是这些表都有主键,按理说参数里应该有指定数据库名字的方法,我没找到,发现强行写在连接字符串里也行
    1
    $ sqoop import-all-tables --connect 'jdbc:sqlserver://192.168.1.xxx;username=sa;password=xxx;DatabaseName=XXX' --hive-import
  • 导入一个表,在这个表没有主键的情况下,可以用–split-by参数来指定,同样我也还是不知道怎么用参数来指定数据库名字
    1
    $ sqoop import --connect 'jdbc:sqlserver://192.168.1.xxx;username=sa;password=xxx;DatabaseName=XXX' --table tablename --split-by split_column --hive-import
  • 导完后发现数据比原数据库多了一半,真蛋疼,研究之后发现,原来是因为我原表中有文本字段,有不少逗号和\n,而hive默认是使用逗号来作为字段分隔符,\n来作为行分隔符,所以还是需要加上quote来保证不被切割
    1
    $ sqoop import --connect 'jdbc:sqlserver://192.168.1.xxx;username=sa;password=xxx;DatabaseName=XXX' --table tablename --split-by split_column --hive-import --fields-terminated-by , --escaped-by \\ --enclosed-by '\"'
  • 上面的方法中,所有的字段都会被加上""包括起来,其实也挺浪费空间的,使用另外一个参数 --optionally-enclosed-by '\"'来代替 --enclosed-by '\"',就会自动用引号括起需要的字段,别的字段还是原样裸的。

CentOS 6.7 安装 CDH 5.7 笔记

本笔记适用于 CentOS 6.7 + CDH 5.7.0 的部署

1、网络配置及基础组件安装

打开网络,配hostname

1
$ vi /etc/sysconfig/network
1
2
NETWORKING=yes
HOSTNAME=master

配置IP地址

1
$ vi /etc/sysconfig/network-scripts/ifcfg-eth0

网络调通后,安装必要组件

1
$ yum install psmisc libxslt-devel

关闭防火墙

1
2
service iptables stop
chkconfig iptables off

2、设置SSH无密码访问

在每台机器上执行

1
$ ssh-keygen -t rsa

一路回车到底

1
2
$ cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
$ chmod 600 ~/.ssh/authorized_keys

把所有机器的key文件放到master机器上

1
$ scp /root/.ssh/id_rsa.pub root@master:/root/.ssh/机器名_id_rsa.pub

在master上

1
2
$ cat /root/.ssh/机器1_id_rsa.pub >> /root/.ssh/authorized_keys
$ cat /root/.ssh/机器2_id_rsa.pub >> /root/.ssh/authorized_keys

最终再把authorized_keys放到每台机器上

1
$ scp /root/.ssh/authorized_keys root@机器名:/root/.ssh/

3、关闭SELINUX(官方文档上有这一步,实陆发现不做也行)

1
2
3
4
$ setenforce 0
```修改文件 `/etc/selinux/config`
```Ini
SELINUX=disabled

重启

4、安装mysql,建库,hive hue oozie

1
2
$ yum install -y mysql-server mysql mysql-devel
$ service mysqld start

设置数据库密码

1
$ mysql_secure_installation

建库

1
2
3
4
5
6
7
create database hive DEFAULT CHARSET utf8 COLLATE utf8_general_ci;
create database hue DEFAULT CHARSET utf8 COLLATE utf8_general_ci;
create database oozie DEFAULT CHARSET utf8 COLLATE utf8_general_ci;
create database amon DEFAULT CHARSET utf8 COLLATE utf8_general_ci;

grant all privileges on *.* to'root'@'master' identified by 'ddssoft' with grant option;
flush privileges;

5、安装ntp

1
$ yum install ntp

在这里,我们使用master作为集群的时间同步服务器,修改master上的ntp配置

1
$ vi /etc/ntp.conf

首先加上这一句,允许内网的机器从本机同步时间

1
restrict 192.168.1.0 mask 255.255.255.0 nomodify notrap

再把原有的server全部注释掉,换成

1
2
3
4
5
6
7
8
9
server 210.72.145.44 perfer
server 202.112.10.36
server 59.124.196.83
restrict 210.72.145.44 nomodify notrap noquery
restrict 202.112.10.36 nomodify notrap noquery
restrict 59.124.196.83 nomodify notrap noquery

server 127.127.1.0
fudge 127.127.1.0 stratum 10

保存退出后先同步一下时间,再打开ntp服务

1
2
3
$ ntpdate 202.112.10.36
$ service ntpd start
$ chkconfig ntpd on

每台机器修改配置,把原有的server全部注释掉

1
2
3
4
5
server master
restrict master nomodify notrap noquery

server 127.127.1.0
fudge 127.127.1.0 stratum 10

保存退出后先同步一下时间,再打开ntp服务

1
2
3
$ ntpdate master
$ service ntpd start
$ chkconfig ntpd on

6、安装jdk,从oracle下载1.7u80下载

1
$ rpm -ivh jdk-7u80-linux-x64.rpm

修改profile

1
$ vi /etc/profile

添加以下内容

1
2
3
export JAVA_HOME=/usr/java/default
export PATH=$JAVA_HOME/bin:$PATH
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar

立即应用一下profile

1
$ source /etc/profile

7、下载CDH并部署

下载压缩包:http://archive.cloudera.com/cm5/cm/5/cloudera-manager-el6-cm5.7.0_x86_64.tar.gz 并解压:

1
$ tar -xzvf cloudera-manager-el6-cm5.7.0_x86_64.tar.gz

把解压后的文件夹拷贝至 /opt/cloudera/

1
$ mv cloudera cm-5.7.0 /opt/

下载三个文件:http://archive.cloudera.com/cdh5/parcels/5.7.0/

1
2
3
CDH-5.7.0-1.cdh5.7.0.p0.45-el6.parcel
CDH-5.7.0-1.cdh5.7.0.p0.45-el6.parcel.sha1
manifest.json

下载后把 CDH-5.7.0-1.cdh5.7.0.p0.45-el6.parcel.sha1 重命名为 CDH-5.7.0-1.cdh5.7.0.p0.45-el6.parcel.sha

再把三个文件拷到/opt/cloudera/parcel-repo/目录中

1
$ cp CDH-5.7.0-1.cdh5.7.0.p0.45-el6* manifest.json /opt/cloudera/parcel-repo/

我们把CM管理控制台安装在主节点,在master机器上操作
首先把mysql的JDBC驱动文件放到java的lib目录下,因为后面多次会用到,所以个人习惯感觉放这里好一些,
用winscp将 mysql-connector-java-5.1.41-bin.jar 放到 /usr/java/default/lib/和/usr/java/default/jre/lib/
然后在cm管理台的文件夹下创建该驱动文件的链接,后面在别的组件里也会用到

1
2
$ cd /opt/cm-5.7.0/share/cmf/lib/
$ ln /usr/java/default/jre/lib/mysql-connector-java-5.1.41-bin.jar ./ -s

然后初始化数据库

1
2
$ cd /opt/cm-5.7.0/share/cmf/schema/
$ ./scm_prepare_database.sh mysql cm -hmaster -uroot -pddssoft --scm-host master scm scm scm

启动CM管理控制台服务端

1
$ /opt/cm-5.7.0/etc/init.d/cloudera-scm-server start

在所有机器上进行Agent配置:

1
2
3
$ useradd --system --home=/opt/cm-5.7.0/run/cloudera-scm-server/ --no-create-home --shell=/bin/false --comment "Cloudera SCM User" cloudera-scm
$ vi /opt/cm-5.7.0/etc/cloudera-scm-agent/config.ini
$ server_host=master

创建运行目录

1
$ mkdir /opt/cm-5.7.0/run/cloudera-scm-agent

启动每台机器上的Agent

1
$ /opt/cm-5.7.0/etc/init.d/cloudera-scm-agent start

在每台机器上配置

1
2
3
4
5
$ echo 10 > /proc/sys/vm/swappiness
$ sysctl -w vm.swappiness=10

$ echo never > /sys/kernel/mm/transparent_hugepage/defrag
$ echo never > /sys/kernel/mm/transparent_hugepage/enabled

修改 /etc/rc.local

1
$ vi /etc/rc.local

在最底下添加

1
2
echo never > /sys/kernel/mm/transparent_hugepage/defrag
echo never > /sys/kernel/mm/transparent_hugepage/enabled

8、通过浏览器进行安装

  • 登录地址:
    http://master1:7180/进行安装配置

  • 在选择Hosts界面上,输入机器名:master1,master2,node1,node2,node3,node4,node5

  • 在接下来选择版本的界面上,一定要选 CDH-5.7.0-1.cdh5.7.0.p0.45,其它都默认,如果选错版本后面又要进入漫长的重新下载过程

  • JDK不要勾,我们已经安装过了

  • 后面一路默认,在 Provide SSH login credentials. 界面输入root登录密码

在安装hive和oozie的过程中,需要输入mysql数据库信息,这时候检查数据库连接会不成功,需要单独处理一下mysql jdbc驱动。

hive 数据库驱动

1
2
$ cd /opt/cloudera/parcels/CDH-5.7.0-1.cdh5.7.0.p0.45/lib/hive/lib
$ ln /usr/java/default/lib/mysql-connector-java-5.1.41-bin.jar ./ -s

oozie 数据库驱动

1
$ cp mysql-connector-java-5.1.41-bin.jar /usr/share/java/mysql-connector-java.jar